mirror of
https://github.com/dolthub/dolt.git
synced 2025-12-30 16:12:39 -06:00
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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user