github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/driver.go (about)

     1  package ydb
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"sync"
     9  
    10  	"google.golang.org/grpc"
    11  
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/config"
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/coordination"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/discovery"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/balancer"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/conn"
    17  	internalCoordination "github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination"
    18  	coordinationConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination/config"
    19  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/credentials"
    20  	internalDiscovery "github.com/ydb-platform/ydb-go-sdk/v3/internal/discovery"
    21  	discoveryConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/discovery/config"
    22  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/dsn"
    23  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/endpoint"
    24  	internalQuery "github.com/ydb-platform/ydb-go-sdk/v3/internal/query"
    25  	queryConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/config"
    26  	internalRatelimiter "github.com/ydb-platform/ydb-go-sdk/v3/internal/ratelimiter"
    27  	ratelimiterConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/ratelimiter/config"
    28  	internalScheme "github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme"
    29  	schemeConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme/config"
    30  	internalScripting "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting"
    31  	scriptingConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting/config"
    32  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    33  	internalTable "github.com/ydb-platform/ydb-go-sdk/v3/internal/table"
    34  	tableConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/config"
    35  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/topic/topicclientinternal"
    36  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    37  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    38  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql"
    39  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync"
    40  	"github.com/ydb-platform/ydb-go-sdk/v3/log"
    41  	"github.com/ydb-platform/ydb-go-sdk/v3/operation"
    42  	"github.com/ydb-platform/ydb-go-sdk/v3/ratelimiter"
    43  	"github.com/ydb-platform/ydb-go-sdk/v3/scheme"
    44  	"github.com/ydb-platform/ydb-go-sdk/v3/scripting"
    45  	"github.com/ydb-platform/ydb-go-sdk/v3/table"
    46  	"github.com/ydb-platform/ydb-go-sdk/v3/topic"
    47  	"github.com/ydb-platform/ydb-go-sdk/v3/topic/topicoptions"
    48  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    49  )
    50  
    51  var _ Connection = (*Driver)(nil)
    52  
    53  // Driver type provide access to YDB service clients
    54  type Driver struct {
    55  	ctxCancel context.CancelFunc
    56  
    57  	userInfo *dsn.UserInfo
    58  
    59  	logger        log.Logger
    60  	loggerOpts    []log.Option
    61  	loggerDetails trace.Detailer
    62  
    63  	opts []Option
    64  
    65  	config  *config.Config
    66  	options []config.Option
    67  
    68  	discovery        *xsync.Once[*internalDiscovery.Client]
    69  	discoveryOptions []discoveryConfig.Option
    70  
    71  	operation *xsync.Once[*operation.Client]
    72  
    73  	table        *xsync.Once[*internalTable.Client]
    74  	tableOptions []tableConfig.Option
    75  
    76  	query        *xsync.Once[*internalQuery.Client]
    77  	queryOptions []queryConfig.Option
    78  
    79  	scripting        *xsync.Once[*internalScripting.Client]
    80  	scriptingOptions []scriptingConfig.Option
    81  
    82  	scheme        *xsync.Once[*internalScheme.Client]
    83  	schemeOptions []schemeConfig.Option
    84  
    85  	coordination        *xsync.Once[*internalCoordination.Client]
    86  	coordinationOptions []coordinationConfig.Option
    87  
    88  	ratelimiter        *xsync.Once[*internalRatelimiter.Client]
    89  	ratelimiterOptions []ratelimiterConfig.Option
    90  
    91  	topic        *xsync.Once[*topicclientinternal.Client]
    92  	topicOptions []topicoptions.TopicOption
    93  
    94  	databaseSQLOptions []xsql.ConnectorOption
    95  
    96  	pool *conn.Pool
    97  
    98  	mtx      sync.Mutex
    99  	balancer *balancer.Balancer
   100  
   101  	children    map[uint64]*Driver
   102  	childrenMtx xsync.Mutex
   103  	onClose     []func(c *Driver)
   104  
   105  	panicCallback func(e interface{})
   106  }
   107  
   108  func (d *Driver) trace() *trace.Driver {
   109  	if d.config != nil {
   110  		return d.config.Trace()
   111  	}
   112  
   113  	return &trace.Driver{}
   114  }
   115  
   116  // Close closes Driver and clear resources
   117  //
   118  //nolint:nonamedreturns
   119  func (d *Driver) Close(ctx context.Context) (finalErr error) {
   120  	onDone := trace.DriverOnClose(d.trace(), &ctx,
   121  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/ydb.(*Driver).Close"),
   122  	)
   123  	defer func() {
   124  		onDone(finalErr)
   125  	}()
   126  	d.ctxCancel()
   127  
   128  	d.mtx.Lock()
   129  	defer d.mtx.Unlock()
   130  
   131  	d.ctxCancel()
   132  
   133  	defer func() {
   134  		for _, f := range d.onClose {
   135  			f(d)
   136  		}
   137  	}()
   138  
   139  	closes := make([]func(context.Context) error, 0)
   140  	d.childrenMtx.WithLock(func() {
   141  		for _, child := range d.children {
   142  			closes = append(closes, child.Close)
   143  		}
   144  		d.children = nil
   145  	})
   146  
   147  	closes = append(
   148  		closes,
   149  		d.ratelimiter.Close,
   150  		d.coordination.Close,
   151  		d.scheme.Close,
   152  		d.scripting.Close,
   153  		d.table.Close,
   154  		d.operation.Close,
   155  		d.query.Close,
   156  		d.topic.Close,
   157  		d.discovery.Close,
   158  		d.balancer.Close,
   159  		d.pool.Release,
   160  	)
   161  
   162  	var issues []error
   163  	for _, f := range closes {
   164  		if err := f(ctx); err != nil {
   165  			issues = append(issues, err)
   166  		}
   167  	}
   168  
   169  	if len(issues) > 0 {
   170  		return xerrors.WithStackTrace(xerrors.NewWithIssues("close failed", issues...))
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  // Endpoint returns initial endpoint
   177  func (d *Driver) Endpoint() string {
   178  	return d.config.Endpoint()
   179  }
   180  
   181  // Name returns database name
   182  func (d *Driver) Name() string {
   183  	return d.config.Database()
   184  }
   185  
   186  // Secure returns true if database Driver is secure
   187  func (d *Driver) Secure() bool {
   188  	return d.config.Secure()
   189  }
   190  
   191  // Table returns table client
   192  func (d *Driver) Table() table.Client {
   193  	return d.table.Must()
   194  }
   195  
   196  // Query returns query client
   197  func (d *Driver) Query() *internalQuery.Client {
   198  	return d.query.Must()
   199  }
   200  
   201  // Scheme returns scheme client
   202  func (d *Driver) Scheme() scheme.Client {
   203  	return d.scheme.Must()
   204  }
   205  
   206  // Coordination returns coordination client
   207  func (d *Driver) Coordination() coordination.Client {
   208  	return d.coordination.Must()
   209  }
   210  
   211  // Ratelimiter returns ratelimiter client
   212  func (d *Driver) Ratelimiter() ratelimiter.Client {
   213  	return d.ratelimiter.Must()
   214  }
   215  
   216  // Discovery returns discovery client
   217  func (d *Driver) Discovery() discovery.Client {
   218  	return d.discovery.Must()
   219  }
   220  
   221  // Operation returns operation client
   222  //
   223  // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental
   224  func (d *Driver) Operation() *operation.Client {
   225  	return d.operation.Must()
   226  }
   227  
   228  // Scripting returns scripting client
   229  func (d *Driver) Scripting() scripting.Client {
   230  	return d.scripting.Must()
   231  }
   232  
   233  // Topic returns topic client
   234  func (d *Driver) Topic() topic.Client {
   235  	return d.topic.Must()
   236  }
   237  
   238  // Open connects to database by DSN and return driver runtime holder
   239  //
   240  // DSN accept Driver string like
   241  //
   242  //	"grpc[s]://{endpoint}/{database}[?param=value]"
   243  //
   244  // See sugar.DSN helper for make dsn from endpoint and database
   245  //
   246  //nolint:nonamedreturns
   247  func Open(ctx context.Context, dsn string, opts ...Option) (_ *Driver, _ error) {
   248  	opts = append(append(make([]Option, 0, len(opts)+1), WithConnectionString(dsn)), opts...)
   249  
   250  	for parserIdx := range dsnParsers {
   251  		if parser := dsnParsers[parserIdx]; parser != nil {
   252  			optsFromParser, err := parser(dsn)
   253  			if err != nil {
   254  				return nil, xerrors.WithStackTrace(fmt.Errorf("data source name '%s' wrong: %w", dsn, err))
   255  			}
   256  			opts = append(opts, optsFromParser...)
   257  		}
   258  	}
   259  
   260  	d, err := newConnectionFromOptions(ctx, opts...)
   261  	if err != nil {
   262  		return nil, xerrors.WithStackTrace(err)
   263  	}
   264  
   265  	onDone := trace.DriverOnInit(
   266  		d.trace(), &ctx,
   267  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/ydb.Open"),
   268  		d.config.Endpoint(), d.config.Database(), d.config.Secure(),
   269  	)
   270  	defer func() {
   271  		onDone(err)
   272  	}()
   273  
   274  	if err = d.connect(ctx); err != nil {
   275  		if d.pool != nil {
   276  			_ = d.pool.Release(ctx)
   277  		}
   278  
   279  		return nil, xerrors.WithStackTrace(err)
   280  	}
   281  
   282  	return d, nil
   283  }
   284  
   285  func MustOpen(ctx context.Context, dsn string, opts ...Option) *Driver {
   286  	db, err := Open(ctx, dsn, opts...)
   287  	if err != nil {
   288  		panic(err)
   289  	}
   290  
   291  	return db
   292  }
   293  
   294  // New connects to database and return driver runtime holder
   295  //
   296  // Deprecated: use ydb.Open instead.
   297  // New func have no required arguments, such as connection string.
   298  // Thats why we recognize that New have wrong signature.
   299  // Will be removed after Oct 2024.
   300  // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated
   301  func New(ctx context.Context, opts ...Option) (_ *Driver, err error) { //nolint:nonamedreturns
   302  	d, err := newConnectionFromOptions(ctx, opts...)
   303  	if err != nil {
   304  		return nil, xerrors.WithStackTrace(err)
   305  	}
   306  
   307  	onDone := trace.DriverOnInit(
   308  		d.trace(), &ctx,
   309  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/ydb.New"),
   310  		d.config.Endpoint(), d.config.Database(), d.config.Secure(),
   311  	)
   312  	defer func() {
   313  		onDone(err)
   314  	}()
   315  
   316  	if err = d.connect(ctx); err != nil {
   317  		return nil, xerrors.WithStackTrace(err)
   318  	}
   319  
   320  	return d, nil
   321  }
   322  
   323  //nolint:cyclop, nonamedreturns, funlen
   324  func newConnectionFromOptions(ctx context.Context, opts ...Option) (_ *Driver, err error) {
   325  	ctx, driverCtxCancel := xcontext.WithCancel(xcontext.ValueOnly(ctx))
   326  	defer func() {
   327  		if err != nil {
   328  			driverCtxCancel()
   329  		}
   330  	}()
   331  
   332  	d := &Driver{
   333  		children:  make(map[uint64]*Driver),
   334  		ctxCancel: driverCtxCancel,
   335  	}
   336  
   337  	if caFile, has := os.LookupEnv("YDB_SSL_ROOT_CERTIFICATES_FILE"); has {
   338  		d.opts = append(d.opts,
   339  			WithCertificatesFromFile(caFile),
   340  		)
   341  	}
   342  	if logLevel, has := os.LookupEnv("YDB_LOG_SEVERITY_LEVEL"); has {
   343  		if l := log.FromString(logLevel); l < log.QUIET {
   344  			d.opts = append(d.opts,
   345  				WithLogger(
   346  					log.Default(os.Stderr,
   347  						log.WithMinLevel(log.FromString(logLevel)),
   348  						log.WithColoring(),
   349  					),
   350  					trace.MatchDetails(
   351  						os.Getenv("YDB_LOG_DETAILS"),
   352  						trace.WithDefaultDetails(trace.DetailsAll),
   353  					),
   354  					log.WithLogQuery(),
   355  				),
   356  			)
   357  		}
   358  	}
   359  	d.opts = append(d.opts, opts...)
   360  	for _, opt := range d.opts {
   361  		if opt != nil {
   362  			err = opt(ctx, d)
   363  			if err != nil {
   364  				return nil, xerrors.WithStackTrace(err)
   365  			}
   366  		}
   367  	}
   368  	if d.logger != nil {
   369  		for _, opt := range []Option{
   370  			WithTraceDriver(log.Driver(d.logger, d.loggerDetails, d.loggerOpts...)),       //nolint:contextcheck
   371  			WithTraceTable(log.Table(d.logger, d.loggerDetails, d.loggerOpts...)),         //nolint:contextcheck
   372  			WithTraceQuery(log.Query(d.logger, d.loggerDetails, d.loggerOpts...)),         //nolint:contextcheck
   373  			WithTraceScripting(log.Scripting(d.logger, d.loggerDetails, d.loggerOpts...)), //nolint:contextcheck
   374  			WithTraceScheme(log.Scheme(d.logger, d.loggerDetails, d.loggerOpts...)),
   375  			WithTraceCoordination(log.Coordination(d.logger, d.loggerDetails, d.loggerOpts...)),
   376  			WithTraceRatelimiter(log.Ratelimiter(d.logger, d.loggerDetails, d.loggerOpts...)),
   377  			WithTraceDiscovery(log.Discovery(d.logger, d.loggerDetails, d.loggerOpts...)),     //nolint:contextcheck
   378  			WithTraceTopic(log.Topic(d.logger, d.loggerDetails, d.loggerOpts...)),             //nolint:contextcheck
   379  			WithTraceDatabaseSQL(log.DatabaseSQL(d.logger, d.loggerDetails, d.loggerOpts...)), //nolint:contextcheck
   380  			WithTraceRetry(log.Retry(d.logger, d.loggerDetails, d.loggerOpts...)),             //nolint:contextcheck
   381  		} {
   382  			if opt != nil {
   383  				err = opt(ctx, d)
   384  				if err != nil {
   385  					return nil, xerrors.WithStackTrace(err)
   386  				}
   387  			}
   388  		}
   389  	}
   390  	d.config = config.New(d.options...)
   391  
   392  	return d, nil
   393  }
   394  
   395  //nolint:cyclop, nonamedreturns, funlen
   396  func (d *Driver) connect(ctx context.Context) (err error) {
   397  	if d.config.Endpoint() == "" {
   398  		return xerrors.WithStackTrace(errors.New("configuration: empty dial address")) //nolint:goerr113
   399  	}
   400  
   401  	if d.config.Database() == "" {
   402  		return xerrors.WithStackTrace(errors.New("configuration: empty database")) //nolint:goerr113
   403  	}
   404  
   405  	if d.userInfo != nil {
   406  		d.config = d.config.With(config.WithCredentials(
   407  			credentials.NewStaticCredentials(
   408  				d.userInfo.User, d.userInfo.Password,
   409  				d.config.Endpoint(),
   410  				credentials.WithGrpcDialOptions(d.config.GrpcDialOptions()...),
   411  			),
   412  		))
   413  	}
   414  
   415  	if d.pool == nil {
   416  		d.pool = conn.NewPool(ctx, d.config)
   417  	}
   418  	if d.balancer == nil {
   419  		d.balancer, err = balancer.New(ctx, d.config, d.pool, d.discoveryOptions...)
   420  		if err != nil {
   421  			return xerrors.WithStackTrace(err)
   422  		}
   423  	}
   424  
   425  	d.table = xsync.OnceValue(func() (*internalTable.Client, error) {
   426  		return internalTable.New(xcontext.ValueOnly(ctx),
   427  			d.balancer,
   428  			tableConfig.New(
   429  				append(
   430  					// prepend common params from root config
   431  					[]tableConfig.Option{
   432  						tableConfig.With(d.config.Common),
   433  					},
   434  					d.tableOptions...,
   435  				)...,
   436  			),
   437  		), nil
   438  	})
   439  
   440  	d.query = xsync.OnceValue(func() (*internalQuery.Client, error) {
   441  		return internalQuery.New(xcontext.ValueOnly(ctx),
   442  			d.balancer,
   443  			queryConfig.New(
   444  				append(
   445  					// prepend common params from root config
   446  					[]queryConfig.Option{
   447  						queryConfig.With(d.config.Common),
   448  					},
   449  					d.queryOptions...,
   450  				)...,
   451  			),
   452  		), nil
   453  	})
   454  	if err != nil {
   455  		return xerrors.WithStackTrace(err)
   456  	}
   457  
   458  	d.scheme = xsync.OnceValue(func() (*internalScheme.Client, error) {
   459  		return internalScheme.New(xcontext.ValueOnly(ctx),
   460  			d.balancer,
   461  			schemeConfig.New(
   462  				append(
   463  					// prepend common params from root config
   464  					[]schemeConfig.Option{
   465  						schemeConfig.WithDatabaseName(d.Name()),
   466  						schemeConfig.With(d.config.Common),
   467  					},
   468  					d.schemeOptions...,
   469  				)...,
   470  			),
   471  		), nil
   472  	})
   473  
   474  	d.coordination = xsync.OnceValue(func() (*internalCoordination.Client, error) {
   475  		return internalCoordination.New(xcontext.ValueOnly(ctx),
   476  			d.balancer,
   477  			coordinationConfig.New(
   478  				append(
   479  					// prepend common params from root config
   480  					[]coordinationConfig.Option{
   481  						coordinationConfig.With(d.config.Common),
   482  					},
   483  					d.coordinationOptions...,
   484  				)...,
   485  			),
   486  		), nil
   487  	})
   488  
   489  	d.ratelimiter = xsync.OnceValue(func() (*internalRatelimiter.Client, error) {
   490  		return internalRatelimiter.New(xcontext.ValueOnly(ctx),
   491  			d.balancer,
   492  			ratelimiterConfig.New(
   493  				append(
   494  					// prepend common params from root config
   495  					[]ratelimiterConfig.Option{
   496  						ratelimiterConfig.With(d.config.Common),
   497  					},
   498  					d.ratelimiterOptions...,
   499  				)...,
   500  			),
   501  		), nil
   502  	})
   503  
   504  	d.discovery = xsync.OnceValue(func() (*internalDiscovery.Client, error) {
   505  		return internalDiscovery.New(xcontext.ValueOnly(ctx),
   506  			d.pool.Get(endpoint.New(d.config.Endpoint())),
   507  			discoveryConfig.New(
   508  				append(
   509  					// prepend common params from root config
   510  					[]discoveryConfig.Option{
   511  						discoveryConfig.With(d.config.Common),
   512  						discoveryConfig.WithEndpoint(d.Endpoint()),
   513  						discoveryConfig.WithDatabase(d.Name()),
   514  						discoveryConfig.WithSecure(d.Secure()),
   515  						discoveryConfig.WithMeta(d.config.Meta()),
   516  					},
   517  					d.discoveryOptions...,
   518  				)...,
   519  			),
   520  		), nil
   521  	})
   522  
   523  	d.operation = xsync.OnceValue(func() (*operation.Client, error) {
   524  		return operation.New(xcontext.ValueOnly(ctx),
   525  			d.balancer,
   526  		), nil
   527  	})
   528  
   529  	d.scripting = xsync.OnceValue(func() (*internalScripting.Client, error) {
   530  		return internalScripting.New(xcontext.ValueOnly(ctx),
   531  			d.balancer,
   532  			scriptingConfig.New(
   533  				append(
   534  					// prepend common params from root config
   535  					[]scriptingConfig.Option{
   536  						scriptingConfig.With(d.config.Common),
   537  					},
   538  					d.scriptingOptions...,
   539  				)...,
   540  			),
   541  		), nil
   542  	})
   543  
   544  	d.topic = xsync.OnceValue(func() (*topicclientinternal.Client, error) {
   545  		return topicclientinternal.New(xcontext.ValueOnly(ctx),
   546  			d.balancer,
   547  			d.config.Credentials(),
   548  			append(
   549  				// prepend common params from root config
   550  				[]topicoptions.TopicOption{
   551  					topicoptions.WithOperationTimeout(d.config.OperationTimeout()),
   552  					topicoptions.WithOperationCancelAfter(d.config.OperationCancelAfter()),
   553  				},
   554  				d.topicOptions...,
   555  			)...,
   556  		), nil
   557  	})
   558  
   559  	return nil
   560  }
   561  
   562  // GRPCConn casts *ydb.Driver to grpc.ClientConnInterface for executing
   563  // unary and streaming RPC over internal driver balancer.
   564  //
   565  // Warning: for connect to driver-unsupported YDB services
   566  func GRPCConn(cc *Driver) grpc.ClientConnInterface {
   567  	return conn.WithContextModifier(cc.balancer, conn.WithoutWrapping)
   568  }