github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/query/client.go (about) 1 package query 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" 8 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Operations" 9 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" 10 "google.golang.org/grpc" 11 12 "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" 13 "github.com/ydb-platform/ydb-go-sdk/v3/internal/closer" 14 "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" 15 "github.com/ydb-platform/ydb-go-sdk/v3/internal/pool" 16 "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/config" 17 "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" 18 "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/result" 19 "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/session" 20 "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/tx" 21 "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" 22 "github.com/ydb-platform/ydb-go-sdk/v3/internal/types" 23 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 24 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 25 "github.com/ydb-platform/ydb-go-sdk/v3/query" 26 "github.com/ydb-platform/ydb-go-sdk/v3/retry" 27 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 28 ) 29 30 //go:generate mockgen -destination grpc_client_mock_test.go --typed -package query -write_package_comment=false github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1 QueryServiceClient,QueryService_AttachSessionClient,QueryService_ExecuteQueryClient 31 32 var ( 33 _ query.Client = (*Client)(nil) 34 _ sessionPool = (*pool.Pool[*Session, Session])(nil) 35 ) 36 37 type ( 38 sessionPool interface { 39 closer.Closer 40 41 Stats() pool.Stats 42 With(ctx context.Context, f func(ctx context.Context, s *Session) error, opts ...retry.Option) error 43 } 44 Client struct { 45 config *config.Config 46 client Ydb_Query_V1.QueryServiceClient 47 pool sessionPool 48 49 done chan struct{} 50 } 51 ) 52 53 func fetchScriptResults(ctx context.Context, 54 client Ydb_Query_V1.QueryServiceClient, 55 opID string, opts ...options.FetchScriptOption, 56 ) (*options.FetchScriptResult, error) { 57 r, err := retry.RetryWithResult(ctx, func(ctx context.Context) (*options.FetchScriptResult, error) { 58 request := &options.FetchScriptResultsRequest{ 59 FetchScriptResultsRequest: Ydb_Query.FetchScriptResultsRequest{ 60 OperationId: opID, 61 }, 62 } 63 for _, opt := range opts { 64 if opt != nil { 65 opt(request) 66 } 67 } 68 69 response, err := client.FetchScriptResults(ctx, &request.FetchScriptResultsRequest) 70 if err != nil { 71 return nil, xerrors.WithStackTrace(err) 72 } 73 74 rs := response.GetResultSet() 75 columns := rs.GetColumns() 76 columnNames := make([]string, len(columns)) 77 columnTypes := make([]types.Type, len(columns)) 78 for i := range columns { 79 columnNames[i] = columns[i].GetName() 80 columnTypes[i] = types.TypeFromYDB(columns[i].GetType()) 81 } 82 rows := make([]query.Row, len(rs.GetRows())) 83 for i, r := range rs.GetRows() { 84 rows[i] = NewRow(columns, r) 85 } 86 87 return &options.FetchScriptResult{ 88 ResultSetIndex: response.GetResultSetIndex(), 89 ResultSet: MaterializedResultSet(int(response.GetResultSetIndex()), columnNames, columnTypes, rows), 90 NextToken: response.GetNextFetchToken(), 91 }, nil 92 }, retry.WithIdempotent(true)) 93 if err != nil { 94 return nil, xerrors.WithStackTrace(err) 95 } 96 97 return r, nil 98 } 99 100 func (c *Client) FetchScriptResults(ctx context.Context, 101 opID string, opts ...options.FetchScriptOption, 102 ) (*options.FetchScriptResult, error) { 103 r, err := retry.RetryWithResult(ctx, func(ctx context.Context) (*options.FetchScriptResult, error) { 104 r, err := fetchScriptResults(ctx, c.client, opID, 105 append(opts, func(request *options.FetchScriptResultsRequest) { 106 request.Trace = c.config.Trace() 107 })..., 108 ) 109 if err != nil { 110 return nil, xerrors.WithStackTrace(err) 111 } 112 113 return r, nil 114 }, retry.WithIdempotent(true)) 115 if err != nil { 116 return nil, xerrors.WithStackTrace(err) 117 } 118 119 return r, nil 120 } 121 122 type executeScriptSettings struct { 123 executeSettings 124 ttl time.Duration 125 operationParams *Ydb_Operations.OperationParams 126 } 127 128 func (s *executeScriptSettings) OperationParams() *Ydb_Operations.OperationParams { 129 return s.operationParams 130 } 131 132 func (s *executeScriptSettings) ResultsTTL() time.Duration { 133 return s.ttl 134 } 135 136 func executeScript(ctx context.Context, 137 client Ydb_Query_V1.QueryServiceClient, request *Ydb_Query.ExecuteScriptRequest, grpcOpts ...grpc.CallOption, 138 ) (*options.ExecuteScriptOperation, error) { 139 op, err := retry.RetryWithResult(ctx, func(ctx context.Context) (*options.ExecuteScriptOperation, error) { 140 response, err := client.ExecuteScript(ctx, request, grpcOpts...) 141 if err != nil { 142 return nil, xerrors.WithStackTrace(err) 143 } 144 145 return &options.ExecuteScriptOperation{ 146 ID: response.GetId(), 147 ConsumedUnits: response.GetCostInfo().GetConsumedUnits(), 148 Metadata: options.ToMetadataExecuteQuery(response.GetMetadata()), 149 }, nil 150 }, retry.WithIdempotent(true)) 151 if err != nil { 152 return op, xerrors.WithStackTrace(err) 153 } 154 155 return op, nil 156 } 157 158 func (c *Client) ExecuteScript( 159 ctx context.Context, q string, ttl time.Duration, opts ...options.Execute, 160 ) ( 161 op *options.ExecuteScriptOperation, err error, 162 ) { 163 a := allocator.New() 164 defer a.Free() 165 166 settings := &executeScriptSettings{ 167 executeSettings: options.ExecuteSettings(opts...), 168 ttl: ttl, 169 operationParams: operation.Params( 170 ctx, 171 c.config.OperationTimeout(), 172 c.config.OperationCancelAfter(), 173 operation.ModeSync, 174 ), 175 } 176 177 request, grpcOpts := executeQueryScriptRequest(a, q, settings) 178 179 op, err = executeScript(ctx, c.client, request, grpcOpts...) 180 if err != nil { 181 return op, xerrors.WithStackTrace(err) 182 } 183 184 return op, nil 185 } 186 187 func (c *Client) Close(ctx context.Context) error { 188 close(c.done) 189 190 if err := c.pool.Close(ctx); err != nil { 191 return xerrors.WithStackTrace(err) 192 } 193 194 return nil 195 } 196 197 func do( 198 ctx context.Context, 199 pool sessionPool, 200 op func(ctx context.Context, s *Session) error, 201 opts ...retry.Option, 202 ) (finalErr error) { 203 err := pool.With(ctx, func(ctx context.Context, s *Session) error { 204 s.SetStatus(session.StatusInUse) 205 206 err := op(ctx, s) 207 if err != nil { 208 s.SetStatus(session.StatusError) 209 210 return xerrors.WithStackTrace(err) 211 } 212 213 s.SetStatus(session.StatusIdle) 214 215 return nil 216 }, opts...) 217 if err != nil { 218 return xerrors.WithStackTrace(err) 219 } 220 221 return nil 222 } 223 224 func (c *Client) Do(ctx context.Context, op query.Operation, opts ...options.DoOption) (finalErr error) { 225 ctx, cancel := xcontext.WithDone(ctx, c.done) 226 defer cancel() 227 228 var ( 229 settings = options.ParseDoOpts(c.config.Trace(), opts...) 230 onDone = trace.QueryOnDo(settings.Trace(), &ctx, 231 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Client).Do"), 232 ) 233 attempts = 0 234 ) 235 defer func() { 236 onDone(attempts, finalErr) 237 }() 238 239 err := do(ctx, c.pool, 240 func(ctx context.Context, s *Session) error { 241 return op(ctx, s) 242 }, 243 append([]retry.Option{ 244 retry.WithTrace(&trace.Retry{ 245 OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { 246 return func(info trace.RetryLoopDoneInfo) { 247 attempts = info.Attempts 248 } 249 }, 250 }), 251 }, settings.RetryOpts()...)..., 252 ) 253 254 return err 255 } 256 257 func doTx( 258 ctx context.Context, 259 pool sessionPool, 260 op query.TxOperation, 261 txSettings tx.Settings, 262 opts ...retry.Option, 263 ) (finalErr error) { 264 err := do(ctx, pool, func(ctx context.Context, s *Session) (opErr error) { 265 tx, err := s.Begin(ctx, txSettings) 266 if err != nil { 267 return xerrors.WithStackTrace(err) 268 } 269 270 defer func() { 271 _ = tx.Rollback(ctx) 272 273 if opErr != nil { 274 s.SetStatus(session.StatusError) 275 } 276 }() 277 278 err = op(ctx, tx) 279 if err != nil { 280 return xerrors.WithStackTrace(err) 281 } 282 283 err = tx.CommitTx(ctx) 284 if err != nil { 285 return xerrors.WithStackTrace(err) 286 } 287 288 return nil 289 }, opts...) 290 if err != nil { 291 return xerrors.WithStackTrace(err) 292 } 293 294 return nil 295 } 296 297 func clientQueryRow( 298 ctx context.Context, pool sessionPool, q string, settings executeSettings, resultOpts ...resultOption, 299 ) (row query.Row, finalErr error) { 300 err := do(ctx, pool, func(ctx context.Context, s *Session) (err error) { 301 row, err = s.queryRow(ctx, q, settings, resultOpts...) 302 if err != nil { 303 return xerrors.WithStackTrace(err) 304 } 305 306 return nil 307 }, settings.RetryOpts()...) 308 if err != nil { 309 return nil, xerrors.WithStackTrace(err) 310 } 311 312 return row, nil 313 } 314 315 // QueryRow is a helper which read only one row from first result set in result 316 func (c *Client) QueryRow(ctx context.Context, q string, opts ...options.Execute) (_ query.Row, finalErr error) { 317 ctx, cancel := xcontext.WithDone(ctx, c.done) 318 defer cancel() 319 320 onDone := trace.QueryOnQueryRow(c.config.Trace(), &ctx, 321 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Client).QueryRow"), 322 q, 323 ) 324 defer func() { 325 onDone(finalErr) 326 }() 327 328 row, err := clientQueryRow(ctx, c.pool, q, options.ExecuteSettings(opts...), withTrace(c.config.Trace())) 329 if err != nil { 330 return nil, xerrors.WithStackTrace(err) 331 } 332 333 return row, nil 334 } 335 336 func clientExec(ctx context.Context, pool sessionPool, q string, opts ...options.Execute) (finalErr error) { 337 settings := options.ExecuteSettings(opts...) 338 err := do(ctx, pool, func(ctx context.Context, s *Session) (err error) { 339 streamResult, err := execute(ctx, s.ID(), s.client, q, settings, withTrace(s.trace)) 340 if err != nil { 341 return xerrors.WithStackTrace(err) 342 } 343 344 err = readAll(ctx, streamResult) 345 if err != nil { 346 return xerrors.WithStackTrace(err) 347 } 348 349 return nil 350 }, settings.RetryOpts()...) 351 if err != nil { 352 return xerrors.WithStackTrace(err) 353 } 354 355 return nil 356 } 357 358 func (c *Client) Exec(ctx context.Context, q string, opts ...options.Execute) (finalErr error) { 359 ctx, cancel := xcontext.WithDone(ctx, c.done) 360 defer cancel() 361 362 onDone := trace.QueryOnExec(c.config.Trace(), &ctx, 363 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Client).Exec"), 364 q, 365 ) 366 defer func() { 367 onDone(finalErr) 368 }() 369 370 err := clientExec(ctx, c.pool, q, opts...) 371 if err != nil { 372 return xerrors.WithStackTrace(err) 373 } 374 375 return nil 376 } 377 378 func clientQuery(ctx context.Context, pool sessionPool, q string, opts ...options.Execute) ( 379 r query.Result, err error, 380 ) { 381 settings := options.ExecuteSettings(opts...) 382 err = do(ctx, pool, func(ctx context.Context, s *Session) (err error) { 383 streamResult, err := execute(ctx, s.ID(), s.client, q, 384 options.ExecuteSettings(opts...), withTrace(s.trace), 385 ) 386 if err != nil { 387 return xerrors.WithStackTrace(err) 388 } 389 390 if err != nil { 391 return xerrors.WithStackTrace(err) 392 } 393 defer func() { 394 _ = streamResult.Close(ctx) 395 }() 396 397 r, err = resultToMaterializedResult(ctx, streamResult) 398 if err != nil { 399 return xerrors.WithStackTrace(err) 400 } 401 402 return nil 403 }, settings.RetryOpts()...) 404 if err != nil { 405 return nil, xerrors.WithStackTrace(err) 406 } 407 408 return r, nil 409 } 410 411 func (c *Client) Query(ctx context.Context, q string, opts ...options.Execute) (r query.Result, err error) { 412 ctx, cancel := xcontext.WithDone(ctx, c.done) 413 defer cancel() 414 415 onDone := trace.QueryOnQuery(c.config.Trace(), &ctx, 416 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Client).Query"), 417 q, 418 ) 419 defer func() { 420 onDone(err) 421 }() 422 423 r, err = clientQuery(ctx, c.pool, q, opts...) 424 if err != nil { 425 return nil, xerrors.WithStackTrace(err) 426 } 427 428 return r, nil 429 } 430 431 func clientQueryResultSet( 432 ctx context.Context, pool sessionPool, q string, settings executeSettings, resultOpts ...resultOption, 433 ) (rs result.ClosableResultSet, finalErr error) { 434 err := do(ctx, pool, func(ctx context.Context, s *Session) error { 435 streamResult, err := execute(ctx, s.ID(), s.client, q, settings, resultOpts...) 436 if err != nil { 437 return xerrors.WithStackTrace(err) 438 } 439 440 rs, err = readMaterializedResultSet(ctx, streamResult) 441 if err != nil { 442 return xerrors.WithStackTrace(err) 443 } 444 445 return nil 446 }, settings.RetryOpts()...) 447 if err != nil { 448 return nil, xerrors.WithStackTrace(err) 449 } 450 451 return rs, nil 452 } 453 454 // QueryResultSet is a helper which read all rows from first result set in result 455 func (c *Client) QueryResultSet( 456 ctx context.Context, q string, opts ...options.Execute, 457 ) (rs result.ClosableResultSet, finalErr error) { 458 ctx, cancel := xcontext.WithDone(ctx, c.done) 459 defer cancel() 460 461 onDone := trace.QueryOnQueryResultSet(c.config.Trace(), &ctx, 462 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Client).QueryResultSet"), 463 q, 464 ) 465 defer func() { 466 onDone(finalErr) 467 }() 468 469 rs, err := clientQueryResultSet(ctx, c.pool, q, options.ExecuteSettings(opts...), withTrace(c.config.Trace())) 470 if err != nil { 471 return nil, xerrors.WithStackTrace(err) 472 } 473 474 return rs, nil 475 } 476 477 func (c *Client) DoTx(ctx context.Context, op query.TxOperation, opts ...options.DoTxOption) (finalErr error) { 478 ctx, cancel := xcontext.WithDone(ctx, c.done) 479 defer cancel() 480 481 var ( 482 settings = options.ParseDoTxOpts(c.config.Trace(), opts...) 483 onDone = trace.QueryOnDoTx(settings.Trace(), &ctx, 484 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Client).DoTx"), 485 ) 486 attempts = 0 487 ) 488 defer func() { 489 onDone(attempts, finalErr) 490 }() 491 492 err := doTx(ctx, c.pool, op, 493 settings.TxSettings(), 494 append( 495 []retry.Option{ 496 retry.WithTrace(&trace.Retry{ 497 OnRetry: func(info trace.RetryLoopStartInfo) func(trace.RetryLoopDoneInfo) { 498 return func(info trace.RetryLoopDoneInfo) { 499 attempts = info.Attempts 500 } 501 }, 502 }), 503 }, 504 settings.RetryOpts()..., 505 )..., 506 ) 507 if err != nil { 508 return xerrors.WithStackTrace(err) 509 } 510 511 return nil 512 } 513 514 func New(ctx context.Context, cc grpc.ClientConnInterface, cfg *config.Config) *Client { 515 onDone := trace.QueryOnNew(cfg.Trace(), &ctx, 516 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.New"), 517 ) 518 defer onDone() 519 520 client := Ydb_Query_V1.NewQueryServiceClient(cc) 521 522 return &Client{ 523 config: cfg, 524 client: client, 525 done: make(chan struct{}), 526 pool: pool.New(ctx, 527 pool.WithLimit[*Session, Session](cfg.PoolLimit()), 528 pool.WithItemUsageLimit[*Session, Session](cfg.PoolSessionUsageLimit()), 529 pool.WithTrace[*Session, Session](poolTrace(cfg.Trace())), 530 pool.WithCreateItemTimeout[*Session, Session](cfg.SessionCreateTimeout()), 531 pool.WithCloseItemTimeout[*Session, Session](cfg.SessionDeleteTimeout()), 532 pool.WithIdleTimeToLive[*Session, Session](cfg.SessionIdleTimeToLive()), 533 pool.WithCreateItemFunc(func(ctx context.Context) (_ *Session, err error) { 534 var ( 535 createCtx context.Context 536 cancelCreate context.CancelFunc 537 ) 538 if d := cfg.SessionCreateTimeout(); d > 0 { 539 createCtx, cancelCreate = xcontext.WithTimeout(ctx, d) 540 } else { 541 createCtx, cancelCreate = xcontext.WithCancel(ctx) 542 } 543 defer cancelCreate() 544 545 s, err := createSession(createCtx, client, 546 session.WithConn(cc), 547 session.WithDeleteTimeout(cfg.SessionDeleteTimeout()), 548 session.WithTrace(cfg.Trace()), 549 ) 550 if err != nil { 551 return nil, xerrors.WithStackTrace(err) 552 } 553 554 s.laztTx = cfg.LazyTx() 555 556 return s, nil 557 }), 558 ), 559 } 560 } 561 562 func poolTrace(t *trace.Query) *pool.Trace { 563 return &pool.Trace{ 564 OnNew: func(ctx *context.Context, call stack.Caller) func(limit int) { 565 onDone := trace.QueryOnPoolNew(t, ctx, call) 566 567 return func(limit int) { 568 onDone(limit) 569 } 570 }, 571 OnClose: func(ctx *context.Context, call stack.Caller) func(err error) { 572 onDone := trace.QueryOnClose(t, ctx, call) 573 574 return func(err error) { 575 onDone(err) 576 } 577 }, 578 OnTry: func(ctx *context.Context, call stack.Caller) func(err error) { 579 onDone := trace.QueryOnPoolTry(t, ctx, call) 580 581 return func(err error) { 582 onDone(err) 583 } 584 }, 585 OnWith: func(ctx *context.Context, call stack.Caller) func(attempts int, err error) { 586 onDone := trace.QueryOnPoolWith(t, ctx, call) 587 588 return func(attempts int, err error) { 589 onDone(attempts, err) 590 } 591 }, 592 OnPut: func(ctx *context.Context, call stack.Caller, item any) func(err error) { 593 onDone := trace.QueryOnPoolPut(t, ctx, call, item.(*Session)) //nolint:forcetypeassert 594 595 return func(err error) { 596 onDone(err) 597 } 598 }, 599 OnGet: func(ctx *context.Context, call stack.Caller) func(item any, attempts int, err error) { 600 onDone := trace.QueryOnPoolGet(t, ctx, call) 601 602 return func(item any, attempts int, err error) { 603 onDone(item.(*Session), attempts, err) //nolint:forcetypeassert 604 } 605 }, 606 OnChange: func(stats pool.Stats) { 607 trace.QueryOnPoolChange(t, stats.Limit, stats.Index, stats.Idle, stats.Wait, stats.CreateInProgress) 608 }, 609 } 610 }