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

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