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 }