github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/query/transaction.go (about) 1 package query 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1" 8 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" 9 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query" 10 11 "github.com/ydb-platform/ydb-go-sdk/v3/internal/allocator" 12 "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/options" 13 "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/result" 14 "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/session" 15 queryTx "github.com/ydb-platform/ydb-go-sdk/v3/internal/query/tx" 16 "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" 17 baseTx "github.com/ydb-platform/ydb-go-sdk/v3/internal/tx" 18 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 19 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" 20 "github.com/ydb-platform/ydb-go-sdk/v3/query" 21 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 22 ) 23 24 var ( 25 _ query.Transaction = (*Transaction)(nil) 26 _ baseTx.Transaction = (*Transaction)(nil) 27 ) 28 29 type ( 30 Transaction struct { 31 baseTx.LazyID 32 33 s *Session 34 txSettings query.TransactionSettings 35 36 completed bool 37 38 onBeforeCommit xsync.Set[*baseTx.OnTransactionBeforeCommit] 39 onCompleted xsync.Set[*baseTx.OnTransactionCompletedFunc] 40 } 41 ) 42 43 func begin( 44 ctx context.Context, 45 client Ydb_Query_V1.QueryServiceClient, 46 sessionID string, 47 txSettings query.TransactionSettings, 48 ) (txID string, _ error) { 49 a := allocator.New() 50 defer a.Free() 51 response, err := client.BeginTransaction(ctx, 52 &Ydb_Query.BeginTransactionRequest{ 53 SessionId: sessionID, 54 TxSettings: txSettings.ToYDB(a), 55 }, 56 ) 57 if err != nil { 58 return "", xerrors.WithStackTrace(err) 59 } 60 61 return response.GetTxMeta().GetId(), nil 62 } 63 64 func (tx *Transaction) UnLazy(ctx context.Context) error { 65 if tx.ID() != baseTx.LazyTxID { 66 return nil 67 } 68 69 txID, err := begin(ctx, tx.s.client, tx.s.ID(), tx.txSettings) 70 if err != nil { 71 return xerrors.WithStackTrace(err) 72 } 73 74 tx.SetTxID(txID) 75 76 return nil 77 } 78 79 func (tx *Transaction) QueryResultSet( 80 ctx context.Context, q string, opts ...options.Execute, 81 ) (rs result.ClosableResultSet, finalErr error) { 82 onDone := trace.QueryOnTxQueryResultSet(tx.s.trace, &ctx, 83 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Transaction).QueryResultSet"), tx, q) 84 defer func() { 85 onDone(finalErr) 86 }() 87 88 if tx.completed { 89 return nil, xerrors.WithStackTrace(errExecuteOnCompletedTx) 90 } 91 92 settings, err := tx.executeSettings(opts...) 93 if err != nil { 94 return nil, xerrors.WithStackTrace(err) 95 } 96 97 resultOpts := []resultOption{ 98 withTrace(tx.s.trace), 99 onTxMeta(func(txMeta *Ydb_Query.TransactionMeta) { 100 tx.SetTxID(txMeta.GetId()) 101 }), 102 } 103 if settings.TxControl().Commit { 104 err = tx.waitOnBeforeCommit(ctx) 105 if err != nil { 106 return nil, err 107 } 108 109 // notification about complete transaction must be sended for any error or for successfully read all result if 110 // it was execution with commit flag 111 resultOpts = append(resultOpts, 112 onNextPartErr(func(err error) { 113 tx.notifyOnCompleted(xerrors.HideEOF(err)) 114 }), 115 ) 116 } 117 r, err := execute(ctx, tx.s.ID(), tx.s.client, q, settings, resultOpts...) 118 if err != nil { 119 return nil, xerrors.WithStackTrace(err) 120 } 121 122 rs, err = readResultSet(ctx, r) 123 if err != nil { 124 return nil, xerrors.WithStackTrace(err) 125 } 126 127 return rs, nil 128 } 129 130 func (tx *Transaction) QueryRow( 131 ctx context.Context, q string, opts ...options.Execute, 132 ) (row query.Row, finalErr error) { 133 onDone := trace.QueryOnTxQueryRow(tx.s.trace, &ctx, 134 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Transaction).QueryRow"), tx, q) 135 defer func() { 136 onDone(finalErr) 137 }() 138 139 settings := options.ExecuteSettings( 140 append( 141 []options.Execute{options.WithTxControl(tx.txControl())}, 142 opts..., 143 )..., 144 ) 145 146 resultOpts := []resultOption{ 147 withTrace(tx.s.trace), 148 onTxMeta(func(txMeta *Ydb_Query.TransactionMeta) { 149 tx.SetTxID(txMeta.GetId()) 150 }), 151 } 152 if settings.TxControl().Commit { 153 err := tx.waitOnBeforeCommit(ctx) 154 if err != nil { 155 return nil, err 156 } 157 158 // notification about complete transaction must be sended for any error or for successfully read all result if 159 // it was execution with commit flag 160 resultOpts = append(resultOpts, 161 onNextPartErr(func(err error) { 162 tx.notifyOnCompleted(xerrors.HideEOF(err)) 163 }), 164 ) 165 } 166 r, err := execute(ctx, tx.s.ID(), tx.s.client, q, settings, resultOpts...) 167 if err != nil { 168 return nil, xerrors.WithStackTrace(err) 169 } 170 171 row, err = readRow(ctx, r) 172 if err != nil { 173 return nil, xerrors.WithStackTrace(err) 174 } 175 176 return row, nil 177 } 178 179 func (tx *Transaction) SessionID() string { 180 return tx.s.ID() 181 } 182 183 func (tx *Transaction) txControl() *queryTx.Control { 184 if tx.ID() != baseTx.LazyTxID { 185 return queryTx.NewControl(queryTx.WithTxID(tx.ID())) 186 } 187 188 return queryTx.NewControl( 189 queryTx.BeginTx(tx.txSettings...), 190 ) 191 } 192 193 func (tx *Transaction) Exec(ctx context.Context, q string, opts ...options.Execute) ( 194 finalErr error, 195 ) { 196 onDone := trace.QueryOnTxExec(tx.s.trace, &ctx, 197 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Transaction).Exec"), tx.s, tx, q) 198 defer func() { 199 onDone(finalErr) 200 }() 201 202 if tx.completed { 203 return xerrors.WithStackTrace(errExecuteOnCompletedTx) 204 } 205 206 settings, err := tx.executeSettings(opts...) 207 if err != nil { 208 return xerrors.WithStackTrace(err) 209 } 210 211 resultOpts := []resultOption{ 212 withTrace(tx.s.trace), 213 onTxMeta(func(txMeta *Ydb_Query.TransactionMeta) { 214 tx.SetTxID(txMeta.GetId()) 215 }), 216 } 217 if settings.TxControl().Commit { 218 err = tx.waitOnBeforeCommit(ctx) 219 if err != nil { 220 return err 221 } 222 223 // notification about complete transaction must be sended for any error or for successfully read all result if 224 // it was execution with commit flag 225 resultOpts = append(resultOpts, 226 onNextPartErr(func(err error) { 227 tx.notifyOnCompleted(xerrors.HideEOF(err)) 228 }), 229 ) 230 } 231 232 r, err := execute(ctx, tx.s.ID(), tx.s.client, q, settings, resultOpts...) 233 if err != nil { 234 return xerrors.WithStackTrace(err) 235 } 236 237 err = readAll(ctx, r) 238 if err != nil { 239 return xerrors.WithStackTrace(err) 240 } 241 242 return nil 243 } 244 245 func (tx *Transaction) executeSettings(opts ...options.Execute) (_ executeSettings, finalErr error) { 246 for _, opt := range opts { 247 if opt == nil { 248 return nil, xerrors.WithStackTrace(errNilOption) 249 } 250 if _, has := opt.(options.ExecuteNoTx); has { 251 return nil, xerrors.WithStackTrace( 252 fmt.Errorf("%T: %w", opt, ErrOptionNotForTxExecute), 253 ) 254 } 255 } 256 257 return options.ExecuteSettings(append([]options.Execute{ 258 options.WithTxControl(tx.txControl()), 259 }, opts...)...), nil 260 } 261 262 func (tx *Transaction) Query(ctx context.Context, q string, opts ...options.Execute) ( 263 _ query.Result, finalErr error, 264 ) { 265 onDone := trace.QueryOnTxQuery(tx.s.trace, &ctx, 266 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Transaction).Query"), tx.s, tx, q) 267 defer func() { 268 onDone(finalErr) 269 }() 270 271 if tx.completed { 272 return nil, xerrors.WithStackTrace(errExecuteOnCompletedTx) 273 } 274 275 settings, err := tx.executeSettings(opts...) 276 if err != nil { 277 return nil, xerrors.WithStackTrace(err) 278 } 279 280 resultOpts := []resultOption{ 281 withTrace(tx.s.trace), 282 onTxMeta(func(txMeta *Ydb_Query.TransactionMeta) { 283 tx.SetTxID(txMeta.GetId()) 284 }), 285 } 286 if settings.TxControl().Commit { 287 err = tx.waitOnBeforeCommit(ctx) 288 if err != nil { 289 return nil, err 290 } 291 292 // notification about complete transaction must be sended for any error or for successfully read all result if 293 // it was execution with commit flag 294 resultOpts = append(resultOpts, 295 onNextPartErr(func(err error) { 296 tx.notifyOnCompleted(xerrors.HideEOF(err)) 297 }), 298 ) 299 } 300 r, err := execute(ctx, tx.s.ID(), tx.s.client, q, settings, resultOpts...) 301 if err != nil { 302 return nil, xerrors.WithStackTrace(err) 303 } 304 305 return r, nil 306 } 307 308 func commitTx(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID, txID string) error { 309 _, err := client.CommitTransaction(ctx, &Ydb_Query.CommitTransactionRequest{ 310 SessionId: sessionID, 311 TxId: txID, 312 }) 313 if err != nil { 314 return xerrors.WithStackTrace(err) 315 } 316 317 return nil 318 } 319 320 func (tx *Transaction) CommitTx(ctx context.Context) (finalErr error) { 321 if tx.ID() == baseTx.LazyTxID { 322 return nil 323 } 324 325 if tx.completed { 326 return nil 327 } 328 329 defer func() { 330 tx.notifyOnCompleted(finalErr) 331 tx.completed = true 332 }() 333 334 err := tx.waitOnBeforeCommit(ctx) 335 if err != nil { 336 return err 337 } 338 339 err = commitTx(ctx, tx.s.client, tx.s.ID(), tx.ID()) 340 if err != nil { 341 if xerrors.IsOperationError(err, Ydb.StatusIds_BAD_SESSION) { 342 tx.s.SetStatus(session.StatusClosed) 343 } 344 345 return xerrors.WithStackTrace(err) 346 } 347 348 return nil 349 } 350 351 func rollback(ctx context.Context, client Ydb_Query_V1.QueryServiceClient, sessionID, txID string) error { 352 _, err := client.RollbackTransaction(ctx, &Ydb_Query.RollbackTransactionRequest{ 353 SessionId: sessionID, 354 TxId: txID, 355 }) 356 if err != nil { 357 return xerrors.WithStackTrace(err) 358 } 359 360 return nil 361 } 362 363 func (tx *Transaction) Rollback(ctx context.Context) (finalErr error) { 364 if tx.ID() == baseTx.LazyTxID { 365 // https://github.com/ydb-platform/ydb-go-sdk/issues/1456 366 return tx.s.Close(ctx) 367 } 368 369 if tx.completed { 370 return nil 371 } 372 373 tx.completed = true 374 375 tx.notifyOnCompleted(ErrTransactionRollingBack) 376 377 err := rollback(ctx, tx.s.client, tx.s.ID(), tx.ID()) 378 if err != nil { 379 if xerrors.IsOperationError(err, Ydb.StatusIds_BAD_SESSION) { 380 tx.s.SetStatus(session.StatusClosed) 381 } 382 383 return xerrors.WithStackTrace(err) 384 } 385 386 return nil 387 } 388 389 func (tx *Transaction) OnBeforeCommit(f baseTx.OnTransactionBeforeCommit) { 390 tx.onBeforeCommit.Add(&f) 391 } 392 393 func (tx *Transaction) OnCompleted(f baseTx.OnTransactionCompletedFunc) { 394 tx.onCompleted.Add(&f) 395 } 396 397 func (tx *Transaction) waitOnBeforeCommit(ctx context.Context) (resErr error) { 398 tx.onBeforeCommit.Range(func(f *baseTx.OnTransactionBeforeCommit) bool { 399 resErr = (*f)(ctx) 400 401 return resErr == nil 402 }) 403 404 return resErr 405 } 406 407 func (tx *Transaction) notifyOnCompleted(err error) { 408 tx.completed = true 409 410 tx.onCompleted.Range(func(f *baseTx.OnTransactionCompletedFunc) bool { 411 (*f)(err) 412 413 return tx.onCompleted.Remove(f) 414 }) 415 }