From 86d12d7b8e64eb4a1430376b1d92413d52bb78c1 Mon Sep 17 00:00:00 2001 From: Stephanie You Date: Thu, 30 Nov 2023 14:59:59 -0800 Subject: [PATCH 1/5] remove dolt sql-client command --- go/cmd/dolt/commands/sqlserver/sqlclient.go | 521 ------------------ go/cmd/dolt/commands/sqlserver/sqlserver.go | 131 +++++ go/cmd/dolt/dolt.go | 3 - .../bats/sql-client-no-privs-no-mysql.expect | 20 - integration-tests/bats/sql-client.bats | 189 ------- 5 files changed, 131 insertions(+), 733 deletions(-) delete mode 100644 go/cmd/dolt/commands/sqlserver/sqlclient.go delete mode 100644 integration-tests/bats/sql-client-no-privs-no-mysql.expect delete mode 100644 integration-tests/bats/sql-client.bats diff --git a/go/cmd/dolt/commands/sqlserver/sqlclient.go b/go/cmd/dolt/commands/sqlserver/sqlclient.go deleted file mode 100644 index 32883bf95a..0000000000 --- a/go/cmd/dolt/commands/sqlserver/sqlclient.go +++ /dev/null @@ -1,521 +0,0 @@ -// Copyright 2020 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 sqlserver - -import ( - "context" - mysql "database/sql" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "time" - - "github.com/abiosoft/readline" - "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/types" - "github.com/dolthub/ishell" - "github.com/fatih/color" - mysqlDriver "github.com/go-sql-driver/mysql" - "github.com/gocraft/dbr/v2" - "github.com/gocraft/dbr/v2/dialect" - - "github.com/dolthub/dolt/go/cmd/dolt/cli" - "github.com/dolthub/dolt/go/cmd/dolt/commands" - "github.com/dolthub/dolt/go/cmd/dolt/commands/engine" - "github.com/dolthub/dolt/go/libraries/doltcore/dbfactory" - "github.com/dolthub/dolt/go/libraries/doltcore/env" - "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" - "github.com/dolthub/dolt/go/libraries/utils/argparser" - "github.com/dolthub/dolt/go/libraries/utils/filesys" - "github.com/dolthub/dolt/go/libraries/utils/iohelp" - "github.com/dolthub/dolt/go/libraries/utils/svcs" -) - -const ( - sqlClientDualFlag = "dual" - SqlClientQueryFlag = "query" - sqlClientResultFormat = "result-format" -) - -var sqlClientDocs = cli.CommandDocumentationContent{ - ShortDesc: "Starts a built-in MySQL client.", - LongDesc: `Starts a MySQL client that is built into dolt. May connect to any database that supports MySQL connections, including dolt servers. - -You may also start a dolt server and automatically connect to it using this client. Both the server and client will be a part of the same process. This is useful for testing behavior of the dolt server without the need for an external client, and is not recommended for general usage. - -Similar to {{.EmphasisLeft}}dolt sql-server{{.EmphasisRight}}, this command may use a YAML configuration file or command line arguments. For more information on the YAML file, refer to the documentation on {{.EmphasisLeft}}dolt sql-server{{.EmphasisRight}}.`, - Synopsis: []string{ - "[-d] --config {{.LessThan}}file{{.GreaterThan}}", - "[-d] [-H {{.LessThan}}host{{.GreaterThan}}] [-P {{.LessThan}}port{{.GreaterThan}}] [-u {{.LessThan}}user{{.GreaterThan}}] [-p {{.LessThan}}password{{.GreaterThan}}] [-t {{.LessThan}}timeout{{.GreaterThan}}] [-l {{.LessThan}}loglevel{{.GreaterThan}}] [--data-dir {{.LessThan}}directory{{.GreaterThan}}] [--query-parallelism {{.LessThan}}num-go-routines{{.GreaterThan}}] [-r]", - "-q {{.LessThan}}string{{.GreaterThan}} [--use-db {{.LessThan}}db_name{{.GreaterThan}}] [--result-format {{.LessThan}}format{{.GreaterThan}}] [-H {{.LessThan}}host{{.GreaterThan}}] [-P {{.LessThan}}port{{.GreaterThan}}] [-u {{.LessThan}}user{{.GreaterThan}}] [-p {{.LessThan}}password{{.GreaterThan}}]", - }, -} - -type SqlClientCmd struct { - VersionStr string -} - -var _ cli.Command = SqlClientCmd{} - -func (cmd SqlClientCmd) Name() string { - return "sql-client" -} - -func (cmd SqlClientCmd) Description() string { - return "Starts a built-in MySQL client." -} - -func (cmd SqlClientCmd) Docs() *cli.CommandDocumentation { - ap := cmd.ArgParser() - return cli.NewCommandDocumentation(sqlClientDocs, ap) -} - -func (cmd SqlClientCmd) ArgParser() *argparser.ArgParser { - ap := SqlServerCmd{}.ArgParserWithName(cmd.Name()) - ap.SupportsFlag(sqlClientDualFlag, "d", "Causes this command to spawn a dolt server that is automatically connected to.") - ap.SupportsString(SqlClientQueryFlag, "q", "string", "Sends the given query to the server and immediately exits.") - ap.SupportsString(commands.UseDbFlag, "", "db_name", fmt.Sprintf("Selects the given database before executing a query. "+ - "By default, uses the current folder's name. Must be used with the --%s flag.", SqlClientQueryFlag)) - ap.SupportsString(sqlClientResultFormat, "", "format", fmt.Sprintf("Returns the results in the given format. Must be used with the --%s flag.", SqlClientQueryFlag)) - return ap -} - -func (cmd SqlClientCmd) RequiresRepo() bool { - return false -} - -func (cmd SqlClientCmd) Hidden() bool { - return true -} - -func (cmd SqlClientCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { - ap := cmd.ArgParser() - help, _ := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, sqlClientDocs, ap)) - - apr := cli.ParseArgsOrDie(ap, args, help) - var serverConfig ServerConfig - var svcsController *svcs.Controller - var err error - - cli.Println(color.YellowString("WARNING: This command is being deprecated and is not recommended for general use.\n" + - "\t Use dolt sql or any compatible MySQL client instead.")) - - if apr.Contains(sqlClientDualFlag) { - if !dEnv.Valid() { - if !cli.CheckEnvIsValid(dEnv) { - return 2 - } - - cli.PrintErrln(color.RedString("--dual flag requires running within a dolt database directory")) - cli.PrintErrln(err.Error()) - return 1 - } - if apr.Contains(SqlClientQueryFlag) { - cli.PrintErrln(color.RedString(fmt.Sprintf("--%s flag may not be used with --%s", sqlClientDualFlag, SqlClientQueryFlag))) - return 1 - } - if apr.Contains(commands.UseDbFlag) { - cli.PrintErrln(color.RedString(fmt.Sprintf("--%s flag may not be used with --%s", sqlClientDualFlag, commands.UseDbFlag))) - return 1 - } - if apr.Contains(sqlClientResultFormat) { - cli.PrintErrln(color.RedString(fmt.Sprintf("--%s flag may not be used with --%s", sqlClientDualFlag, sqlClientResultFormat))) - return 1 - } - - serverConfig, err = GetServerConfig(dEnv.FS, apr) - if err != nil { - cli.PrintErrln(color.RedString("Bad Configuration")) - cli.PrintErrln(err.Error()) - return 1 - } - if err = SetupDoltConfig(dEnv, apr, serverConfig); err != nil { - cli.PrintErrln(color.RedString("Bad Configuration")) - cli.PrintErrln(err.Error()) - return 1 - } - cli.PrintErrf("Starting server with Config %v\n", ConfigInfo(serverConfig)) - - svcsController = svcs.NewController() - go func() { - _, _ = Serve(ctx, cmd.VersionStr, serverConfig, svcsController, dEnv) - }() - err = svcsController.WaitForStart() - if err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - } else { - serverConfig, err = GetServerConfig(dEnv.FS, apr) - if err != nil { - cli.PrintErrln(color.RedString("Bad Configuration")) - cli.PrintErrln(err.Error()) - return 1 - } - } - - query, hasQuery := apr.GetValue(SqlClientQueryFlag) - dbToUse, hasUseDb := apr.GetValue(commands.UseDbFlag) - resultFormat, hasResultFormat := apr.GetValue(sqlClientResultFormat) - if !hasQuery && hasUseDb { - cli.PrintErrln(color.RedString(fmt.Sprintf("--%s may only be used with --%s", commands.UseDbFlag, SqlClientQueryFlag))) - return 1 - } else if !hasQuery && hasResultFormat { - cli.PrintErrln(color.RedString(fmt.Sprintf("--%s may only be used with --%s", commands.UseDbFlag, sqlClientResultFormat))) - return 1 - } - if !hasUseDb && hasQuery { - directory, err := os.Getwd() - if err != nil { - cli.PrintErrln(color.RedString(err.Error())) - return 1 - } - dbToUse = dbfactory.DirToDBName(filepath.Base(directory)) - } - format := engine.FormatTabular - if hasResultFormat { - switch strings.ToLower(resultFormat) { - case "tabular": - format = engine.FormatTabular - case "csv": - format = engine.FormatCsv - case "json": - format = engine.FormatJson - case "null": - format = engine.FormatNull - case "vertical": - format = engine.FormatVertical - default: - cli.PrintErrln(color.RedString(fmt.Sprintf("unknown --%s value: %s", sqlClientResultFormat, resultFormat))) - return 1 - } - } - - // The standard DSN parser cannot handle a forward slash in the database name, so we have to workaround it. - // See the original issue: https://github.com/dolthub/dolt/issues/4623 - parsedMySQLConfig, err := mysqlDriver.ParseDSN(ConnectionString(serverConfig, "no_database")) - if err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - - if parsedMySQLConfig.User == "" { - cli.PrintErrln(color.RedString("--user or -u argument is required")) - return 1 - } - - parsedMySQLConfig.DBName = dbToUse - mysqlConnector, err := mysqlDriver.NewConnector(parsedMySQLConfig) - if err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - conn := &dbr.Connection{DB: mysql.OpenDB(mysqlConnector), EventReceiver: nil, Dialect: dialect.MySQL} - _ = conn.Ping() - - if hasQuery { - defer conn.Close() - - if apr.Contains(noAutoCommitFlag) { - _, err = conn.Exec("set @@autocommit = off;") - if err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - } - - scanner := commands.NewSqlStatementScanner(strings.NewReader(query)) - query = "" - for scanner.Scan() { - query += scanner.Text() - if len(query) == 0 || query == "\n" { - continue - } - - rows, err := conn.Query(query) - if err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - if rows != nil { - sqlCtx := sql.NewContext(ctx) - wrapper, err := NewMysqlRowWrapper(rows) - if err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - defer wrapper.Close(sqlCtx) - if wrapper.HasMoreRows() { - err = engine.PrettyPrintResults(sqlCtx, format, wrapper.Schema(), wrapper) - if err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - } - } - query = "" - } - - if err = scanner.Err(); err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - return 0 - } - - ticker := time.NewTicker(time.Second * 10) - go func() { - for range ticker.C { - _ = conn.Ping() - } - }() - - _ = iohelp.WriteLine(cli.CliOut, `# Welcome to the Dolt MySQL client. -# Statements must be terminated with ';'. -# "exit" or "quit" (or Ctrl-D) to exit.`) - historyFile := filepath.Join(".sqlhistory") // history file written to working dir - prompt := "mysql> " - multilinePrompt := fmt.Sprintf(fmt.Sprintf("%%%ds", len(prompt)), "-> ") - - rlConf := readline.Config{ - Prompt: prompt, - Stdout: cli.CliOut, - Stderr: cli.CliOut, - HistoryFile: historyFile, - HistoryLimit: 500, - HistorySearchFold: true, - DisableAutoSaveHistory: true, - } - shellConf := ishell.UninterpretedConfig{ - ReadlineConfig: &rlConf, - QuitKeywords: []string{ - "quit", "exit", "quit()", "exit()", - }, - LineTerminator: ";", - MysqlShellCmds: []string{}, - } - - shell := ishell.NewUninterpreted(&shellConf) - shell.SetMultiPrompt(multilinePrompt) - - shell.EOF(func(c *ishell.Context) { - c.Stop() - }) - - shell.Interrupt(func(c *ishell.Context, count int, input string) { - if count > 1 { - c.Stop() - } else { - c.Println("Received SIGINT. Interrupt again to exit, or use ^D, quit, or exit.") - } - }) - - shell.Uninterpreted(func(c *ishell.Context) { - query := c.Args[0] - if len(strings.TrimSpace(query)) == 0 { - return - } - - // grab time for query timing - startTime := time.Now() - - rows, err := conn.Query(query) - if err != nil { - shell.Println(color.RedString(err.Error())) - return - } - if rows != nil { - wrapper, err := NewMysqlRowWrapper(rows) - if err != nil { - shell.Println(color.RedString(err.Error())) - return - } - if wrapper.HasMoreRows() { - sqlCtx := sql.NewContext(ctx) - sqlCtx.SetQueryTime(startTime) - err = engine.PrettyPrintResultsExtended(sqlCtx, engine.FormatTabular, wrapper.Schema(), wrapper) - if err != nil { - shell.Println(color.RedString(err.Error())) - return - } - } else { - err := iohelp.WriteLine(cli.CliOut, fmt.Sprintf("Query OK (%.2f sec)", secondsSince(startTime, time.Now()))) - if err != nil { - shell.Println(color.RedString(err.Error())) - return - } - } - } - - // TODO: there's a bug in the readline library when editing multi-line history entries. - singleLine := strings.ReplaceAll(query, "\n", " ") - if err := shell.AddHistory(singleLine); err != nil { - shell.Println(color.RedString(err.Error())) - } - }) - - shell.Run() - ticker.Stop() - - // everything beyond this point may error and the overall process is still a success, thus we return 0 regardless - err = conn.Close() - if err != nil { - cli.PrintErrln(err.Error()) - } - if apr.Contains(sqlClientDualFlag) { - svcsController.Stop() - err = svcsController.WaitForStop() - if err != nil { - cli.PrintErrln(err.Error()) - } - } - - return 0 -} - -type MysqlRowWrapper struct { - rows *mysql.Rows - schema sql.Schema - finished bool - vRow []*string - iRow []interface{} -} - -var _ sql.RowIter = (*MysqlRowWrapper)(nil) - -func NewMysqlRowWrapper(rows *mysql.Rows) (*MysqlRowWrapper, error) { - colNames, err := rows.Columns() - if err != nil { - return nil, err - } - schema := make(sql.Schema, len(colNames)) - vRow := make([]*string, len(colNames)) - iRow := make([]interface{}, len(colNames)) - for i, colName := range colNames { - schema[i] = &sql.Column{ - Name: colName, - Type: types.LongText, - Nullable: true, - } - iRow[i] = &vRow[i] - } - return &MysqlRowWrapper{ - rows: rows, - schema: schema, - finished: !rows.Next(), - vRow: vRow, - iRow: iRow, - }, nil -} - -func (s *MysqlRowWrapper) Schema() sql.Schema { - return s.schema -} - -func (s *MysqlRowWrapper) Next(*sql.Context) (sql.Row, error) { - if s.finished { - return nil, io.EOF - } - err := s.rows.Scan(s.iRow...) - if err != nil { - return nil, err - } - sqlRow := make(sql.Row, len(s.vRow)) - for i, val := range s.vRow { - if val != nil { - sqlRow[i] = *val - } - } - s.finished = !s.rows.Next() - return sqlRow, nil -} - -func (s *MysqlRowWrapper) HasMoreRows() bool { - return !s.finished -} - -func (s *MysqlRowWrapper) Close(*sql.Context) error { - return s.rows.Close() -} - -// secondsSince returns the number of full and partial seconds since the time given -func secondsSince(start time.Time, end time.Time) float64 { - runTime := end.Sub(start) - seconds := runTime / time.Second - milliRemainder := (runTime - seconds*time.Second) / time.Millisecond - timeDisplay := float64(seconds) + float64(milliRemainder)*.001 - return timeDisplay -} - -// ConnectionQueryist executes queries by connecting to a running mySql server. -type ConnectionQueryist struct { - connection *dbr.Connection -} - -var _ cli.Queryist = ConnectionQueryist{} - -func (c ConnectionQueryist) Query(ctx *sql.Context, query string) (sql.Schema, sql.RowIter, error) { - rows, err := c.connection.QueryContext(ctx, query) - if err != nil { - return nil, nil, err - } - rowIter, err := NewMysqlRowWrapper(rows) - if err != nil { - return nil, nil, err - } - return rowIter.Schema(), rowIter, nil -} - -// BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this -// module isn't ideal, but it's the only way to get the server config into the queryist. -func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults, host string, port int, useTLS bool, dbRev string) (cli.LateBindQueryist, error) { - clientConfig, err := GetClientConfig(cwdFS, creds, apr) - if err != nil { - return nil, err - } - - // ParseDSN currently doesn't support `/` in the db name - dbName, _ := dsess.SplitRevisionDbName(dbRev) - parsedMySQLConfig, err := mysqlDriver.ParseDSN(ConnectionString(clientConfig, dbName)) - if err != nil { - return nil, err - } - - parsedMySQLConfig.DBName = dbRev - parsedMySQLConfig.Addr = fmt.Sprintf("%s:%d", host, port) - - if useTLS { - parsedMySQLConfig.TLSConfig = "true" - } - - mysqlConnector, err := mysqlDriver.NewConnector(parsedMySQLConfig) - if err != nil { - return nil, err - } - - conn := &dbr.Connection{DB: mysql.OpenDB(mysqlConnector), EventReceiver: nil, Dialect: dialect.MySQL} - - queryist := ConnectionQueryist{connection: conn} - - var lateBind cli.LateBindQueryist = func(ctx context.Context) (cli.Queryist, *sql.Context, func(), error) { - sqlCtx := sql.NewContext(ctx) - sqlCtx.SetCurrentDatabase(dbRev) - return queryist, sqlCtx, func() { conn.Conn(ctx) }, nil - } - - return lateBind, nil -} diff --git a/go/cmd/dolt/commands/sqlserver/sqlserver.go b/go/cmd/dolt/commands/sqlserver/sqlserver.go index 609015d468..75b7e408c2 100644 --- a/go/cmd/dolt/commands/sqlserver/sqlserver.go +++ b/go/cmd/dolt/commands/sqlserver/sqlserver.go @@ -16,13 +16,20 @@ package sqlserver import ( "context" + sql2 "database/sql" "fmt" + "io" "path/filepath" "strconv" "strings" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" "github.com/fatih/color" + "github.com/go-sql-driver/mysql" + "github.com/gocraft/dbr/v2" + "github.com/gocraft/dbr/v2/dialect" "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" @@ -541,3 +548,127 @@ func getYAMLServerConfig(fs filesys.Filesys, path string) (ServerConfig, error) return cfg, nil } + +// BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this +// module isn't ideal, but it's the only way to get the server config into the queryist. +func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults, host string, port int, useTLS bool, dbRev string) (cli.LateBindQueryist, error) { + clientConfig, err := GetClientConfig(cwdFS, creds, apr) + if err != nil { + return nil, err + } + + // ParseDSN currently doesn't support `/` in the db name + dbName, _ := dsess.SplitRevisionDbName(dbRev) + parsedMySQLConfig, err := mysql.ParseDSN(ConnectionString(clientConfig, dbName)) + if err != nil { + return nil, err + } + + parsedMySQLConfig.DBName = dbRev + parsedMySQLConfig.Addr = fmt.Sprintf("%s:%d", host, port) + + if useTLS { + parsedMySQLConfig.TLSConfig = "true" + } + + mysqlConnector, err := mysql.NewConnector(parsedMySQLConfig) + if err != nil { + return nil, err + } + + conn := &dbr.Connection{DB: sql2.OpenDB(mysqlConnector), EventReceiver: nil, Dialect: dialect.MySQL} + + queryist := ConnectionQueryist{connection: conn} + + var lateBind cli.LateBindQueryist = func(ctx context.Context) (cli.Queryist, *sql.Context, func(), error) { + sqlCtx := sql.NewContext(ctx) + sqlCtx.SetCurrentDatabase(dbRev) + return queryist, sqlCtx, func() { conn.Conn(ctx) }, nil + } + + return lateBind, nil +} + +// ConnectionQueryist executes queries by connecting to a running mySql server. +type ConnectionQueryist struct { + connection *dbr.Connection +} + +var _ cli.Queryist = ConnectionQueryist{} + +func (c ConnectionQueryist) Query(ctx *sql.Context, query string) (sql.Schema, sql.RowIter, error) { + rows, err := c.connection.QueryContext(ctx, query) + if err != nil { + return nil, nil, err + } + rowIter, err := NewMysqlRowWrapper(rows) + if err != nil { + return nil, nil, err + } + return rowIter.Schema(), rowIter, nil +} + +type MysqlRowWrapper struct { + rows *sql2.Rows + schema sql.Schema + finished bool + vRow []*string + iRow []interface{} +} + +var _ sql.RowIter = (*MysqlRowWrapper)(nil) + +func NewMysqlRowWrapper(rows *sql2.Rows) (*MysqlRowWrapper, error) { + colNames, err := rows.Columns() + if err != nil { + return nil, err + } + schema := make(sql.Schema, len(colNames)) + vRow := make([]*string, len(colNames)) + iRow := make([]interface{}, len(colNames)) + for i, colName := range colNames { + schema[i] = &sql.Column{ + Name: colName, + Type: types.LongText, + Nullable: true, + } + iRow[i] = &vRow[i] + } + return &MysqlRowWrapper{ + rows: rows, + schema: schema, + finished: !rows.Next(), + vRow: vRow, + iRow: iRow, + }, nil +} + +func (s *MysqlRowWrapper) Schema() sql.Schema { + return s.schema +} + +func (s *MysqlRowWrapper) Next(*sql.Context) (sql.Row, error) { + if s.finished { + return nil, io.EOF + } + err := s.rows.Scan(s.iRow...) + if err != nil { + return nil, err + } + sqlRow := make(sql.Row, len(s.vRow)) + for i, val := range s.vRow { + if val != nil { + sqlRow[i] = *val + } + } + s.finished = !s.rows.Next() + return sqlRow, nil +} + +func (s *MysqlRowWrapper) HasMoreRows() bool { + return !s.finished +} + +func (s *MysqlRowWrapper) Close(*sql.Context) error { + return s.rows.Close() +} diff --git a/go/cmd/dolt/dolt.go b/go/cmd/dolt/dolt.go index 41b3ef5b28..a170747660 100644 --- a/go/cmd/dolt/dolt.go +++ b/go/cmd/dolt/dolt.go @@ -81,7 +81,6 @@ var doltSubCommands = []cli.Command{ commands.SqlCmd{VersionStr: Version}, admin.Commands, sqlserver.SqlServerCmd{VersionStr: Version}, - sqlserver.SqlClientCmd{VersionStr: Version}, commands.LogCmd{}, commands.ShowCmd{}, commands.BranchCmd{}, @@ -128,7 +127,6 @@ var doltSubCommands = []cli.Command{ var commandsWithoutCliCtx = []cli.Command{ admin.Commands, sqlserver.SqlServerCmd{VersionStr: Version}, - sqlserver.SqlClientCmd{VersionStr: Version}, commands.CloneCmd{}, commands.RemoteCmd{}, commands.BackupCmd{}, @@ -162,7 +160,6 @@ var commandsWithoutGlobalArgSupport = []cli.Command{ commands.LoginCmd{}, credcmds.Commands, sqlserver.SqlServerCmd{VersionStr: Version}, - sqlserver.SqlClientCmd{VersionStr: Version}, commands.VersionCmd{VersionStr: Version}, commands.ConfigCmd{}, } diff --git a/integration-tests/bats/sql-client-no-privs-no-mysql.expect b/integration-tests/bats/sql-client-no-privs-no-mysql.expect deleted file mode 100644 index fafa27d063..0000000000 --- a/integration-tests/bats/sql-client-no-privs-no-mysql.expect +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/expect - -# start client -set timeout 1 -spawn dolt sql-client --host=0.0.0.0 --port=$PORT -d - -# list all users -expect { - "mysql> " { send "select user from mysql.user;\r"; } -} - -# look for only root user -expect { - "root" -} - -# quit -expect { - "mysql> " { exit 0 } -} \ No newline at end of file diff --git a/integration-tests/bats/sql-client.bats b/integration-tests/bats/sql-client.bats deleted file mode 100644 index c8374d3b7b..0000000000 --- a/integration-tests/bats/sql-client.bats +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env bats -load $BATS_TEST_DIRNAME/helper/common.bash -load $BATS_TEST_DIRNAME/helper/query-server-common.bash - -make_repo() { - mkdir "$1" - cd "$1" - dolt init - cd .. -} - -create_test_table() { - dolt sql-client --host=0.0.0.0 --port=$PORT --user=dolt < Date: Thu, 30 Nov 2023 23:11:26 +0000 Subject: [PATCH 2/5] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/cmd/dolt/commands/sqlserver/sqlserver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/cmd/dolt/commands/sqlserver/sqlserver.go b/go/cmd/dolt/commands/sqlserver/sqlserver.go index 75b7e408c2..44186e815f 100644 --- a/go/cmd/dolt/commands/sqlserver/sqlserver.go +++ b/go/cmd/dolt/commands/sqlserver/sqlserver.go @@ -23,7 +23,6 @@ import ( "strconv" "strings" - "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" "github.com/fatih/color" @@ -35,6 +34,7 @@ import ( "github.com/dolthub/dolt/go/cmd/dolt/commands" eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" "github.com/dolthub/dolt/go/libraries/doltcore/env" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/libraries/utils/filesys" "github.com/dolthub/dolt/go/libraries/utils/svcs" From bd045ae65809af34887e2d13ecb14f3696513d77 Mon Sep 17 00:00:00 2001 From: Stephanie You Date: Thu, 30 Nov 2023 15:32:43 -0800 Subject: [PATCH 3/5] refactor queryist utils --- go/cmd/dolt/commands/sqlserver/sqlserver.go | 136 +---------------- go/cmd/dolt/dolt.go | 4 +- go/cmd/dolt/queryist_utils.go | 157 ++++++++++++++++++++ 3 files changed, 161 insertions(+), 136 deletions(-) create mode 100644 go/cmd/dolt/queryist_utils.go diff --git a/go/cmd/dolt/commands/sqlserver/sqlserver.go b/go/cmd/dolt/commands/sqlserver/sqlserver.go index 44186e815f..55382499cb 100644 --- a/go/cmd/dolt/commands/sqlserver/sqlserver.go +++ b/go/cmd/dolt/commands/sqlserver/sqlserver.go @@ -16,28 +16,20 @@ package sqlserver import ( "context" - sql2 "database/sql" "fmt" - "io" "path/filepath" "strconv" "strings" - "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/types" - "github.com/fatih/color" - "github.com/go-sql-driver/mysql" - "github.com/gocraft/dbr/v2" - "github.com/gocraft/dbr/v2/dialect" - "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" "github.com/dolthub/dolt/go/libraries/doltcore/env" - "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/libraries/utils/filesys" "github.com/dolthub/dolt/go/libraries/utils/svcs" + "github.com/dolthub/go-mysql-server/sql" + "github.com/fatih/color" ) const ( @@ -548,127 +540,3 @@ func getYAMLServerConfig(fs filesys.Filesys, path string) (ServerConfig, error) return cfg, nil } - -// BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this -// module isn't ideal, but it's the only way to get the server config into the queryist. -func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults, host string, port int, useTLS bool, dbRev string) (cli.LateBindQueryist, error) { - clientConfig, err := GetClientConfig(cwdFS, creds, apr) - if err != nil { - return nil, err - } - - // ParseDSN currently doesn't support `/` in the db name - dbName, _ := dsess.SplitRevisionDbName(dbRev) - parsedMySQLConfig, err := mysql.ParseDSN(ConnectionString(clientConfig, dbName)) - if err != nil { - return nil, err - } - - parsedMySQLConfig.DBName = dbRev - parsedMySQLConfig.Addr = fmt.Sprintf("%s:%d", host, port) - - if useTLS { - parsedMySQLConfig.TLSConfig = "true" - } - - mysqlConnector, err := mysql.NewConnector(parsedMySQLConfig) - if err != nil { - return nil, err - } - - conn := &dbr.Connection{DB: sql2.OpenDB(mysqlConnector), EventReceiver: nil, Dialect: dialect.MySQL} - - queryist := ConnectionQueryist{connection: conn} - - var lateBind cli.LateBindQueryist = func(ctx context.Context) (cli.Queryist, *sql.Context, func(), error) { - sqlCtx := sql.NewContext(ctx) - sqlCtx.SetCurrentDatabase(dbRev) - return queryist, sqlCtx, func() { conn.Conn(ctx) }, nil - } - - return lateBind, nil -} - -// ConnectionQueryist executes queries by connecting to a running mySql server. -type ConnectionQueryist struct { - connection *dbr.Connection -} - -var _ cli.Queryist = ConnectionQueryist{} - -func (c ConnectionQueryist) Query(ctx *sql.Context, query string) (sql.Schema, sql.RowIter, error) { - rows, err := c.connection.QueryContext(ctx, query) - if err != nil { - return nil, nil, err - } - rowIter, err := NewMysqlRowWrapper(rows) - if err != nil { - return nil, nil, err - } - return rowIter.Schema(), rowIter, nil -} - -type MysqlRowWrapper struct { - rows *sql2.Rows - schema sql.Schema - finished bool - vRow []*string - iRow []interface{} -} - -var _ sql.RowIter = (*MysqlRowWrapper)(nil) - -func NewMysqlRowWrapper(rows *sql2.Rows) (*MysqlRowWrapper, error) { - colNames, err := rows.Columns() - if err != nil { - return nil, err - } - schema := make(sql.Schema, len(colNames)) - vRow := make([]*string, len(colNames)) - iRow := make([]interface{}, len(colNames)) - for i, colName := range colNames { - schema[i] = &sql.Column{ - Name: colName, - Type: types.LongText, - Nullable: true, - } - iRow[i] = &vRow[i] - } - return &MysqlRowWrapper{ - rows: rows, - schema: schema, - finished: !rows.Next(), - vRow: vRow, - iRow: iRow, - }, nil -} - -func (s *MysqlRowWrapper) Schema() sql.Schema { - return s.schema -} - -func (s *MysqlRowWrapper) Next(*sql.Context) (sql.Row, error) { - if s.finished { - return nil, io.EOF - } - err := s.rows.Scan(s.iRow...) - if err != nil { - return nil, err - } - sqlRow := make(sql.Row, len(s.vRow)) - for i, val := range s.vRow { - if val != nil { - sqlRow[i] = *val - } - } - s.finished = !s.rows.Next() - return sqlRow, nil -} - -func (s *MysqlRowWrapper) HasMoreRows() bool { - return !s.finished -} - -func (s *MysqlRowWrapper) Close(*sql.Context) error { - return s.rows.Close() -} diff --git a/go/cmd/dolt/dolt.go b/go/cmd/dolt/dolt.go index a170747660..1e2655f053 100644 --- a/go/cmd/dolt/dolt.go +++ b/go/cmd/dolt/dolt.go @@ -654,7 +654,7 @@ If you're interested in running this command against a remote host, hit us up on port = 3306 } useTLS := !apr.Contains(cli.NoTLSFlag) - return sqlserver.BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, host, port, useTLS, useDb) + return BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, host, port, useTLS, useDb) } else { _, hasPort := apr.GetInt(cli.PortFlag) if hasPort { @@ -708,7 +708,7 @@ If you're interested in running this command against a remote host, hit us up on if !creds.Specified { creds = &cli.UserPassword{Username: sqlserver.LocalConnectionUser, Password: lock.Secret, Specified: false} } - return sqlserver.BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, "localhost", lock.Port, false, useDb) + return BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, "localhost", lock.Port, false, useDb) } if verbose { diff --git a/go/cmd/dolt/queryist_utils.go b/go/cmd/dolt/queryist_utils.go new file mode 100644 index 0000000000..66d23c39c5 --- /dev/null +++ b/go/cmd/dolt/queryist_utils.go @@ -0,0 +1,157 @@ +// Copyright 2023 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 main + +import ( + "context" + sql2 "database/sql" + "fmt" + "io" + + "github.com/dolthub/dolt/go/cmd/dolt/cli" + "github.com/dolthub/dolt/go/cmd/dolt/commands/sqlserver" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" + "github.com/dolthub/dolt/go/libraries/utils/argparser" + "github.com/dolthub/dolt/go/libraries/utils/filesys" + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" + "github.com/go-sql-driver/mysql" + "github.com/gocraft/dbr/v2" + "github.com/gocraft/dbr/v2/dialect" +) + +// BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this +// module isn't ideal, but it's the only way to get the server config into the queryist. +func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults, host string, port int, useTLS bool, dbRev string) (cli.LateBindQueryist, error) { + clientConfig, err := sqlserver.GetClientConfig(cwdFS, creds, apr) + if err != nil { + return nil, err + } + + // ParseDSN currently doesn't support `/` in the db name + dbName, _ := dsess.SplitRevisionDbName(dbRev) + parsedMySQLConfig, err := mysql.ParseDSN(sqlserver.ConnectionString(clientConfig, dbName)) + if err != nil { + return nil, err + } + + parsedMySQLConfig.DBName = dbRev + parsedMySQLConfig.Addr = fmt.Sprintf("%s:%d", host, port) + + if useTLS { + parsedMySQLConfig.TLSConfig = "true" + } + + mysqlConnector, err := mysql.NewConnector(parsedMySQLConfig) + if err != nil { + return nil, err + } + + conn := &dbr.Connection{DB: sql2.OpenDB(mysqlConnector), EventReceiver: nil, Dialect: dialect.MySQL} + + queryist := ConnectionQueryist{connection: conn} + + var lateBind cli.LateBindQueryist = func(ctx context.Context) (cli.Queryist, *sql.Context, func(), error) { + sqlCtx := sql.NewContext(ctx) + sqlCtx.SetCurrentDatabase(dbRev) + return queryist, sqlCtx, func() { conn.Conn(ctx) }, nil + } + + return lateBind, nil +} + +// ConnectionQueryist executes queries by connecting to a running mySql server. +type ConnectionQueryist struct { + connection *dbr.Connection +} + +var _ cli.Queryist = ConnectionQueryist{} + +func (c ConnectionQueryist) Query(ctx *sql.Context, query string) (sql.Schema, sql.RowIter, error) { + rows, err := c.connection.QueryContext(ctx, query) + if err != nil { + return nil, nil, err + } + rowIter, err := NewMysqlRowWrapper(rows) + if err != nil { + return nil, nil, err + } + return rowIter.Schema(), rowIter, nil +} + +type MysqlRowWrapper struct { + rows *sql2.Rows + schema sql.Schema + finished bool + vRow []*string + iRow []interface{} +} + +var _ sql.RowIter = (*MysqlRowWrapper)(nil) + +func NewMysqlRowWrapper(rows *sql2.Rows) (*MysqlRowWrapper, error) { + colNames, err := rows.Columns() + if err != nil { + return nil, err + } + schema := make(sql.Schema, len(colNames)) + vRow := make([]*string, len(colNames)) + iRow := make([]interface{}, len(colNames)) + for i, colName := range colNames { + schema[i] = &sql.Column{ + Name: colName, + Type: types.LongText, + Nullable: true, + } + iRow[i] = &vRow[i] + } + return &MysqlRowWrapper{ + rows: rows, + schema: schema, + finished: !rows.Next(), + vRow: vRow, + iRow: iRow, + }, nil +} + +func (s *MysqlRowWrapper) Schema() sql.Schema { + return s.schema +} + +func (s *MysqlRowWrapper) Next(*sql.Context) (sql.Row, error) { + if s.finished { + return nil, io.EOF + } + err := s.rows.Scan(s.iRow...) + if err != nil { + return nil, err + } + sqlRow := make(sql.Row, len(s.vRow)) + for i, val := range s.vRow { + if val != nil { + sqlRow[i] = *val + } + } + s.finished = !s.rows.Next() + return sqlRow, nil +} + +func (s *MysqlRowWrapper) HasMoreRows() bool { + return !s.finished +} + +func (s *MysqlRowWrapper) Close(*sql.Context) error { + return s.rows.Close() +} From 9761372c98699c8fed914b601178b8a42238fe56 Mon Sep 17 00:00:00 2001 From: Stephanie You Date: Thu, 30 Nov 2023 15:38:08 -0800 Subject: [PATCH 4/5] move queryist utils into sqlserver package --- go/cmd/dolt/{ => commands/sqlserver}/queryist_utils.go | 7 +++---- go/cmd/dolt/dolt.go | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) rename go/cmd/dolt/{ => commands/sqlserver}/queryist_utils.go (94%) diff --git a/go/cmd/dolt/queryist_utils.go b/go/cmd/dolt/commands/sqlserver/queryist_utils.go similarity index 94% rename from go/cmd/dolt/queryist_utils.go rename to go/cmd/dolt/commands/sqlserver/queryist_utils.go index 66d23c39c5..cc620e960b 100644 --- a/go/cmd/dolt/queryist_utils.go +++ b/go/cmd/dolt/commands/sqlserver/queryist_utils.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package main +package sqlserver import ( "context" @@ -21,7 +21,6 @@ import ( "io" "github.com/dolthub/dolt/go/cmd/dolt/cli" - "github.com/dolthub/dolt/go/cmd/dolt/commands/sqlserver" "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/libraries/utils/filesys" @@ -35,14 +34,14 @@ import ( // BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this // module isn't ideal, but it's the only way to get the server config into the queryist. func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults, host string, port int, useTLS bool, dbRev string) (cli.LateBindQueryist, error) { - clientConfig, err := sqlserver.GetClientConfig(cwdFS, creds, apr) + clientConfig, err := GetClientConfig(cwdFS, creds, apr) if err != nil { return nil, err } // ParseDSN currently doesn't support `/` in the db name dbName, _ := dsess.SplitRevisionDbName(dbRev) - parsedMySQLConfig, err := mysql.ParseDSN(sqlserver.ConnectionString(clientConfig, dbName)) + parsedMySQLConfig, err := mysql.ParseDSN(ConnectionString(clientConfig, dbName)) if err != nil { return nil, err } diff --git a/go/cmd/dolt/dolt.go b/go/cmd/dolt/dolt.go index 1e2655f053..a170747660 100644 --- a/go/cmd/dolt/dolt.go +++ b/go/cmd/dolt/dolt.go @@ -654,7 +654,7 @@ If you're interested in running this command against a remote host, hit us up on port = 3306 } useTLS := !apr.Contains(cli.NoTLSFlag) - return BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, host, port, useTLS, useDb) + return sqlserver.BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, host, port, useTLS, useDb) } else { _, hasPort := apr.GetInt(cli.PortFlag) if hasPort { @@ -708,7 +708,7 @@ If you're interested in running this command against a remote host, hit us up on if !creds.Specified { creds = &cli.UserPassword{Username: sqlserver.LocalConnectionUser, Password: lock.Secret, Specified: false} } - return BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, "localhost", lock.Port, false, useDb) + return sqlserver.BuildConnectionStringQueryist(ctx, cwdFS, creds, apr, "localhost", lock.Port, false, useDb) } if verbose { From d135c4b95d219878e9433c58c746d719b51062fd Mon Sep 17 00:00:00 2001 From: stephkyou Date: Thu, 30 Nov 2023 23:45:28 +0000 Subject: [PATCH 5/5] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/cmd/dolt/commands/sqlserver/queryist_utils.go | 9 +++++---- go/cmd/dolt/commands/sqlserver/sqlserver.go | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/go/cmd/dolt/commands/sqlserver/queryist_utils.go b/go/cmd/dolt/commands/sqlserver/queryist_utils.go index cc620e960b..c552735344 100644 --- a/go/cmd/dolt/commands/sqlserver/queryist_utils.go +++ b/go/cmd/dolt/commands/sqlserver/queryist_utils.go @@ -20,15 +20,16 @@ import ( "fmt" "io" - "github.com/dolthub/dolt/go/cmd/dolt/cli" - "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" - "github.com/dolthub/dolt/go/libraries/utils/argparser" - "github.com/dolthub/dolt/go/libraries/utils/filesys" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" "github.com/go-sql-driver/mysql" "github.com/gocraft/dbr/v2" "github.com/gocraft/dbr/v2/dialect" + + "github.com/dolthub/dolt/go/cmd/dolt/cli" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" + "github.com/dolthub/dolt/go/libraries/utils/argparser" + "github.com/dolthub/dolt/go/libraries/utils/filesys" ) // BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this diff --git a/go/cmd/dolt/commands/sqlserver/sqlserver.go b/go/cmd/dolt/commands/sqlserver/sqlserver.go index 55382499cb..609015d468 100644 --- a/go/cmd/dolt/commands/sqlserver/sqlserver.go +++ b/go/cmd/dolt/commands/sqlserver/sqlserver.go @@ -21,6 +21,9 @@ import ( "strconv" "strings" + "github.com/dolthub/go-mysql-server/sql" + "github.com/fatih/color" + "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" @@ -28,8 +31,6 @@ import ( "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/libraries/utils/filesys" "github.com/dolthub/dolt/go/libraries/utils/svcs" - "github.com/dolthub/go-mysql-server/sql" - "github.com/fatih/color" ) const (