diff --git a/go/cmd/dolt/commands/backup.go b/go/cmd/dolt/commands/backup.go index 7d09017332..849ecf28ee 100644 --- a/go/cmd/dolt/commands/backup.go +++ b/go/cmd/dolt/commands/backup.go @@ -16,17 +16,15 @@ package commands import ( "context" - - "github.com/gocraft/dbr/v2" - "github.com/gocraft/dbr/v2/dialect" + "fmt" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/errhand" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/libraries/doltcore/env" "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dprocedures" - "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtablefunctions" "github.com/dolthub/dolt/go/libraries/utils/argparser" eventsapi "github.com/dolthub/eventsapi_schema/dolt/services/eventsapi/v1alpha1" ) @@ -108,8 +106,8 @@ func (cmd BackupCmd) EventType() eventsapi.ClientEventType { return eventsapi.ClientEventType_REMOTE } -// Exec executes the `dolt backup` command with the provided subcommand. If no subcommand is provided, the -// `dolt_backups()` table function will be printed. +// Exec executes the `dolt backup` command with the provided subcommand. If no subcommand is provided, the dolt_backups +// table will be printed. func (cmd BackupCmd) Exec(ctx context.Context, commandStr string, args []string, _ *env.DoltEnv, cliCtx cli.CliContext) int { argParser := cmd.ArgParser() help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, backupDocs, argParser)) @@ -121,7 +119,7 @@ func (cmd BackupCmd) Exec(ctx context.Context, commandStr string, args []string, } if apr.NArg() == 0 { - verboseErr := printDoltBackupsTableFunc(&queryEngine, apr) + verboseErr := printDoltBackupsTable(&queryEngine, apr) return HandleVErrAndExitCode(verboseErr, usage) } @@ -164,25 +162,20 @@ func callDoltBackupProc(queryEngine *cli.QueryEngineResult, params []string) err return nil } -// printDoltBackupsTableFunc queries the dolt_backups() table function and prints the results. If the verbose flag is -// set, it prints name, url, and params columns. Otherwise, it prints only the name column. -func printDoltBackupsTableFunc(queryEngine *cli.QueryEngineResult, apr *argparser.ArgParseResults) errhand.VerboseError { - params := []interface{}{apr.Option(cli.VerboseFlag)} - query, err := dbr.InterpolateForDialect("SELECT * FROM dolt_backups(?)", params, dialect.MySQL) - if err != nil { - return errhand.BuildDError("failed to interpolate SELECT query for %s()", dtablefunctions.BackupsTableFunctionName).AddCause(err).Build() - } - +// printDoltBackupsTable queries the dolt_backups table and prints the results. If the verbose flag is set, it prints +// name, url, and params columns. Otherwise, it prints only the name column. +func printDoltBackupsTable(queryEngine *cli.QueryEngineResult, apr *argparser.ArgParseResults) errhand.VerboseError { + query := fmt.Sprintf("SELECT * FROM `%s`", doltdb.BackupsTableName) schema, rowItr, _, err := queryEngine.Queryist.Query(queryEngine.Context, query) if err != nil { - return errhand.BuildDError("failed to execute query for %s()", dtablefunctions.BackupsTableFunctionName).AddCause(err).Build() + return errhand.BuildDError("failed to execute query for %s", doltdb.BackupsTableName).AddCause(err).Build() } rows, err := sql.RowIterToRows(queryEngine.Context, rowItr) if err != nil { - return errhand.BuildDError("failed to retrieve slice for %s()", dtablefunctions.BackupsTableFunctionName).AddCause(err).Build() + return errhand.BuildDError("failed to retrieve slice for %s", doltdb.BackupsTableName).AddCause(err).Build() } - const colExpectedStrFmt = "col %s: expected string, got %v" + const colExpectedStrFmt = "column '%s': expected string, got %v" for _, row := range rows { name, ok := row[0].(string) if !ok { diff --git a/go/libraries/doltcore/sqle/dtablefunctions/dolt_backups.go b/go/libraries/doltcore/sqle/dtablefunctions/dolt_backups.go deleted file mode 100644 index 9f6e18c905..0000000000 --- a/go/libraries/doltcore/sqle/dtablefunctions/dolt_backups.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2025 Dolthub, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package dtablefunctions - -import ( - "fmt" - "io" - "sort" - "strings" - - "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/expression" - "github.com/dolthub/go-mysql-server/sql/types" - - "github.com/dolthub/dolt/go/cmd/dolt/cli" - "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" - "github.com/dolthub/dolt/go/libraries/doltcore/env" - "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" - "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtables" -) - -const BackupsTableFunctionName = doltdb.BackupsTableName - -type BackupsTableFunction struct { - ctx *sql.Context - database sql.Database - argumentExprs []sql.Expression -} - -var _ sql.TableFunction = (*BackupsTableFunction)(nil) -var _ sql.ExecSourceRel = (*BackupsTableFunction)(nil) - -func (btf *BackupsTableFunction) NewInstance(ctx *sql.Context, database sql.Database, expressions []sql.Expression) (sql.Node, error) { - newInstance := &BackupsTableFunction{ - ctx: ctx, - database: database, - } - - node, err := newInstance.WithExpressions(expressions...) - if err != nil { - return nil, err - } - - return node, nil -} - -func (btf *BackupsTableFunction) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) { - args, err := getDoltArgs(ctx, btf.argumentExprs, btf.Name(), row) - if err != nil { - return nil, err - } - - apr, err := cli.CreateBackupArgParser().Parse(args) - if err != nil { - return nil, sql.ErrInvalidArgumentDetails.New(btf.Name(), err.Error()) - } - - sqlDb, ok := btf.database.(dsess.SqlDatabase) - if !ok { - return nil, fmt.Errorf("unexpected database type: %T", btf.database) - } - - dbName := sqlDb.Name() - if len(dbName) == 0 { - return nil, fmt.Errorf("empty database name") - } - - sess := dsess.DSessFromSess(ctx.Session) - dbData, ok := sess.GetDbData(ctx, dbName) - if !ok { - return nil, sql.ErrDatabaseNotFound.New(dbName) - } - - backups, err := dbData.Rsr.GetBackups() - if err != nil { - return nil, err - } - - names := make([]string, 0) - remotes := map[string]env.Remote{} - - backups.Iter(func(key string, val env.Remote) bool { - names = append(names, key) - remotes[key] = val - return true - }) - - sort.Strings(names) - - return &backupsItr{ - names: names, - remotes: remotes, - showVerbose: apr.Contains(cli.VerboseFlag), - idx: 0, - }, nil -} - -func (btf *BackupsTableFunction) Schema() sql.Schema { - argParser := cli.CreateBackupArgParser() - var literals []string - // This is called at plan time, so we can only evaluate constant literals - for _, expr := range btf.argumentExprs { - if !expr.Resolved() { - continue - } - - lit, ok := expr.(*expression.Literal) - if !ok { - continue - } - - val, _ := lit.Eval(nil, nil) - str, ok := val.(string) - if !ok { - continue - } - literals = append(literals, str) - } - - apr, _ := argParser.Parse(literals) - if apr.Contains(cli.VerboseFlag) { - return dtables.BackupsTableSchema - } - return dtables.BackupsTableSchema[:2] -} - -func (btf *BackupsTableFunction) Resolved() bool { - for _, expr := range btf.argumentExprs { - if !expr.Resolved() { - return false - } - } - return true -} - -func (btf *BackupsTableFunction) String() string { - var args []string - for _, expr := range btf.argumentExprs { - args = append(args, expr.String()) - } - return fmt.Sprintf("DOLT_BACKUPS(%s)", strings.Join(args, ", ")) -} - -func (btf *BackupsTableFunction) Children() []sql.Node { - return nil -} - -func (btf *BackupsTableFunction) WithChildren(children ...sql.Node) (sql.Node, error) { - if len(children) != 0 { - return nil, fmt.Errorf("unexpected children") - } - return btf, nil -} - -func (btf *BackupsTableFunction) IsReadOnly() bool { - return true -} - -func (btf *BackupsTableFunction) Expressions() []sql.Expression { - return btf.argumentExprs -} - -func (btf *BackupsTableFunction) WithExpressions(expressions ...sql.Expression) (sql.Node, error) { - if len(expressions) > 1 { - return nil, sql.ErrInvalidArgumentNumber.New(btf.Name(), "0 to 1", len(expressions)) - } - - newBtf := *btf - newBtf.argumentExprs = expressions - - return &newBtf, nil -} - -func (btf *BackupsTableFunction) Name() string { - return BackupsTableFunctionName -} - -func (btf *BackupsTableFunction) Database() sql.Database { - return btf.database -} - -func (btf *BackupsTableFunction) WithDatabase(database sql.Database) (sql.Node, error) { - newBtf := *btf - newBtf.database = database - return &newBtf, nil -} - -type backupsItr struct { - names []string - remotes map[string]env.Remote - showVerbose bool - idx int -} - -var _ sql.RowIter = (*backupsItr)(nil) - -func (bi *backupsItr) Next(ctx *sql.Context) (sql.Row, error) { - if bi.idx >= len(bi.names) { - return nil, io.EOF - } - - name := bi.names[bi.idx] - remote := bi.remotes[name] - bi.idx++ - - if bi.showVerbose { - params, _, err := types.JSON.Convert(ctx, remote.Params) - if err != nil { - return nil, err - } - return sql.NewRow(name, remote.Url, params), nil - } - - return sql.NewRow(name, remote.Url), nil -} - -func (bi *backupsItr) Close(_ *sql.Context) error { - return nil -} diff --git a/go/libraries/doltcore/sqle/dtables/backups_table.go b/go/libraries/doltcore/sqle/dtables/backups_table.go index 50f52fe120..26c2517efa 100644 --- a/go/libraries/doltcore/sqle/dtables/backups_table.go +++ b/go/libraries/doltcore/sqle/dtables/backups_table.go @@ -27,12 +27,6 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/sqle/index" ) -var BackupsTableSchema = []*sql.Column{ - {Name: "name", Type: types.Text, PrimaryKey: true, Nullable: false}, - {Name: "url", Type: types.Text, PrimaryKey: false, Nullable: false}, - {Name: "params", Type: types.JSON, PrimaryKey: false, Nullable: false}, -} - type BackupsTable struct { db dsess.SqlDatabase tableName string @@ -53,7 +47,11 @@ func (bt BackupsTable) String() string { } func (bt BackupsTable) Schema() sql.Schema { - return BackupsTableSchema[:2] + return []*sql.Column{ + {Name: "name", Type: types.Text, PrimaryKey: true, Nullable: false}, + {Name: "url", Type: types.Text, PrimaryKey: false, Nullable: false}, + {Name: "params", Type: types.JSON, PrimaryKey: false, Nullable: false}, + } } func (bt BackupsTable) Collation() sql.CollationID { @@ -69,9 +67,10 @@ func (bt BackupsTable) PartitionRows(context *sql.Context, _ sql.Partition) (sql } type backupsItr struct { - urls map[string]string - names []string - idx int + names []string + urls map[string]string + params map[string]map[string]string + idx int } var _ sql.RowIter = (*backupsItr)(nil) @@ -80,7 +79,14 @@ func (bi *backupsItr) Next(ctx *sql.Context) (sql.Row, error) { if bi.idx < len(bi.names) { bi.idx++ name := bi.names[bi.idx-1] - return sql.NewRow(name, bi.urls[name]), nil + url := bi.urls[name] + + params, _, err := types.JSON.Convert(ctx, bi.params[name]) + if err != nil { + return nil, err + } + + return sql.NewRow(name, url, params), nil } return nil, io.EOF } @@ -105,14 +111,16 @@ func newBackupsIter(ctx *sql.Context, dbName string) (*backupsItr, error) { names := make([]string, 0) urls := map[string]string{} + params := map[string]map[string]string{} backups.Iter(func(key string, val env.Remote) bool { names = append(names, key) urls[key] = val.Url + params[key] = val.Params return true }) sort.Strings(names) - return &backupsItr{names: names, urls: urls, idx: 0}, nil + return &backupsItr{names: names, urls: urls, params: params, idx: 0}, nil } diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go b/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go index be77ca50c4..2351b198bb 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go @@ -1487,12 +1487,6 @@ func TestBackupsSystemTable(t *testing.T) { enginetest.TestScript(t, h, BackupsSystemTableQueries) } -func TestBackupsTableFunction(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, BackupsTableFunctionQueries) -} - func TestHistorySystemTable(t *testing.T) { harness := newDoltEnginetestHarness(t).WithParallelism(2) RunHistorySystemTableTests(t, harness) diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_system_table_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_system_table_queries.go index c2b1d1d4db..0a3a9abac4 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_system_table_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_system_table_queries.go @@ -83,7 +83,7 @@ var BackupsSystemTableQueries = queries.ScriptTest{ ExpectedErrStr: "table doesn't support UPDATE", }, { - Query: "insert into dolt_backups values ('backup4', 'file:///tmp/broken');", // nolint: gas + Query: "insert into dolt_backups values ('backup4', 'file:///tmp/broken', '{}');", // nolint: gas ExpectedErrStr: "table doesn't support INSERT INTO", }, { @@ -95,29 +95,11 @@ var BackupsSystemTableQueries = queries.ScriptTest{ Expected: []sql.Row{{0}}, }, { - Query: "select * from dolt_backups where url like 'aws://%'", + Query: "select name, url, params from dolt_backups where url like 'aws://%'", Expected: []sql.Row{ - {"backup2", "aws://[ddb_table:ddb_s3_bucket]/db1"}, - {"backup4", "aws://[ddb_table_4:ddb_s3_bucket_4]/db1"}, + {"backup2", "aws://[ddb_table:ddb_s3_bucket]/db1", "{}"}, + {"backup4", "aws://[ddb_table_4:ddb_s3_bucket_4]/db1", "{}"}, }, }, }, } - -var BackupsTableFunctionQueries = queries.ScriptTest{ - Name: "dolt_backups() table function", - SetUpScript: []string{ - `call dolt_backup("add", "backup1", "file:///tmp/backup1");`, - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "select name from dolt_backups();", - Expected: []sql.Row{{"backup1"}}, - }, - { - Query: "select name from dolt_backups() where name = 'backup1';", - Expected: []sql.Row{{"backup1"}}, - }, - // Table functions are implicitly read-only at the SQL parser level. - }, -} diff --git a/go/libraries/utils/argparser/results.go b/go/libraries/utils/argparser/results.go index 18612184b2..25c8a20e69 100644 --- a/go/libraries/utils/argparser/results.go +++ b/go/libraries/utils/argparser/results.go @@ -273,13 +273,3 @@ func (res *ArgParseResults) FlagsEqualTo(names []string, val bool) *set.StrSet { return set.NewStrSet(results) } - -// Option returns the long form of the option flag (e.g., "--force"). Returns empty string if the option is not found or -// has no long name. -func (res *ArgParseResults) Option(flagName string) string { - opt, ok := res.parser.nameOrAbbrevToOpt[flagName] - if !ok || opt.Name == "" { - return "" - } - return "--" + opt.Name -}