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