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  }