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, ¶meters, 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, ¶meters) 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, ¶ms, 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, ¶ms, 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, ¶ms) 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 }