github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/tests/integration/query_tx_execute_test.go (about) 1 //go:build integration 2 // +build integration 3 4 package integration 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "os" 11 "testing" 12 13 "github.com/stretchr/testify/require" 14 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" 15 16 "github.com/ydb-platform/ydb-go-sdk/v3" 17 internalQuery "github.com/ydb-platform/ydb-go-sdk/v3/internal/query" 18 baseTx "github.com/ydb-platform/ydb-go-sdk/v3/internal/tx" 19 "github.com/ydb-platform/ydb-go-sdk/v3/internal/version" 20 "github.com/ydb-platform/ydb-go-sdk/v3/query" 21 ) 22 23 func TestQueryTxExecute(t *testing.T) { 24 if version.Lt(os.Getenv("YDB_VERSION"), "24.1") { 25 t.Skip("query service not allowed in YDB version '" + os.Getenv("YDB_VERSION") + "'") 26 } 27 28 scope := newScope(t) 29 30 t.Run("Default", func(t *testing.T) { 31 var ( 32 columnNames []string 33 columnTypes []string 34 ) 35 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 36 res, err := tx.Query(ctx, "SELECT 1 AS col1") 37 if err != nil { 38 return err 39 } 40 rs, err := res.NextResultSet(ctx) 41 if err != nil { 42 return err 43 } 44 columnNames = rs.Columns() 45 for _, t := range rs.ColumnTypes() { 46 columnTypes = append(columnTypes, t.Yql()) 47 } 48 row, err := rs.NextRow(ctx) 49 if err != nil { 50 return err 51 } 52 var col1 int 53 err = row.ScanNamed(query.Named("col1", &col1)) 54 if err != nil { 55 return err 56 } 57 err = tx.Exec(ctx, "SELECT 1") 58 if err != nil { 59 return err 60 } 61 _ = res.Close(ctx) 62 63 return nil 64 }, query.WithIdempotent()) 65 require.NoError(t, err) 66 require.Equal(t, []string{"col1"}, columnNames) 67 require.Equal(t, []string{"Int32"}, columnTypes) 68 }) 69 t.Run("WithLazyTx", func(t *testing.T) { 70 var ( 71 columnNames []string 72 columnTypes []string 73 ) 74 err := scope.Driver(ydb.WithLazyTx(true)).Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 75 if tx.ID() != baseTx.LazyTxID { 76 return errors.New("transaction is not lazy") 77 } 78 res, err := tx.Query(ctx, "SELECT 1 AS col1") 79 if err != nil { 80 return err 81 } 82 if tx.ID() == baseTx.LazyTxID { 83 return errors.New("transaction is lazy yet") 84 } 85 rs, err := res.NextResultSet(ctx) 86 if err != nil { 87 return err 88 } 89 columnNames = rs.Columns() 90 for _, t := range rs.ColumnTypes() { 91 columnTypes = append(columnTypes, t.Yql()) 92 } 93 row, err := rs.NextRow(ctx) 94 if err != nil { 95 return err 96 } 97 var col1 int 98 err = row.ScanNamed(query.Named("col1", &col1)) 99 if err != nil { 100 return err 101 } 102 err = tx.Exec(ctx, "SELECT 1") 103 if err != nil { 104 return err 105 } 106 _ = res.Close(ctx) 107 108 return nil 109 }, query.WithIdempotent()) 110 require.NoError(t, err) 111 require.Equal(t, []string{"col1"}, columnNames) 112 require.Equal(t, []string{"Int32"}, columnTypes) 113 }) 114 t.Run("SerializableReadWrite", func(t *testing.T) { 115 var ( 116 columnNames []string 117 columnTypes []string 118 ) 119 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 120 res, err := tx.Query(ctx, "SELECT 1 AS col1") 121 if err != nil { 122 return err 123 } 124 rs, err := res.NextResultSet(ctx) 125 if err != nil { 126 return err 127 } 128 columnNames = rs.Columns() 129 columnTypes = columnTypes[:0] 130 for _, t := range rs.ColumnTypes() { 131 columnTypes = append(columnTypes, t.Yql()) 132 } 133 row, err := rs.NextRow(ctx) 134 if err != nil { 135 return err 136 } 137 var col1 int 138 err = row.ScanNamed(query.Named("col1", &col1)) 139 if err != nil { 140 return err 141 } 142 return nil 143 }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithSerializableReadWrite()))) 144 require.NoError(t, err) 145 require.Equal(t, []string{"col1"}, columnNames) 146 require.Equal(t, []string{"Int32"}, columnTypes) 147 }) 148 t.Run("SnapshotReadOnly", func(t *testing.T) { 149 var ( 150 columnNames []string 151 columnTypes []string 152 ) 153 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 154 res, err := tx.Query(ctx, "SELECT 1 AS col1") 155 if err != nil { 156 return err 157 } 158 rs, err := res.NextResultSet(ctx) 159 if err != nil { 160 return err 161 } 162 columnNames = rs.Columns() 163 columnTypes = columnTypes[:0] 164 for _, t := range rs.ColumnTypes() { 165 columnTypes = append(columnTypes, t.Yql()) 166 } 167 row, err := rs.NextRow(ctx) 168 if err != nil { 169 return err 170 } 171 var col1 int 172 err = row.ScanNamed(query.Named("col1", &col1)) 173 if err != nil { 174 return err 175 } 176 return nil 177 }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithSnapshotReadOnly()))) 178 require.NoError(t, err) 179 require.Equal(t, []string{"col1"}, columnNames) 180 require.Equal(t, []string{"Int32"}, columnTypes) 181 }) 182 t.Run("OnlineReadOnly", func(t *testing.T) { 183 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 184 res, err := tx.Query(ctx, "SELECT 1 AS col1") 185 if err != nil { 186 return err 187 } 188 rs, err := res.NextResultSet(ctx) 189 if err != nil { 190 return err 191 } 192 row, err := rs.NextRow(ctx) 193 if err != nil { 194 return err 195 } 196 var col1 int 197 err = row.ScanNamed(query.Named("col1", &col1)) 198 if err != nil { 199 return err 200 } 201 return nil 202 }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithOnlineReadOnly()))) 203 require.True(t, ydb.IsOperationError(err, Ydb.StatusIds_BAD_REQUEST)) 204 }) 205 t.Run("StaleReadOnly", func(t *testing.T) { 206 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 207 res, err := tx.Query(ctx, "SELECT 1 AS col1") 208 if err != nil { 209 return err 210 } 211 rs, err := res.NextResultSet(ctx) 212 if err != nil { 213 return err 214 } 215 row, err := rs.NextRow(ctx) 216 if err != nil { 217 return err 218 } 219 var col1 int 220 err = row.ScanNamed(query.Named("col1", &col1)) 221 if err != nil { 222 return err 223 } 224 return nil 225 }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithStaleReadOnly()))) 226 require.True(t, ydb.IsOperationError(err, Ydb.StatusIds_BAD_REQUEST)) 227 }) 228 t.Run("ErrOptionNotForTxExecute", func(t *testing.T) { 229 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 230 err = tx.Exec(ctx, "SELECT 1 AS col1", 231 query.WithTxControl(query.TxControl(query.BeginTx(query.WithOnlineReadOnly()))), 232 ) 233 if err != nil { 234 return err 235 } 236 237 return nil 238 }, query.WithIdempotent()) 239 require.Error(t, err) 240 t.Logf("err: %s", err.Error()) 241 require.ErrorIs(t, err, internalQuery.ErrOptionNotForTxExecute) 242 }) 243 } 244 245 func TestQueryLazyTxExecute(t *testing.T) { 246 if version.Lt(os.Getenv("YDB_VERSION"), "24.1") { 247 t.Skip("query service not allowed in YDB version '" + os.Getenv("YDB_VERSION") + "'") 248 } 249 250 scope := newScope(t) 251 252 var ( 253 columnNames []string 254 columnTypes []string 255 ) 256 t.Run("Default", func(t *testing.T) { 257 err := scope.DriverWithLogs(ydb.WithLazyTx(true)).Query().DoTx( 258 scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 259 if tx.ID() != baseTx.LazyTxID { 260 return errors.New("transaction is not lazy") 261 } 262 res, err := tx.Query(ctx, "SELECT 1 AS col1") 263 if err != nil { 264 return err 265 } 266 if tx.ID() == baseTx.LazyTxID { 267 return errors.New("transaction is lazy yet") 268 } 269 rs, err := res.NextResultSet(ctx) 270 if err != nil { 271 return err 272 } 273 columnNames = rs.Columns() 274 for _, t := range rs.ColumnTypes() { 275 columnTypes = append(columnTypes, t.Yql()) 276 } 277 row, err := rs.NextRow(ctx) 278 if err != nil { 279 return err 280 } 281 var col1 int 282 err = row.ScanNamed(query.Named("col1", &col1)) 283 if err != nil { 284 return err 285 } 286 err = tx.Exec(ctx, "SELECT 1") 287 if err != nil { 288 return err 289 } 290 _ = res.Close(ctx) 291 292 return nil 293 }, query.WithIdempotent(), 294 ) 295 require.NoError(t, err) 296 require.Equal(t, []string{"col1"}, columnNames) 297 require.Equal(t, []string{"Int32"}, columnTypes) 298 }) 299 t.Run("SerializableReadWrite", func(t *testing.T) { 300 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 301 if tx.ID() != baseTx.LazyTxID { 302 return errors.New("transaction is not lazy") 303 } 304 res, err := tx.Query(ctx, "SELECT 1 AS col1") 305 if err != nil { 306 return err 307 } 308 if tx.ID() == baseTx.LazyTxID { 309 return errors.New("transaction is lazy yet") 310 } 311 rs, err := res.NextResultSet(ctx) 312 if err != nil { 313 return err 314 } 315 columnNames = rs.Columns() 316 columnTypes = columnTypes[:0] 317 for _, t := range rs.ColumnTypes() { 318 columnTypes = append(columnTypes, t.Yql()) 319 } 320 row, err := rs.NextRow(ctx) 321 if err != nil { 322 return err 323 } 324 var col1 int 325 err = row.ScanNamed(query.Named("col1", &col1)) 326 if err != nil { 327 return err 328 } 329 return nil 330 }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithSerializableReadWrite()))) 331 require.NoError(t, err) 332 require.Equal(t, []string{"col1"}, columnNames) 333 require.Equal(t, []string{"Int32"}, columnTypes) 334 }) 335 t.Run("SnapshotReadOnly", func(t *testing.T) { 336 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 337 if tx.ID() != baseTx.LazyTxID { 338 return errors.New("transaction is not lazy") 339 } 340 res, err := tx.Query(ctx, "SELECT 1 AS col1") 341 if err != nil { 342 return err 343 } 344 if tx.ID() == baseTx.LazyTxID { 345 return errors.New("transaction is lazy yet") 346 } 347 rs, err := res.NextResultSet(ctx) 348 if err != nil { 349 return err 350 } 351 columnNames = rs.Columns() 352 columnTypes = columnTypes[:0] 353 for _, t := range rs.ColumnTypes() { 354 columnTypes = append(columnTypes, t.Yql()) 355 } 356 row, err := rs.NextRow(ctx) 357 if err != nil { 358 return err 359 } 360 var col1 int 361 err = row.ScanNamed(query.Named("col1", &col1)) 362 if err != nil { 363 return err 364 } 365 return nil 366 }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithSnapshotReadOnly()))) 367 require.NoError(t, err) 368 require.Equal(t, []string{"col1"}, columnNames) 369 require.Equal(t, []string{"Int32"}, columnTypes) 370 }) 371 t.Run("OnlineReadOnly", func(t *testing.T) { 372 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 373 if tx.ID() != baseTx.LazyTxID { 374 return errors.New("transaction is not lazy") 375 } 376 res, err := tx.Query(ctx, "SELECT 1 AS col1") 377 if err != nil { 378 return err 379 } 380 if tx.ID() == baseTx.LazyTxID { 381 return errors.New("transaction is lazy yet") 382 } 383 rs, err := res.NextResultSet(ctx) 384 if err != nil { 385 return err 386 } 387 columnNames = rs.Columns() 388 columnTypes = columnTypes[:0] 389 for _, t := range rs.ColumnTypes() { 390 columnTypes = append(columnTypes, t.Yql()) 391 } 392 row, err := rs.NextRow(ctx) 393 if err != nil { 394 return err 395 } 396 var col1 int 397 err = row.ScanNamed(query.Named("col1", &col1)) 398 if err != nil { 399 return err 400 } 401 return nil 402 }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithOnlineReadOnly()))) 403 require.NoError(t, err) 404 }) 405 t.Run("StaleReadOnly", func(t *testing.T) { 406 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 407 if tx.ID() != baseTx.LazyTxID { 408 return errors.New("transaction is not lazy") 409 } 410 res, err := tx.Query(ctx, "SELECT 1 AS col1") 411 if err != nil { 412 return err 413 } 414 if tx.ID() == baseTx.LazyTxID { 415 return errors.New("transaction is lazy yet") 416 } 417 rs, err := res.NextResultSet(ctx) 418 if err != nil { 419 return err 420 } 421 columnNames = rs.Columns() 422 columnTypes = columnTypes[:0] 423 for _, t := range rs.ColumnTypes() { 424 columnTypes = append(columnTypes, t.Yql()) 425 } 426 row, err := rs.NextRow(ctx) 427 if err != nil { 428 return err 429 } 430 var col1 int 431 err = row.ScanNamed(query.Named("col1", &col1)) 432 if err != nil { 433 return err 434 } 435 return nil 436 }, query.WithIdempotent(), query.WithTxSettings(query.TxSettings(query.WithStaleReadOnly()))) 437 require.NoError(t, err) 438 }) 439 t.Run("ErrOptionNotForTxExecute", func(t *testing.T) { 440 err := scope.DriverWithLogs().Query().DoTx(scope.Ctx, func(ctx context.Context, tx query.TxActor) (err error) { 441 if tx.ID() != baseTx.LazyTxID { 442 return errors.New("transaction is not lazy") 443 } 444 err = tx.Exec(ctx, "SELECT 1 AS col1", 445 query.WithTxControl(query.TxControl(query.BeginTx(query.WithOnlineReadOnly()))), 446 ) 447 if err != nil { 448 return err 449 } 450 451 return nil 452 }, query.WithIdempotent()) 453 require.Error(t, err) 454 t.Logf("err: %s", err.Error()) 455 require.ErrorIs(t, err, internalQuery.ErrOptionNotForTxExecute) 456 }) 457 } 458 459 func TestQueryWithCommitTxFlag(t *testing.T) { 460 if version.Lt(os.Getenv("YDB_VERSION"), "24.1") { 461 t.Skip("query service not allowed in YDB version '" + os.Getenv("YDB_VERSION") + "'") 462 } 463 464 scope := newScope(t) 465 var count uint64 466 err := scope.DriverWithLogs().Query().Do(scope.Ctx, func(ctx context.Context, s query.Session) error { 467 tableName := scope.TablePath() 468 tx, err := s.Begin(ctx, query.TxSettings(query.WithDefaultTxMode())) 469 if err != nil { 470 return fmt.Errorf("failed start transaction: %w", err) 471 } 472 q := fmt.Sprintf("UPSERT INTO `%v` (id, val) VALUES(1, \"2\")", tableName) 473 err = tx.Exec(ctx, q, query.WithCommit()) 474 if err != nil { 475 return fmt.Errorf("failed execute insert: %w", err) 476 } 477 478 // read row within other (implicit) transaction 479 q2 := fmt.Sprintf("SELECT COUNT(*) FROM `%v`", tableName) 480 r, err := s.Query(ctx, q2) 481 if err != nil { 482 return fmt.Errorf("failed query: %w", err) 483 } 484 485 rs, err := r.NextResultSet(ctx) 486 if err != nil { 487 return fmt.Errorf("failed iterate to next result set: %w", err) 488 } 489 490 row, err := rs.NextRow(ctx) 491 if err != nil { 492 return fmt.Errorf("failed iterate to next row: %w", err) 493 } 494 495 if err = row.Scan(&count); err != nil { 496 return fmt.Errorf("failed scan row: %w", err) 497 } 498 return nil 499 }) 500 require.NoError(t, err) 501 require.Equal(t, uint64(1), count) 502 }