github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/xsql/conn.go (about)

     1  package xsql
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"database/sql/driver"
     7  	"fmt"
     8  	"io"
     9  	"path"
    10  	"strings"
    11  	"sync/atomic"
    12  	"time"
    13  
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/params"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme/helpers"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/tx"
    18  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    19  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    20  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/badconn"
    21  	"github.com/ydb-platform/ydb-go-sdk/v3/retry"
    22  	"github.com/ydb-platform/ydb-go-sdk/v3/scheme"
    23  	"github.com/ydb-platform/ydb-go-sdk/v3/table"
    24  	"github.com/ydb-platform/ydb-go-sdk/v3/table/options"
    25  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    26  )
    27  
    28  type connOption func(*conn)
    29  
    30  func withFakeTxModes(modes ...QueryMode) connOption {
    31  	return func(c *conn) {
    32  		for _, m := range modes {
    33  			c.beginTxFuncs[m] = c.beginTxFake
    34  		}
    35  	}
    36  }
    37  
    38  func withDataOpts(dataOpts ...options.ExecuteDataQueryOption) connOption {
    39  	return func(c *conn) {
    40  		c.dataOpts = dataOpts
    41  	}
    42  }
    43  
    44  func withScanOpts(scanOpts ...options.ExecuteScanQueryOption) connOption {
    45  	return func(c *conn) {
    46  		c.scanOpts = scanOpts
    47  	}
    48  }
    49  
    50  func withDefaultTxControl(defaultTxControl *table.TransactionControl) connOption {
    51  	return func(c *conn) {
    52  		c.defaultTxControl = defaultTxControl
    53  	}
    54  }
    55  
    56  func withDefaultQueryMode(mode QueryMode) connOption {
    57  	return func(c *conn) {
    58  		c.defaultQueryMode = mode
    59  	}
    60  }
    61  
    62  func withTrace(t *trace.DatabaseSQL) connOption {
    63  	return func(c *conn) {
    64  		c.trace = t
    65  	}
    66  }
    67  
    68  type beginTxFunc func(ctx context.Context, txOptions driver.TxOptions) (currentTx, error)
    69  
    70  type conn struct {
    71  	ctx context.Context //nolint:containedctx
    72  
    73  	connector *Connector
    74  	trace     *trace.DatabaseSQL
    75  	session   table.ClosableSession // Immutable and r/o usage.
    76  
    77  	beginTxFuncs map[QueryMode]beginTxFunc
    78  
    79  	closed           atomic.Bool
    80  	lastUsage        atomic.Int64
    81  	defaultQueryMode QueryMode
    82  
    83  	defaultTxControl *table.TransactionControl
    84  	dataOpts         []options.ExecuteDataQueryOption
    85  
    86  	scanOpts []options.ExecuteScanQueryOption
    87  
    88  	currentTx currentTx
    89  }
    90  
    91  func (c *conn) GetDatabaseName() string {
    92  	return c.connector.parent.Name()
    93  }
    94  
    95  func (c *conn) CheckNamedValue(*driver.NamedValue) error {
    96  	// on this stage allows all values
    97  	return nil
    98  }
    99  
   100  func (c *conn) IsValid() bool {
   101  	return c.isReady()
   102  }
   103  
   104  type currentTx interface {
   105  	tx.Identifier
   106  	driver.Tx
   107  	driver.ExecerContext
   108  	driver.QueryerContext
   109  	driver.ConnPrepareContext
   110  }
   111  
   112  type resultNoRows struct{}
   113  
   114  func (resultNoRows) LastInsertId() (int64, error) { return 0, ErrUnsupported }
   115  func (resultNoRows) RowsAffected() (int64, error) { return 0, ErrUnsupported }
   116  
   117  var (
   118  	_ driver.Conn               = &conn{}
   119  	_ driver.ConnPrepareContext = &conn{}
   120  	_ driver.ConnBeginTx        = &conn{}
   121  	_ driver.ExecerContext      = &conn{}
   122  	_ driver.QueryerContext     = &conn{}
   123  	_ driver.Pinger             = &conn{}
   124  	_ driver.Validator          = &conn{}
   125  	_ driver.NamedValueChecker  = &conn{}
   126  
   127  	_ driver.Result = resultNoRows{}
   128  )
   129  
   130  func newConn(ctx context.Context, c *Connector, s table.ClosableSession, opts ...connOption) *conn {
   131  	cc := &conn{
   132  		ctx:       ctx,
   133  		connector: c,
   134  		session:   s,
   135  	}
   136  	cc.beginTxFuncs = map[QueryMode]beginTxFunc{
   137  		DataQueryMode: cc.beginTx,
   138  	}
   139  	for _, opt := range opts {
   140  		if opt != nil {
   141  			opt(cc)
   142  		}
   143  	}
   144  	c.attach(cc)
   145  
   146  	return cc
   147  }
   148  
   149  func (c *conn) isReady() bool {
   150  	return c.session.Status() == table.SessionReady
   151  }
   152  
   153  func (c *conn) PrepareContext(ctx context.Context, query string) (_ driver.Stmt, finalErr error) {
   154  	if c.currentTx != nil {
   155  		return c.currentTx.PrepareContext(ctx, query)
   156  	}
   157  	onDone := trace.DatabaseSQLOnConnPrepare(c.trace, &ctx,
   158  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql.(*conn).PrepareContext"),
   159  		query,
   160  	)
   161  	defer func() {
   162  		onDone(finalErr)
   163  	}()
   164  
   165  	if !c.isReady() {
   166  		return nil, badconn.Map(xerrors.WithStackTrace(errNotReadyConn))
   167  	}
   168  
   169  	return &stmt{
   170  		conn:      c,
   171  		processor: c,
   172  		ctx:       ctx,
   173  		query:     query,
   174  		trace:     c.trace,
   175  	}, nil
   176  }
   177  
   178  func (c *conn) sinceLastUsage() time.Duration {
   179  	return time.Since(time.Unix(c.lastUsage.Load(), 0))
   180  }
   181  
   182  func (c *conn) execContext(
   183  	ctx context.Context,
   184  	query string,
   185  	args []driver.NamedValue,
   186  ) (_ driver.Result, finalErr error) {
   187  	defer func() {
   188  		c.lastUsage.Store(time.Now().Unix())
   189  	}()
   190  
   191  	if !c.isReady() {
   192  		return nil, badconn.Map(xerrors.WithStackTrace(errNotReadyConn))
   193  	}
   194  
   195  	if c.currentTx != nil {
   196  		return c.currentTx.ExecContext(ctx, query, args)
   197  	}
   198  
   199  	m := queryModeFromContext(ctx, c.defaultQueryMode)
   200  	onDone := trace.DatabaseSQLOnConnExec(
   201  		c.trace, &ctx,
   202  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql.(*conn).execContext"),
   203  		query, m.String(), xcontext.IsIdempotent(ctx), c.sinceLastUsage(),
   204  	)
   205  	defer func() {
   206  		onDone(finalErr)
   207  	}()
   208  
   209  	switch m {
   210  	case DataQueryMode:
   211  		return c.executeDataQuery(ctx, query, args)
   212  	case SchemeQueryMode:
   213  		return c.executeSchemeQuery(ctx, query)
   214  	case ScriptingQueryMode:
   215  		return c.executeScriptingQuery(ctx, query, args)
   216  	default:
   217  		return nil, fmt.Errorf("unsupported query mode '%s' for execute query", m)
   218  	}
   219  }
   220  
   221  func (c *conn) executeDataQuery(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
   222  	normalizedQuery, parameters, err := c.normalize(query, args...)
   223  	if err != nil {
   224  		return nil, xerrors.WithStackTrace(err)
   225  	}
   226  
   227  	_, res, err := c.session.Execute(ctx,
   228  		txControl(ctx, c.defaultTxControl),
   229  		normalizedQuery, &parameters, c.dataQueryOptions(ctx)...,
   230  	)
   231  	if err != nil {
   232  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   233  	}
   234  	defer res.Close()
   235  
   236  	if err := res.NextResultSetErr(ctx); err != nil && !xerrors.Is(err, nil, io.EOF) {
   237  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   238  	}
   239  	if err := res.Err(); err != nil {
   240  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   241  	}
   242  
   243  	return resultNoRows{}, nil
   244  }
   245  
   246  func (c *conn) executeSchemeQuery(ctx context.Context, query string) (driver.Result, error) {
   247  	normalizedQuery, _, err := c.normalize(query)
   248  	if err != nil {
   249  		return nil, xerrors.WithStackTrace(err)
   250  	}
   251  
   252  	if err := c.session.ExecuteSchemeQuery(ctx, normalizedQuery); err != nil {
   253  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   254  	}
   255  
   256  	return resultNoRows{}, nil
   257  }
   258  
   259  func (c *conn) executeScriptingQuery(
   260  	ctx context.Context,
   261  	query string,
   262  	args []driver.NamedValue,
   263  ) (driver.Result, error) {
   264  	normalizedQuery, parameters, err := c.normalize(query, args...)
   265  	if err != nil {
   266  		return nil, xerrors.WithStackTrace(err)
   267  	}
   268  
   269  	res, err := c.connector.parent.Scripting().StreamExecute(ctx, normalizedQuery, &parameters)
   270  	if err != nil {
   271  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   272  	}
   273  	defer res.Close()
   274  
   275  	if err := res.NextResultSetErr(ctx); err != nil && !xerrors.Is(err, nil, io.EOF) {
   276  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   277  	}
   278  	if err := res.Err(); err != nil {
   279  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   280  	}
   281  
   282  	return resultNoRows{}, nil
   283  }
   284  
   285  func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (_ driver.Result, _ error) {
   286  	if !c.isReady() {
   287  		return nil, badconn.Map(xerrors.WithStackTrace(errNotReadyConn))
   288  	}
   289  	if c.currentTx != nil {
   290  		return c.currentTx.ExecContext(ctx, query, args)
   291  	}
   292  
   293  	return c.execContext(ctx, query, args)
   294  }
   295  
   296  func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (_ driver.Rows, _ error) {
   297  	if !c.isReady() {
   298  		return nil, badconn.Map(xerrors.WithStackTrace(errNotReadyConn))
   299  	}
   300  	if c.currentTx != nil {
   301  		return c.currentTx.QueryContext(ctx, query, args)
   302  	}
   303  
   304  	return c.queryContext(ctx, query, args)
   305  }
   306  
   307  func (c *conn) queryContext(ctx context.Context, query string, args []driver.NamedValue) (
   308  	_ driver.Rows, finalErr error,
   309  ) {
   310  	defer func() {
   311  		c.lastUsage.Store(time.Now().Unix())
   312  	}()
   313  
   314  	if !c.isReady() {
   315  		return nil, badconn.Map(xerrors.WithStackTrace(errNotReadyConn))
   316  	}
   317  
   318  	if c.currentTx != nil {
   319  		return c.currentTx.QueryContext(ctx, query, args)
   320  	}
   321  
   322  	var (
   323  		queryMode = queryModeFromContext(ctx, c.defaultQueryMode)
   324  		onDone    = trace.DatabaseSQLOnConnQuery(
   325  			c.trace, &ctx,
   326  			stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql.(*conn).queryContext"),
   327  			query, queryMode.String(), xcontext.IsIdempotent(ctx), c.sinceLastUsage(),
   328  		)
   329  	)
   330  	defer func() {
   331  		onDone(finalErr)
   332  	}()
   333  
   334  	normalizedQuery, parameters, err := c.normalize(query, args...)
   335  	if err != nil {
   336  		return nil, xerrors.WithStackTrace(err)
   337  	}
   338  
   339  	switch queryMode {
   340  	case DataQueryMode:
   341  		return c.execDataQuery(ctx, normalizedQuery, parameters)
   342  	case ScanQueryMode:
   343  		return c.execScanQuery(ctx, normalizedQuery, parameters)
   344  	case ExplainQueryMode:
   345  		return c.explainQuery(ctx, normalizedQuery)
   346  	case ScriptingQueryMode:
   347  		return c.execScriptingQuery(ctx, normalizedQuery, parameters)
   348  	default:
   349  		return nil, fmt.Errorf("unsupported query mode '%s' on conn query", queryMode)
   350  	}
   351  }
   352  
   353  func (c *conn) execDataQuery(ctx context.Context, query string, params params.Parameters) (driver.Rows, error) {
   354  	_, res, err := c.session.Execute(ctx,
   355  		txControl(ctx, c.defaultTxControl),
   356  		query, &params, c.dataQueryOptions(ctx)...,
   357  	)
   358  	if err != nil {
   359  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   360  	}
   361  	if err = res.Err(); err != nil {
   362  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   363  	}
   364  
   365  	return &rows{
   366  		conn:   c,
   367  		result: res,
   368  	}, nil
   369  }
   370  
   371  func (c *conn) execScanQuery(ctx context.Context, query string, params params.Parameters) (driver.Rows, error) {
   372  	res, err := c.session.StreamExecuteScanQuery(ctx,
   373  		query, &params, c.scanQueryOptions(ctx)...,
   374  	)
   375  	if err != nil {
   376  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   377  	}
   378  	if err = res.Err(); err != nil {
   379  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   380  	}
   381  
   382  	return &rows{
   383  		conn:   c,
   384  		result: res,
   385  	}, nil
   386  }
   387  
   388  func (c *conn) explainQuery(ctx context.Context, query string) (driver.Rows, error) {
   389  	exp, err := c.session.Explain(ctx, query)
   390  	if err != nil {
   391  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   392  	}
   393  
   394  	return &single{
   395  		values: []sql.NamedArg{
   396  			sql.Named("AST", exp.AST),
   397  			sql.Named("Plan", exp.Plan),
   398  		},
   399  	}, nil
   400  }
   401  
   402  func (c *conn) execScriptingQuery(ctx context.Context, query string, params params.Parameters) (driver.Rows, error) {
   403  	res, err := c.connector.parent.Scripting().StreamExecute(ctx, query, &params)
   404  	if err != nil {
   405  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   406  	}
   407  	if err = res.Err(); err != nil {
   408  		return nil, badconn.Map(xerrors.WithStackTrace(err))
   409  	}
   410  
   411  	return &rows{
   412  		conn:   c,
   413  		result: res,
   414  	}, nil
   415  }
   416  
   417  func (c *conn) Ping(ctx context.Context) (finalErr error) {
   418  	onDone := trace.DatabaseSQLOnConnPing(c.trace, &ctx,
   419  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql.(*conn).Ping"),
   420  	)
   421  	defer func() {
   422  		onDone(finalErr)
   423  	}()
   424  	if !c.isReady() {
   425  		return badconn.Map(xerrors.WithStackTrace(errNotReadyConn))
   426  	}
   427  	if err := c.session.KeepAlive(ctx); err != nil {
   428  		return badconn.Map(xerrors.WithStackTrace(err))
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  func (c *conn) Close() (finalErr error) {
   435  	if c.closed.CompareAndSwap(false, true) {
   436  		c.connector.detach(c)
   437  		var (
   438  			ctx    = c.ctx
   439  			onDone = trace.DatabaseSQLOnConnClose(
   440  				c.trace, &ctx,
   441  				stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql.(*conn).Close"),
   442  			)
   443  		)
   444  		defer func() {
   445  			onDone(finalErr)
   446  		}()
   447  		if c.currentTx != nil {
   448  			_ = c.currentTx.Rollback()
   449  		}
   450  		err := c.session.Close(xcontext.ValueOnly(ctx))
   451  		if err != nil {
   452  			return badconn.Map(xerrors.WithStackTrace(err))
   453  		}
   454  
   455  		return nil
   456  	}
   457  
   458  	return badconn.Map(xerrors.WithStackTrace(errConnClosedEarly))
   459  }
   460  
   461  func (c *conn) Prepare(string) (driver.Stmt, error) {
   462  	return nil, errDeprecated
   463  }
   464  
   465  func (c *conn) Begin() (driver.Tx, error) {
   466  	return nil, errDeprecated
   467  }
   468  
   469  func (c *conn) normalize(q string, args ...driver.NamedValue) (query string, _ params.Parameters, _ error) {
   470  	return c.connector.Bindings.RewriteQuery(q, func() (ii []interface{}) {
   471  		for i := range args {
   472  			ii = append(ii, args[i])
   473  		}
   474  
   475  		return ii
   476  	}()...)
   477  }
   478  
   479  func (c *conn) ID() string {
   480  	return c.session.ID()
   481  }
   482  
   483  func (c *conn) BeginTx(ctx context.Context, txOptions driver.TxOptions) (_ driver.Tx, finalErr error) {
   484  	var tx currentTx
   485  	onDone := trace.DatabaseSQLOnConnBegin(c.trace, &ctx,
   486  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql.(*conn).BeginTx"),
   487  	)
   488  	defer func() {
   489  		onDone(tx, finalErr)
   490  	}()
   491  
   492  	if c.currentTx != nil {
   493  		return nil, xerrors.WithStackTrace(
   494  			xerrors.Retryable(
   495  				&ConnAlreadyHaveTxError{
   496  					currentTx: c.currentTx.ID(),
   497  				},
   498  				xerrors.InvalidObject(),
   499  			),
   500  		)
   501  	}
   502  
   503  	m := queryModeFromContext(ctx, c.defaultQueryMode)
   504  
   505  	beginTx, isKnown := c.beginTxFuncs[m]
   506  	if !isKnown {
   507  		return nil, badconn.Map(
   508  			xerrors.WithStackTrace(
   509  				xerrors.Retryable(
   510  					fmt.Errorf("wrong query mode: %s", m.String()),
   511  					xerrors.InvalidObject(),
   512  					xerrors.WithName("WRONG_QUERY_MODE"),
   513  				),
   514  			),
   515  		)
   516  	}
   517  
   518  	var err error
   519  	tx, err = beginTx(ctx, txOptions)
   520  	if err != nil {
   521  		return nil, xerrors.WithStackTrace(err)
   522  	}
   523  
   524  	return tx, nil
   525  }
   526  
   527  func (c *conn) Version(_ context.Context) (_ string, _ error) {
   528  	const version = "default"
   529  
   530  	return version, nil
   531  }
   532  
   533  func (c *conn) IsTableExists(ctx context.Context, tableName string) (tableExists bool, finalErr error) {
   534  	tableName = c.normalizePath(tableName)
   535  	onDone := trace.DatabaseSQLOnConnIsTableExists(c.trace, &ctx,
   536  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql.(*conn).IsTableExists"),
   537  		tableName,
   538  	)
   539  	defer func() {
   540  		onDone(tableExists, finalErr)
   541  	}()
   542  	tableExists, err := helpers.IsEntryExists(ctx,
   543  		c.connector.parent.Scheme(), tableName,
   544  		scheme.EntryTable, scheme.EntryColumnTable,
   545  	)
   546  	if err != nil {
   547  		return false, xerrors.WithStackTrace(err)
   548  	}
   549  
   550  	return tableExists, nil
   551  }
   552  
   553  func (c *conn) IsColumnExists(ctx context.Context, tableName, columnName string) (columnExists bool, _ error) {
   554  	tableName = c.normalizePath(tableName)
   555  	tableExists, err := helpers.IsEntryExists(ctx,
   556  		c.connector.parent.Scheme(), tableName,
   557  		scheme.EntryTable, scheme.EntryColumnTable,
   558  	)
   559  	if err != nil {
   560  		return false, xerrors.WithStackTrace(err)
   561  	}
   562  	if !tableExists {
   563  		return false, xerrors.WithStackTrace(fmt.Errorf("table '%s' not exist", tableName))
   564  	}
   565  
   566  	err = c.retryIdempotent(ctx, func(ctx context.Context) (err error) {
   567  		desc, err := c.session.DescribeTable(ctx, tableName)
   568  		if err != nil {
   569  			return err
   570  		}
   571  		for i := range desc.Columns {
   572  			if desc.Columns[i].Name == columnName {
   573  				columnExists = true
   574  
   575  				break
   576  			}
   577  		}
   578  
   579  		return nil
   580  	})
   581  	if err != nil {
   582  		return false, xerrors.WithStackTrace(err)
   583  	}
   584  
   585  	return columnExists, nil
   586  }
   587  
   588  func (c *conn) GetColumns(ctx context.Context, tableName string) (columns []string, _ error) {
   589  	tableName = c.normalizePath(tableName)
   590  	tableExists, err := helpers.IsEntryExists(ctx,
   591  		c.connector.parent.Scheme(), tableName,
   592  		scheme.EntryTable, scheme.EntryColumnTable,
   593  	)
   594  	if err != nil {
   595  		return nil, xerrors.WithStackTrace(err)
   596  	}
   597  	if !tableExists {
   598  		return nil, xerrors.WithStackTrace(fmt.Errorf("table '%s' not exist", tableName))
   599  	}
   600  
   601  	err = c.retryIdempotent(ctx, func(ctx context.Context) (err error) {
   602  		desc, err := c.session.DescribeTable(ctx, tableName)
   603  		if err != nil {
   604  			return err
   605  		}
   606  		for i := range desc.Columns {
   607  			columns = append(columns, desc.Columns[i].Name)
   608  		}
   609  
   610  		return nil
   611  	})
   612  	if err != nil {
   613  		return nil, xerrors.WithStackTrace(err)
   614  	}
   615  
   616  	return columns, nil
   617  }
   618  
   619  func (c *conn) GetColumnType(ctx context.Context, tableName, columnName string) (dataType string, _ error) {
   620  	tableName = c.normalizePath(tableName)
   621  	tableExists, err := helpers.IsEntryExists(ctx,
   622  		c.connector.parent.Scheme(), tableName,
   623  		scheme.EntryTable, scheme.EntryColumnTable,
   624  	)
   625  	if err != nil {
   626  		return "", xerrors.WithStackTrace(err)
   627  	}
   628  	if !tableExists {
   629  		return "", xerrors.WithStackTrace(fmt.Errorf("table '%s' not exist", tableName))
   630  	}
   631  
   632  	columnExist, err := c.IsColumnExists(ctx, tableName, columnName)
   633  	if err != nil {
   634  		return "", xerrors.WithStackTrace(err)
   635  	}
   636  	if !columnExist {
   637  		return "", xerrors.WithStackTrace(fmt.Errorf("column '%s' not exist in table '%s'", columnName, tableName))
   638  	}
   639  
   640  	err = c.retryIdempotent(ctx, func(ctx context.Context) (err error) {
   641  		desc, err := c.session.DescribeTable(ctx, tableName)
   642  		if err != nil {
   643  			return err
   644  		}
   645  		for i := range desc.Columns {
   646  			if desc.Columns[i].Name == columnName {
   647  				dataType = desc.Columns[i].Type.Yql()
   648  
   649  				break
   650  			}
   651  		}
   652  
   653  		return nil
   654  	})
   655  	if err != nil {
   656  		return "", xerrors.WithStackTrace(err)
   657  	}
   658  
   659  	return dataType, nil
   660  }
   661  
   662  func (c *conn) GetPrimaryKeys(ctx context.Context, tableName string) (pkCols []string, _ error) {
   663  	tableName = c.normalizePath(tableName)
   664  	tableExists, err := helpers.IsEntryExists(ctx,
   665  		c.connector.parent.Scheme(), tableName,
   666  		scheme.EntryTable, scheme.EntryColumnTable,
   667  	)
   668  	if err != nil {
   669  		return nil, xerrors.WithStackTrace(err)
   670  	}
   671  	if !tableExists {
   672  		return nil, xerrors.WithStackTrace(fmt.Errorf("table '%s' not exist", tableName))
   673  	}
   674  
   675  	err = c.retryIdempotent(ctx, func(ctx context.Context) (err error) {
   676  		desc, err := c.session.DescribeTable(ctx, tableName)
   677  		if err != nil {
   678  			return err
   679  		}
   680  		pkCols = append(pkCols, desc.PrimaryKey...)
   681  
   682  		return nil
   683  	})
   684  	if err != nil {
   685  		return nil, xerrors.WithStackTrace(err)
   686  	}
   687  
   688  	return pkCols, nil
   689  }
   690  
   691  func (c *conn) IsPrimaryKey(ctx context.Context, tableName, columnName string) (ok bool, _ error) {
   692  	tableName = c.normalizePath(tableName)
   693  	tableExists, err := helpers.IsEntryExists(ctx,
   694  		c.connector.parent.Scheme(), tableName,
   695  		scheme.EntryTable, scheme.EntryColumnTable,
   696  	)
   697  	if err != nil {
   698  		return false, xerrors.WithStackTrace(err)
   699  	}
   700  	if !tableExists {
   701  		return false, xerrors.WithStackTrace(fmt.Errorf("table '%s' not exist", tableName))
   702  	}
   703  
   704  	columnExist, err := c.IsColumnExists(ctx, tableName, columnName)
   705  	if err != nil {
   706  		return false, xerrors.WithStackTrace(err)
   707  	}
   708  	if !columnExist {
   709  		return false, xerrors.WithStackTrace(fmt.Errorf("column '%s' not exist in table '%s'", columnName, tableName))
   710  	}
   711  
   712  	pkCols, err := c.GetPrimaryKeys(ctx, tableName)
   713  	if err != nil {
   714  		return false, xerrors.WithStackTrace(err)
   715  	}
   716  	for _, pkCol := range pkCols {
   717  		if pkCol == columnName {
   718  			ok = true
   719  
   720  			break
   721  		}
   722  	}
   723  
   724  	return ok, nil
   725  }
   726  
   727  func (c *conn) normalizePath(folderOrTable string) (absPath string) {
   728  	return c.connector.pathNormalizer.NormalizePath(folderOrTable)
   729  }
   730  
   731  func isSysDir(databaseName, dirAbsPath string) bool {
   732  	for _, sysDir := range [...]string{
   733  		path.Join(databaseName, ".sys"),
   734  		path.Join(databaseName, ".sys_health"),
   735  	} {
   736  		if strings.HasPrefix(dirAbsPath, sysDir) {
   737  			return true
   738  		}
   739  	}
   740  
   741  	return false
   742  }
   743  
   744  func (c *conn) getTables(ctx context.Context, absPath string, recursive, excludeSysDirs bool) (
   745  	tables []string, _ error,
   746  ) {
   747  	if excludeSysDirs && isSysDir(c.connector.parent.Name(), absPath) {
   748  		return nil, nil
   749  	}
   750  
   751  	var d scheme.Directory
   752  	err := c.retryIdempotent(ctx, func(ctx context.Context) (err error) {
   753  		d, err = c.connector.parent.Scheme().ListDirectory(ctx, absPath)
   754  
   755  		return err
   756  	})
   757  	if err != nil {
   758  		return nil, xerrors.WithStackTrace(err)
   759  	}
   760  
   761  	if !d.IsDirectory() && !d.IsDatabase() {
   762  		return nil, xerrors.WithStackTrace(fmt.Errorf("'%s' is not a folder", absPath))
   763  	}
   764  
   765  	for i := range d.Children {
   766  		switch d.Children[i].Type {
   767  		case scheme.EntryTable, scheme.EntryColumnTable:
   768  			tables = append(tables, path.Join(absPath, d.Children[i].Name))
   769  		case scheme.EntryDirectory, scheme.EntryDatabase:
   770  			if recursive {
   771  				childTables, err := c.getTables(ctx, path.Join(absPath, d.Children[i].Name), recursive, excludeSysDirs)
   772  				if err != nil {
   773  					return nil, xerrors.WithStackTrace(err)
   774  				}
   775  				tables = append(tables, childTables...)
   776  			}
   777  		}
   778  	}
   779  
   780  	return tables, nil
   781  }
   782  
   783  func (c *conn) GetTables(ctx context.Context, folder string, recursive, excludeSysDirs bool) (
   784  	tables []string, _ error,
   785  ) {
   786  	absPath := c.normalizePath(folder)
   787  
   788  	var e scheme.Entry
   789  	err := c.retryIdempotent(ctx, func(ctx context.Context) (err error) {
   790  		e, err = c.connector.parent.Scheme().DescribePath(ctx, absPath)
   791  
   792  		return err
   793  	})
   794  	if err != nil {
   795  		return nil, xerrors.WithStackTrace(err)
   796  	}
   797  
   798  	switch e.Type {
   799  	case scheme.EntryTable, scheme.EntryColumnTable:
   800  		return []string{e.Name}, err
   801  	case scheme.EntryDirectory, scheme.EntryDatabase:
   802  		tables, err = c.getTables(ctx, absPath, recursive, excludeSysDirs)
   803  		if err != nil {
   804  			return nil, xerrors.WithStackTrace(err)
   805  		}
   806  
   807  		for i := range tables {
   808  			tables[i] = strings.TrimPrefix(tables[i], absPath+"/")
   809  		}
   810  
   811  		return tables, nil
   812  	default:
   813  		return nil, xerrors.WithStackTrace(
   814  			fmt.Errorf("'%s' is not a table or directory (%s)", folder, e.Type.String()),
   815  		)
   816  	}
   817  }
   818  
   819  func (c *conn) GetIndexes(ctx context.Context, tableName string) (indexes []string, _ error) {
   820  	tableName = c.normalizePath(tableName)
   821  	tableExists, err := helpers.IsEntryExists(ctx,
   822  		c.connector.parent.Scheme(), tableName,
   823  		scheme.EntryTable, scheme.EntryColumnTable,
   824  	)
   825  	if err != nil {
   826  		return nil, xerrors.WithStackTrace(err)
   827  	}
   828  	if !tableExists {
   829  		return nil, xerrors.WithStackTrace(fmt.Errorf("table '%s' not exist", tableName))
   830  	}
   831  
   832  	err = c.retryIdempotent(ctx, func(ctx context.Context) (err error) {
   833  		desc, err := c.session.DescribeTable(ctx, tableName)
   834  		if err != nil {
   835  			return err
   836  		}
   837  		for i := range desc.Indexes {
   838  			indexes = append(indexes, desc.Indexes[i].Name)
   839  		}
   840  
   841  		return nil
   842  	})
   843  	if err != nil {
   844  		return nil, xerrors.WithStackTrace(err)
   845  	}
   846  
   847  	return indexes, nil
   848  }
   849  
   850  func (c *conn) retryIdempotent(ctx context.Context, f func(ctx context.Context) error) error {
   851  	err := retry.Retry(ctx, f,
   852  		retry.WithIdempotent(true),
   853  		retry.WithTrace(c.connector.traceRetry),
   854  		retry.WithBudget(c.connector.retryBudget),
   855  	)
   856  	if err != nil {
   857  		return xerrors.WithStackTrace(err)
   858  	}
   859  
   860  	return nil
   861  }
   862  
   863  func (c *conn) GetIndexColumns(ctx context.Context, tableName, indexName string) (columns []string, _ error) {
   864  	tableName = c.normalizePath(tableName)
   865  	tableExists, err := helpers.IsEntryExists(ctx,
   866  		c.connector.parent.Scheme(), tableName,
   867  		scheme.EntryTable, scheme.EntryColumnTable,
   868  	)
   869  	if err != nil {
   870  		return nil, xerrors.WithStackTrace(err)
   871  	}
   872  	if !tableExists {
   873  		return nil, xerrors.WithStackTrace(fmt.Errorf("table '%s' not exist", tableName))
   874  	}
   875  
   876  	err = c.retryIdempotent(ctx, func(ctx context.Context) (err error) {
   877  		desc, err := c.session.DescribeTable(ctx, tableName)
   878  		if err != nil {
   879  			return err
   880  		}
   881  		for i := range desc.Indexes {
   882  			if desc.Indexes[i].Name == indexName {
   883  				columns = append(columns, desc.Indexes[i].IndexColumns...)
   884  
   885  				return nil
   886  			}
   887  		}
   888  
   889  		return xerrors.WithStackTrace(fmt.Errorf("index '%s' not found in table '%s'", indexName, tableName))
   890  	})
   891  	if err != nil {
   892  		return nil, xerrors.WithStackTrace(err)
   893  	}
   894  
   895  	return columns, nil
   896  }