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 }