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  }