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 }