rm dolt_backups() table function, amend dolt_backups schema with json params column, and amend dolt backup command to use dolt_backups table

This commit is contained in:
elianddb
2025-11-21 02:48:39 -08:00
parent ff065d9c22
commit 4baaa61c2b
6 changed files with 36 additions and 300 deletions

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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.
},
}

View File

@@ -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
}