github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/query/client.go (about)

     1  package query
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"sync/atomic"
     7  
     8  	"github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1"
     9  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb"
    10  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query"
    11  	"google.golang.org/grpc"
    12  
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/pool"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/query/config"
    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/query"
    18  	"github.com/ydb-platform/ydb-go-sdk/v3/retry"
    19  )
    20  
    21  //go:generate mockgen -destination grpc_client_mock_test.go -package query -write_package_comment=false github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1 QueryServiceClient,QueryService_AttachSessionClient,QueryService_ExecuteQueryClient
    22  
    23  type balancer interface {
    24  	grpc.ClientConnInterface
    25  }
    26  
    27  var _ query.Client = (*Client)(nil)
    28  
    29  type Client struct {
    30  	grpcClient Ydb_Query_V1.QueryServiceClient
    31  	pool       *pool.Pool[Session]
    32  }
    33  
    34  func (c Client) Close(ctx context.Context) error {
    35  	err := c.pool.Close(ctx)
    36  	if err != nil {
    37  		return xerrors.WithStackTrace(err)
    38  	}
    39  
    40  	return nil
    41  }
    42  
    43  func do(ctx context.Context, pool *pool.Pool[Session], op query.Operation, opts *query.DoOptions) error {
    44  	return retry.Retry(ctx, func(ctx context.Context) error {
    45  		err := pool.With(ctx, func(ctx context.Context, s *Session) error {
    46  			err := op(ctx, s)
    47  			if err != nil {
    48  				return xerrors.WithStackTrace(err)
    49  			}
    50  
    51  			return nil
    52  		})
    53  		if err != nil {
    54  			return xerrors.WithStackTrace(err)
    55  		}
    56  
    57  		return nil
    58  	}, opts.RetryOptions...)
    59  }
    60  
    61  func (c Client) Do(ctx context.Context, op query.Operation, opts ...query.DoOption) error {
    62  	doOptions := query.NewDoOptions(opts...)
    63  	if doOptions.Label != "" {
    64  		doOptions.RetryOptions = append(doOptions.RetryOptions, retry.WithLabel(doOptions.Label))
    65  	}
    66  	if doOptions.Idempotent {
    67  		doOptions.RetryOptions = append(doOptions.RetryOptions, retry.WithIdempotent(doOptions.Idempotent))
    68  	}
    69  
    70  	return do(ctx, c.pool, op, &doOptions)
    71  }
    72  
    73  func doTx(ctx context.Context, pool *pool.Pool[Session], op query.TxOperation, opts *query.DoTxOptions) error {
    74  	return do(ctx, pool, func(ctx context.Context, s query.Session) error {
    75  		tx, err := s.Begin(ctx, opts.TxSettings)
    76  		if err != nil {
    77  			return xerrors.WithStackTrace(err)
    78  		}
    79  		err = op(ctx, tx)
    80  		if err != nil {
    81  			errRollback := tx.Rollback(ctx)
    82  			if errRollback != nil {
    83  				return xerrors.WithStackTrace(xerrors.Join(err, errRollback))
    84  			}
    85  
    86  			return xerrors.WithStackTrace(err)
    87  		}
    88  		err = tx.CommitTx(ctx)
    89  		if err != nil {
    90  			errRollback := tx.Rollback(ctx)
    91  			if errRollback != nil {
    92  				return xerrors.WithStackTrace(xerrors.Join(err, errRollback))
    93  			}
    94  
    95  			return xerrors.WithStackTrace(err)
    96  		}
    97  
    98  		return nil
    99  	}, &opts.DoOptions)
   100  }
   101  
   102  func (c Client) DoTx(ctx context.Context, op query.TxOperation, opts ...query.DoTxOption) error {
   103  	doTxOptions := query.NewDoTxOptions(opts...)
   104  	if doTxOptions.Label != "" {
   105  		doTxOptions.RetryOptions = append(doTxOptions.RetryOptions, retry.WithLabel(doTxOptions.Label))
   106  	}
   107  	if doTxOptions.Idempotent {
   108  		doTxOptions.RetryOptions = append(doTxOptions.RetryOptions, retry.WithIdempotent(doTxOptions.Idempotent))
   109  	}
   110  
   111  	return doTx(ctx, c.pool, op, &doTxOptions)
   112  }
   113  
   114  func deleteSession(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID string) error {
   115  	response, err := client.DeleteSession(ctx,
   116  		&Ydb_Query.DeleteSessionRequest{
   117  			SessionId: sessionID,
   118  		},
   119  	)
   120  	if err != nil {
   121  		return xerrors.WithStackTrace(xerrors.Transport(err))
   122  	}
   123  	if response.GetStatus() != Ydb.StatusIds_SUCCESS {
   124  		return xerrors.WithStackTrace(xerrors.FromOperation(response))
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  type createSessionConfig struct {
   131  	onAttach func(s *Session)
   132  	onClose  func(s *Session)
   133  }
   134  
   135  func createSession(
   136  	ctx context.Context, client Ydb_Query_V1.QueryServiceClient, cfg createSessionConfig,
   137  ) (_ *Session, finalErr error) {
   138  	s, err := client.CreateSession(ctx, &Ydb_Query.CreateSessionRequest{})
   139  	if err != nil {
   140  		return nil, xerrors.WithStackTrace(
   141  			xerrors.Transport(err),
   142  		)
   143  	}
   144  
   145  	if s.GetStatus() != Ydb.StatusIds_SUCCESS {
   146  		return nil, xerrors.WithStackTrace(
   147  			xerrors.FromOperation(s),
   148  		)
   149  	}
   150  
   151  	defer func() {
   152  		if finalErr != nil {
   153  			_ = deleteSession(ctx, client, s.GetSessionId())
   154  		}
   155  	}()
   156  
   157  	attachCtx, cancelAttach := xcontext.WithCancel(context.Background())
   158  	defer func() {
   159  		if finalErr != nil {
   160  			cancelAttach()
   161  		}
   162  	}()
   163  
   164  	attach, err := client.AttachSession(attachCtx, &Ydb_Query.AttachSessionRequest{
   165  		SessionId: s.GetSessionId(),
   166  	})
   167  	if err != nil {
   168  		return nil, xerrors.WithStackTrace(
   169  			xerrors.Transport(err),
   170  		)
   171  	}
   172  
   173  	defer func() {
   174  		if finalErr != nil {
   175  			_ = attach.CloseSend()
   176  		}
   177  	}()
   178  
   179  	state, err := attach.Recv()
   180  	if err != nil {
   181  		return nil, xerrors.WithStackTrace(xerrors.Transport(err))
   182  	}
   183  
   184  	if state.GetStatus() != Ydb.StatusIds_SUCCESS {
   185  		return nil, xerrors.WithStackTrace(xerrors.FromOperation(state))
   186  	}
   187  
   188  	session := &Session{
   189  		id:          s.GetSessionId(),
   190  		nodeID:      s.GetNodeId(),
   191  		queryClient: client,
   192  		status:      query.SessionStatusReady,
   193  	}
   194  
   195  	if cfg.onAttach != nil {
   196  		cfg.onAttach(session)
   197  	}
   198  
   199  	session.close = sync.OnceFunc(func() {
   200  		if cfg.onClose != nil {
   201  			cfg.onClose(session)
   202  		}
   203  
   204  		_ = attach.CloseSend()
   205  
   206  		cancelAttach()
   207  
   208  		atomic.StoreUint32(
   209  			(*uint32)(&session.status),
   210  			uint32(query.SessionStatusClosed),
   211  		)
   212  	})
   213  
   214  	go func() {
   215  		defer session.close()
   216  		for {
   217  			switch session.Status() {
   218  			case query.SessionStatusReady, query.SessionStatusInUse:
   219  				sessionState, recvErr := attach.Recv()
   220  				if recvErr != nil || sessionState.GetStatus() != Ydb.StatusIds_SUCCESS {
   221  					return
   222  				}
   223  			default:
   224  				return
   225  			}
   226  		}
   227  	}()
   228  
   229  	return session, nil
   230  }
   231  
   232  func New(ctx context.Context, balancer balancer, config *config.Config) (*Client, error) {
   233  	client := &Client{
   234  		grpcClient: Ydb_Query_V1.NewQueryServiceClient(balancer),
   235  	}
   236  
   237  	client.pool = pool.New(
   238  		config.PoolMaxSize(),
   239  		func(ctx context.Context, onClose func(s *Session)) (*Session, error) {
   240  			var cancel context.CancelFunc
   241  			if d := config.CreateSessionTimeout(); d > 0 {
   242  				ctx, cancel = xcontext.WithTimeout(ctx, d)
   243  			} else {
   244  				ctx, cancel = xcontext.WithCancel(ctx)
   245  			}
   246  			defer cancel()
   247  
   248  			s, err := createSession(ctx, client.grpcClient, createSessionConfig{
   249  				onClose: onClose,
   250  			})
   251  			if err != nil {
   252  				return nil, xerrors.WithStackTrace(err)
   253  			}
   254  
   255  			return s, nil
   256  		},
   257  		func(ctx context.Context, s *Session) error {
   258  			var cancel context.CancelFunc
   259  			if d := config.CreateSessionTimeout(); d > 0 {
   260  				ctx, cancel = xcontext.WithTimeout(ctx, d)
   261  			} else {
   262  				ctx, cancel = xcontext.WithCancel(ctx)
   263  			}
   264  			defer cancel()
   265  
   266  			err := deleteSession(ctx, client.grpcClient, s.id)
   267  			if err != nil {
   268  				return xerrors.WithStackTrace(err)
   269  			}
   270  
   271  			return nil
   272  		},
   273  		xerrors.MustDeleteSession,
   274  	)
   275  
   276  	return client, ctx.Err()
   277  }