github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/xsql/connector.go (about)

     1  package xsql
     2  
     3  import (
     4  	"context"
     5  	"database/sql/driver"
     6  	"io"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/jonboulle/clockwork"
    11  
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/bind"
    13  	metaHeaders "github.com/ydb-platform/ydb-go-sdk/v3/internal/meta"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/meta"
    18  	"github.com/ydb-platform/ydb-go-sdk/v3/retry/budget"
    19  	"github.com/ydb-platform/ydb-go-sdk/v3/scheme"
    20  	"github.com/ydb-platform/ydb-go-sdk/v3/scripting"
    21  	"github.com/ydb-platform/ydb-go-sdk/v3/table"
    22  	"github.com/ydb-platform/ydb-go-sdk/v3/table/options"
    23  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    24  )
    25  
    26  type ConnectorOption interface {
    27  	Apply(c *Connector) error
    28  }
    29  
    30  type defaultQueryModeConnectorOption QueryMode
    31  
    32  func (mode defaultQueryModeConnectorOption) Apply(c *Connector) error {
    33  	c.defaultQueryMode = QueryMode(mode)
    34  
    35  	return nil
    36  }
    37  
    38  type QueryBindConnectorOption interface {
    39  	ConnectorOption
    40  	bind.Bind
    41  }
    42  
    43  type queryBindConnectorOption struct {
    44  	bind.Bind
    45  }
    46  
    47  func (o queryBindConnectorOption) Apply(c *Connector) error {
    48  	c.Bindings = bind.Sort(append(c.Bindings, o.Bind))
    49  
    50  	return nil
    51  }
    52  
    53  type tablePathPrefixConnectorOption struct {
    54  	bind.TablePathPrefix
    55  }
    56  
    57  func (o tablePathPrefixConnectorOption) Apply(c *Connector) error {
    58  	c.Bindings = bind.Sort(append(c.Bindings, o.TablePathPrefix))
    59  	c.pathNormalizer = o.TablePathPrefix
    60  
    61  	return nil
    62  }
    63  
    64  func WithQueryBind(bind bind.Bind) QueryBindConnectorOption {
    65  	return queryBindConnectorOption{Bind: bind}
    66  }
    67  
    68  func WithTablePathPrefix(tablePathPrefix string) QueryBindConnectorOption {
    69  	return tablePathPrefixConnectorOption{TablePathPrefix: bind.TablePathPrefix(tablePathPrefix)}
    70  }
    71  
    72  func WithDefaultQueryMode(mode QueryMode) ConnectorOption {
    73  	return defaultQueryModeConnectorOption(mode)
    74  }
    75  
    76  type defaultTxControlOption struct {
    77  	txControl *table.TransactionControl
    78  }
    79  
    80  func (opt defaultTxControlOption) Apply(c *Connector) error {
    81  	c.defaultTxControl = opt.txControl
    82  
    83  	return nil
    84  }
    85  
    86  func WithDefaultTxControl(txControl *table.TransactionControl) ConnectorOption {
    87  	return defaultTxControlOption{txControl}
    88  }
    89  
    90  type defaultDataQueryOptionsConnectorOption []options.ExecuteDataQueryOption
    91  
    92  func (opts defaultDataQueryOptionsConnectorOption) Apply(c *Connector) error {
    93  	c.defaultDataQueryOpts = append(c.defaultDataQueryOpts, opts...)
    94  
    95  	return nil
    96  }
    97  
    98  func WithDefaultDataQueryOptions(opts ...options.ExecuteDataQueryOption) ConnectorOption {
    99  	return defaultDataQueryOptionsConnectorOption(opts)
   100  }
   101  
   102  type defaultScanQueryOptionsConnectorOption []options.ExecuteScanQueryOption
   103  
   104  func (opts defaultScanQueryOptionsConnectorOption) Apply(c *Connector) error {
   105  	c.defaultScanQueryOpts = append(c.defaultScanQueryOpts, opts...)
   106  
   107  	return nil
   108  }
   109  
   110  func WithDefaultScanQueryOptions(opts ...options.ExecuteScanQueryOption) ConnectorOption {
   111  	return defaultScanQueryOptionsConnectorOption(opts)
   112  }
   113  
   114  type traceConnectorOption struct {
   115  	t    *trace.DatabaseSQL
   116  	opts []trace.DatabaseSQLComposeOption
   117  }
   118  
   119  func (option traceConnectorOption) Apply(c *Connector) error {
   120  	c.trace = c.trace.Compose(option.t, option.opts...)
   121  
   122  	return nil
   123  }
   124  
   125  func WithTrace(t *trace.DatabaseSQL, opts ...trace.DatabaseSQLComposeOption) ConnectorOption {
   126  	return traceConnectorOption{t, opts}
   127  }
   128  
   129  type disableServerBalancerConnectorOption struct{}
   130  
   131  func (d disableServerBalancerConnectorOption) Apply(c *Connector) error {
   132  	c.disableServerBalancer = true
   133  
   134  	return nil
   135  }
   136  
   137  func WithDisableServerBalancer() ConnectorOption {
   138  	return disableServerBalancerConnectorOption{}
   139  }
   140  
   141  type idleThresholdConnectorOption time.Duration
   142  
   143  func (idleThreshold idleThresholdConnectorOption) Apply(c *Connector) error {
   144  	c.idleThreshold = time.Duration(idleThreshold)
   145  
   146  	return nil
   147  }
   148  
   149  func WithIdleThreshold(idleThreshold time.Duration) ConnectorOption {
   150  	return idleThresholdConnectorOption(idleThreshold)
   151  }
   152  
   153  type onCloseConnectorOption func(connector *Connector)
   154  
   155  func (f onCloseConnectorOption) Apply(c *Connector) error {
   156  	c.onClose = append(c.onClose, f)
   157  
   158  	return nil
   159  }
   160  
   161  func WithOnClose(f func(connector *Connector)) ConnectorOption {
   162  	return onCloseConnectorOption(f)
   163  }
   164  
   165  type traceRetryConnectorOption struct {
   166  	t *trace.Retry
   167  }
   168  
   169  func (t traceRetryConnectorOption) Apply(c *Connector) error {
   170  	c.traceRetry = t.t
   171  
   172  	return nil
   173  }
   174  
   175  func WithTraceRetry(t *trace.Retry) ConnectorOption {
   176  	return traceRetryConnectorOption{t: t}
   177  }
   178  
   179  type retryBudgetConnectorOption struct {
   180  	b budget.Budget
   181  }
   182  
   183  func (l retryBudgetConnectorOption) Apply(c *Connector) error {
   184  	c.retryBudget = l.b
   185  
   186  	return nil
   187  }
   188  
   189  func WithretryBudget(b budget.Budget) ConnectorOption {
   190  	return retryBudgetConnectorOption{b: b}
   191  }
   192  
   193  type fakeTxConnectorOption QueryMode
   194  
   195  func (m fakeTxConnectorOption) Apply(c *Connector) error {
   196  	c.fakeTxModes = append(c.fakeTxModes, QueryMode(m))
   197  
   198  	return nil
   199  }
   200  
   201  // WithFakeTx returns a copy of context with given QueryMode
   202  func WithFakeTx(m QueryMode) ConnectorOption {
   203  	return fakeTxConnectorOption(m)
   204  }
   205  
   206  type ydbDriver interface {
   207  	Name() string
   208  	Table() table.Client
   209  	Scripting() scripting.Client
   210  	Scheme() scheme.Client
   211  }
   212  
   213  func Open(parent ydbDriver, opts ...ConnectorOption) (_ *Connector, err error) {
   214  	c := &Connector{
   215  		parent:           parent,
   216  		clock:            clockwork.NewRealClock(),
   217  		conns:            make(map[*conn]struct{}),
   218  		defaultTxControl: table.DefaultTxControl(),
   219  		defaultQueryMode: DefaultQueryMode,
   220  		pathNormalizer:   bind.TablePathPrefix(parent.Name()),
   221  		trace:            &trace.DatabaseSQL{},
   222  	}
   223  	for _, opt := range opts {
   224  		if opt != nil {
   225  			if err = opt.Apply(c); err != nil {
   226  				return nil, err
   227  			}
   228  		}
   229  	}
   230  	if c.idleThreshold > 0 {
   231  		c.idleStopper = c.idleCloser()
   232  	}
   233  
   234  	return c, nil
   235  }
   236  
   237  type pathNormalizer interface {
   238  	NormalizePath(folderOrTable string) string
   239  }
   240  
   241  // Connector is a producer of database/sql connections
   242  type Connector struct {
   243  	parent ydbDriver
   244  
   245  	clock clockwork.Clock
   246  
   247  	Bindings       bind.Bindings
   248  	pathNormalizer pathNormalizer
   249  
   250  	fakeTxModes []QueryMode
   251  
   252  	onClose []func(connector *Connector)
   253  
   254  	conns    map[*conn]struct{}
   255  	connsMtx sync.RWMutex
   256  
   257  	idleStopper func()
   258  
   259  	defaultTxControl      *table.TransactionControl
   260  	defaultQueryMode      QueryMode
   261  	defaultDataQueryOpts  []options.ExecuteDataQueryOption
   262  	defaultScanQueryOpts  []options.ExecuteScanQueryOption
   263  	disableServerBalancer bool
   264  	idleThreshold         time.Duration
   265  
   266  	trace       *trace.DatabaseSQL
   267  	traceRetry  *trace.Retry
   268  	retryBudget budget.Budget
   269  }
   270  
   271  var (
   272  	_ driver.Connector = &Connector{}
   273  	_ io.Closer        = &Connector{}
   274  )
   275  
   276  func (c *Connector) idleCloser() (idleStopper func()) {
   277  	var ctx context.Context
   278  	ctx, idleStopper = xcontext.WithCancel(context.Background())
   279  	go func() {
   280  		for {
   281  			idleThresholdTimer := c.clock.NewTimer(c.idleThreshold)
   282  			select {
   283  			case <-ctx.Done():
   284  				idleThresholdTimer.Stop()
   285  
   286  				return
   287  			case <-idleThresholdTimer.Chan():
   288  				idleThresholdTimer.Stop() // no really need, stop for common style only
   289  				c.connsMtx.RLock()
   290  				conns := make([]*conn, 0, len(c.conns))
   291  				for cc := range c.conns {
   292  					conns = append(conns, cc)
   293  				}
   294  				c.connsMtx.RUnlock()
   295  				for _, cc := range conns {
   296  					if cc.sinceLastUsage() > c.idleThreshold {
   297  						cc.session.Close(context.Background())
   298  					}
   299  				}
   300  			}
   301  		}
   302  	}()
   303  
   304  	return idleStopper
   305  }
   306  
   307  func (c *Connector) Close() (err error) {
   308  	defer func() {
   309  		for _, onClose := range c.onClose {
   310  			onClose(c)
   311  		}
   312  	}()
   313  	if c.idleStopper != nil {
   314  		c.idleStopper()
   315  	}
   316  
   317  	return nil
   318  }
   319  
   320  func (c *Connector) attach(cc *conn) {
   321  	c.connsMtx.Lock()
   322  	defer c.connsMtx.Unlock()
   323  	c.conns[cc] = struct{}{}
   324  }
   325  
   326  func (c *Connector) detach(cc *conn) {
   327  	c.connsMtx.Lock()
   328  	defer c.connsMtx.Unlock()
   329  	delete(c.conns, cc)
   330  }
   331  
   332  func (c *Connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
   333  	var (
   334  		onDone = trace.DatabaseSQLOnConnectorConnect(
   335  			c.trace, &ctx,
   336  			stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql.(*Connector).Connect"),
   337  		)
   338  		session table.ClosableSession
   339  	)
   340  	defer func() {
   341  		onDone(err, session)
   342  	}()
   343  	if !c.disableServerBalancer {
   344  		ctx = meta.WithAllowFeatures(ctx, metaHeaders.HintSessionBalancer)
   345  	}
   346  	session, err = c.parent.Table().CreateSession(ctx) //nolint
   347  	if err != nil {
   348  		return nil, xerrors.WithStackTrace(err)
   349  	}
   350  
   351  	return newConn(ctx, c, session, withDefaultTxControl(c.defaultTxControl),
   352  		withDefaultQueryMode(c.defaultQueryMode),
   353  		withDataOpts(c.defaultDataQueryOpts...),
   354  		withScanOpts(c.defaultScanQueryOpts...),
   355  		withTrace(c.trace),
   356  		withFakeTxModes(c.fakeTxModes...),
   357  	), nil
   358  }
   359  
   360  func (c *Connector) Driver() driver.Driver {
   361  	return &driverWrapper{c: c}
   362  }
   363  
   364  type driverWrapper struct {
   365  	c *Connector
   366  }
   367  
   368  func (d *driverWrapper) TraceRetry() *trace.Retry {
   369  	return d.c.traceRetry
   370  }
   371  
   372  func (d *driverWrapper) RetryBudget() budget.Budget {
   373  	return d.c.retryBudget
   374  }
   375  
   376  func (d *driverWrapper) Open(_ string) (driver.Conn, error) {
   377  	return nil, ErrUnsupported
   378  }