mirror of
https://github.com/dolthub/dolt.git
synced 2026-05-06 19:35:18 -05:00
Merged and commands
This commit is contained in:
+45
-44
@@ -13,9 +13,9 @@ import (
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/rowconv"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
|
||||
dsql "github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/sql"
|
||||
dsqle "github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/sqle"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/pipeline"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/fwt"
|
||||
@@ -23,6 +23,10 @@ import (
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/tabular"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
|
||||
"github.com/liquidata-inc/ld/dolt/go/store/types"
|
||||
sqle "github.com/src-d/go-mysql-server"
|
||||
"github.com/src-d/go-mysql-server/sql"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"vitess.io/vitess/go/vt/sqlparser"
|
||||
@@ -270,9 +274,17 @@ func processQuery(query string, dEnv *env.DoltEnv, root *doltdb.RootValue) (*dol
|
||||
|
||||
switch s := sqlStatement.(type) {
|
||||
case *sqlparser.Show:
|
||||
return nil, sqlShow(root, s)
|
||||
sqlSch, rowIter, err := sqlNewEngine(query, root)
|
||||
if err == nil {
|
||||
err = prettyPrintResults(sqlSch, rowIter)
|
||||
}
|
||||
return nil, err
|
||||
case *sqlparser.Select:
|
||||
return nil, sqlSelect(root, s)
|
||||
sqlSch, rowIter, err := sqlNewEngine(query, root)
|
||||
if err == nil {
|
||||
err = prettyPrintResults(sqlSch, rowIter)
|
||||
}
|
||||
return nil, err
|
||||
case *sqlparser.Insert:
|
||||
return sqlInsert(dEnv, root, s, query)
|
||||
case *sqlparser.Update:
|
||||
@@ -290,38 +302,38 @@ func processQuery(query string, dEnv *env.DoltEnv, root *doltdb.RootValue) (*dol
|
||||
}
|
||||
}
|
||||
|
||||
// Executes a SQL show statement and prints the result to the CLI.
|
||||
func sqlShow(root *doltdb.RootValue, show *sqlparser.Show) error {
|
||||
p, sch, err := dsql.BuildShowPipeline(context.TODO(), root, show)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runPrintingPipeline(p, sch)
|
||||
// Executes a SQL statement of either SHOW or SELECT and returns values for printing if applicable.
|
||||
func sqlNewEngine(query string, root *doltdb.RootValue) (sql.Schema, sql.RowIter, error) {
|
||||
db := dsqle.NewDatabase("dolt", root)
|
||||
engine := sqle.NewDefault()
|
||||
engine.AddDatabase(db)
|
||||
ctx := sql.NewEmptyContext()
|
||||
return engine.Query(ctx, query)
|
||||
}
|
||||
|
||||
// Executes a SQL select statement and prints the result to the CLI.
|
||||
func sqlSelect(root *doltdb.RootValue, s *sqlparser.Select) error {
|
||||
// Pretty prints the output of the new SQL engine
|
||||
func prettyPrintResults(sqlSch sql.Schema, rowIter sql.RowIter) error {
|
||||
var chanErr error
|
||||
doltSch := dsqle.SqlSchemaToDoltSchema(sqlSch)
|
||||
untypedSch := untyped.UntypeUnkeySchema(doltSch)
|
||||
|
||||
p, statement, err := dsql.BuildSelectQueryPipeline(context.TODO(), root, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowChannel := make(chan row.Row)
|
||||
p := pipeline.NewPartialPipeline(pipeline.InFuncForChannel(rowChannel))
|
||||
|
||||
// Now that we have the output schema, we add three additional steps to the pipeline:
|
||||
// 1) Coerce all the values in each row into strings
|
||||
// 2) Convert null values to printed values
|
||||
// 3) Run them through a fixed width transformer to make them print pretty
|
||||
resultSchema := statement.ResultSetSchema
|
||||
untypedSch, untypingTransform := newUntypingTransformer(resultSchema)
|
||||
p.AddStage(untypingTransform)
|
||||
go func() {
|
||||
defer close(rowChannel)
|
||||
var sqlRow sql.Row
|
||||
for sqlRow, chanErr = rowIter.Next(); chanErr == nil; sqlRow, chanErr = rowIter.Next() {
|
||||
taggedVals := make(row.TaggedValues)
|
||||
for i, col := range sqlRow {
|
||||
if col != nil {
|
||||
taggedVals[uint64(i)] = types.String(fmt.Sprintf("%v", col))
|
||||
}
|
||||
}
|
||||
rowChannel <- row.New(untypedSch, taggedVals)
|
||||
}
|
||||
}()
|
||||
|
||||
return runPrintingPipeline(p, untypedSch)
|
||||
}
|
||||
|
||||
// Adds some print-handling stages to the pipeline given and runs it, returning any error.
|
||||
// Adds null-printing and fixed-width transformers. The schema given is assumed to be untyped (string-typed).
|
||||
func runPrintingPipeline(p *pipeline.Pipeline, untypedSch schema.Schema) error {
|
||||
nullPrinter := nullprinter.NewNullPrinter(untypedSch)
|
||||
p.AddStage(pipeline.NewNamedTransform(nullprinter.NULL_PRINTING_STAGE, nullPrinter.ProcessRow))
|
||||
|
||||
@@ -349,6 +361,9 @@ func runPrintingPipeline(p *pipeline.Pipeline, untypedSch schema.Schema) error {
|
||||
if err := p.Wait(); err != nil {
|
||||
return errFmt("error processing results: %v", err)
|
||||
}
|
||||
if chanErr != io.EOF {
|
||||
return errFmt("error processing results: %v", chanErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -429,17 +444,3 @@ func sqlDDL(dEnv *env.DoltEnv, root *doltdb.RootValue, ddl *sqlparser.DDL, query
|
||||
func errFmt(fmtMsg string, args ...interface{}) error {
|
||||
return errors.New(fmt.Sprintf(fmtMsg, args...))
|
||||
}
|
||||
|
||||
// Returns a new untyping transformer for the schema given.
|
||||
// TODO: move this somewhere more appropriate. Import cycles currently make this difficult.
|
||||
func newUntypingTransformer(sch schema.Schema) (schema.Schema, pipeline.NamedTransform) {
|
||||
untypedSch := untyped.UntypeUnkeySchema(sch)
|
||||
mapping, err := rowconv.TagMapping(sch, untypedSch)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rConv, _ := rowconv.NewRowConverter(mapping)
|
||||
return untypedSch, pipeline.NewNamedTransform("untype", rowconv.GetRowConvTransformFunc(rConv))
|
||||
}
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
package sqle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/abiosoft/readline"
|
||||
"github.com/fatih/color"
|
||||
"github.com/flynn-archive/go-shlex"
|
||||
"github.com/liquidata-inc/ishell"
|
||||
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
|
||||
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands"
|
||||
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/schema"
|
||||
dsql "github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/sql"
|
||||
dsqle "github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/sqle"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
|
||||
sqle "github.com/src-d/go-mysql-server"
|
||||
"github.com/src-d/go-mysql-server/sql"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var sqlShortDesc = "Runs a SQL query"
|
||||
var sqlLongDesc = `Runs a SQL query you specify. By default, begins an interactive shell to run queries and view the
|
||||
results. With the -q option, runs the given query and prints any results, then exits.
|
||||
|
||||
THIS FUNCTIONALITY IS EXPERIMENTAL and being intensively developed. Feedback is welcome:
|
||||
dolt-interest@liquidata.co
|
||||
|
||||
Reasonably well supported functionality:
|
||||
* SELECT statements, including most kinds of joins
|
||||
* CREATE TABLE statements
|
||||
* ALTER TABLE / DROP TABLE statements
|
||||
* UPDATE and DELETE statements
|
||||
* Table and column aliases
|
||||
* ORDER BY and LIMIT clauses
|
||||
|
||||
Known limitations:
|
||||
* Some expressions in SELECT statements
|
||||
* GROUP BY or aggregate functions
|
||||
* Subqueries
|
||||
* Column functions, e.g. CONCAT
|
||||
* Non-primary indexes
|
||||
* Foreign keys
|
||||
* Column constraints besides NOT NULL
|
||||
* VARCHAR columns are unlimited length; FLOAT, INTEGER columns are 64 bit
|
||||
* Performance is very bad for many SELECT statements, especially JOINs
|
||||
`
|
||||
var sqlSynopsis = []string{
|
||||
"",
|
||||
"-q <query>",
|
||||
}
|
||||
|
||||
const (
|
||||
queryFlag = "query"
|
||||
welcomeMsg = `# Welcome to the DoltSQL shell.
|
||||
# Statements must be terminated with ';'.
|
||||
# "exit" or "quit" (or Ctrl-D) to exit.`
|
||||
)
|
||||
|
||||
func Sql(commandStr string, args []string, dEnv *env.DoltEnv) int {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsString(queryFlag, "q", "SQL query to run", "Runs a single query and exits")
|
||||
help, usage := cli.HelpAndUsagePrinters(commandStr, sqlShortDesc, sqlLongDesc, sqlSynopsis, ap)
|
||||
|
||||
apr := cli.ParseArgs(ap, args, help)
|
||||
args = apr.Args()
|
||||
|
||||
root, verr := commands.GetWorkingWithVErr(dEnv)
|
||||
if verr != nil {
|
||||
return commands.HandleVErrAndExitCode(verr, usage)
|
||||
}
|
||||
|
||||
// run a single command and exit
|
||||
if query, ok := apr.GetValue(queryFlag); ok {
|
||||
if newRoot, err := processQuery(query, dEnv, root); err != nil {
|
||||
return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
} else if newRoot != nil {
|
||||
return commands.HandleVErrAndExitCode(commands.UpdateWorkingWithVErr(dEnv, newRoot), usage)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// start an interactive shell
|
||||
root = runShell(dEnv, root)
|
||||
|
||||
// If the SQL session wrote a new root value, update the working set with it
|
||||
if root != nil {
|
||||
return commands.HandleVErrAndExitCode(commands.UpdateWorkingWithVErr(dEnv, root), usage)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// runShell starts a SQL shell. Returns when the user exits the shell with the root value resulting from any queries.
|
||||
func runShell(dEnv *env.DoltEnv, root *doltdb.RootValue) *doltdb.RootValue {
|
||||
_ = iohelp.WriteLine(cli.CliOut, welcomeMsg)
|
||||
|
||||
// start the doltsql shell
|
||||
historyFile := filepath.Join(dEnv.GetDoltDir(), ".sqlhistory")
|
||||
rlConf := readline.Config{
|
||||
Prompt: "doltsql> ",
|
||||
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: ";",
|
||||
}
|
||||
|
||||
shell := ishell.NewUninterpreted(&shellConf)
|
||||
shell.SetMultiPrompt( " -> ")
|
||||
// TODO: update completer on create / drop / alter statements
|
||||
shell.CustomCompleter(newCompleter(dEnv))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if newRoot, err := processQuery(query, dEnv, root); err != nil {
|
||||
shell.Println(color.RedString(err.Error()))
|
||||
} else if newRoot != nil {
|
||||
root = newRoot
|
||||
}
|
||||
|
||||
// TODO: there's a bug in the readline library when editing multi-line history entries.
|
||||
// Longer term we need to switch to a new readline library, like in this bug:
|
||||
// https://github.com/cockroachdb/cockroach/issues/15460
|
||||
// For now, we store all history entries as single-line strings to avoid the issue.
|
||||
// TODO: only store history if it's a tty
|
||||
singleLine := strings.ReplaceAll(query, "\n", " ")
|
||||
if err := shell.AddHistory(singleLine); err != nil {
|
||||
// TODO: handle better, like by turning off history writing for the rest of the session
|
||||
shell.Println(color.RedString(err.Error()))
|
||||
}
|
||||
})
|
||||
|
||||
shell.Run()
|
||||
_ = iohelp.WriteLine(cli.CliOut, "Bye")
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
// Returns a new auto completer with table names, column names, and SQL keywords.
|
||||
func newCompleter(dEnv *env.DoltEnv) *sqlCompleter {
|
||||
var completionWords []string
|
||||
|
||||
root, err := dEnv.WorkingRoot(context.TODO())
|
||||
if err != nil {
|
||||
return &sqlCompleter{}
|
||||
}
|
||||
|
||||
tableNames := root.GetTableNames(context.TODO())
|
||||
completionWords = append(completionWords, tableNames...)
|
||||
var columnNames []string
|
||||
for _, tableName := range tableNames {
|
||||
tbl, _ := root.GetTable(context.TODO(), tableName)
|
||||
sch := tbl.GetSchema(context.TODO())
|
||||
sch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool) {
|
||||
completionWords = append(completionWords, col.Name)
|
||||
columnNames = append(columnNames, col.Name)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
completionWords = append(completionWords, dsql.CommonKeywords...)
|
||||
|
||||
return &sqlCompleter{
|
||||
allWords: completionWords,
|
||||
columnNames: columnNames,
|
||||
}
|
||||
}
|
||||
|
||||
type sqlCompleter struct {
|
||||
allWords []string
|
||||
columnNames []string
|
||||
}
|
||||
|
||||
// Do function for autocompletion, defined by the Readline library. Mostly stolen from ishell.
|
||||
func (c *sqlCompleter) Do(line []rune, pos int) (newLine [][]rune, length int) {
|
||||
var words []string
|
||||
if w, err := shlex.Split(string(line)); err == nil {
|
||||
words = w
|
||||
} else {
|
||||
// fall back
|
||||
words = strings.Fields(string(line))
|
||||
}
|
||||
|
||||
var cWords []string
|
||||
prefix := ""
|
||||
lastWord := ""
|
||||
if len(words) > 0 && pos > 0 && line[pos-1] != ' ' {
|
||||
lastWord = words[len(words)-1]
|
||||
prefix = strings.ToLower(lastWord)
|
||||
} else if len(words) > 0 {
|
||||
lastWord = words[len(words)-1]
|
||||
}
|
||||
|
||||
cWords = c.getWords(lastWord)
|
||||
|
||||
var suggestions [][]rune
|
||||
for _, w := range cWords {
|
||||
lowered := strings.ToLower(w)
|
||||
if strings.HasPrefix(lowered, prefix) {
|
||||
suggestions = append(suggestions, []rune(strings.TrimPrefix(lowered, prefix)))
|
||||
}
|
||||
}
|
||||
if len(suggestions) == 1 && prefix != "" && string(suggestions[0]) == "" {
|
||||
suggestions = [][]rune{[]rune(" ")}
|
||||
}
|
||||
|
||||
return suggestions, len(prefix)
|
||||
}
|
||||
|
||||
// Simple suggestion function. Returns column name suggestions if the last word in the input has exactly one '.' in it,
|
||||
// otherwise returns all tables, columns, and reserved words.
|
||||
func (c *sqlCompleter) getWords(lastWord string) (s []string) {
|
||||
lastDot := strings.LastIndex(lastWord, ".")
|
||||
if lastDot > 0 && strings.Count(lastWord, ".") == 1 {
|
||||
alias := lastWord[:lastDot]
|
||||
return prepend(alias + ".", c.columnNames)
|
||||
}
|
||||
|
||||
return c.allWords
|
||||
}
|
||||
|
||||
func prepend(s string, ss []string) []string {
|
||||
newSs := make([]string, len(ss))
|
||||
for i := range ss {
|
||||
newSs[i] = s + ss[i]
|
||||
}
|
||||
return newSs
|
||||
}
|
||||
|
||||
// Processes a single query and returns the new root value of the DB, or an error encountered.
|
||||
func processQuery(query string, dEnv *env.DoltEnv, root *doltdb.RootValue) (*doltdb.RootValue, error) {
|
||||
db := dsqle.NewDatabase("dolt", root)
|
||||
engine := sqle.NewDefault()
|
||||
engine.AddDatabase(db)
|
||||
ctx := sql.NewEmptyContext()
|
||||
|
||||
var err error
|
||||
_, iter, err := engine.Query(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var r sql.Row
|
||||
for r, err = iter.Next(); err == nil; r, err = iter.Next() {
|
||||
// TODO: make this print pretty tables like original sql commands
|
||||
cli.Println(r)
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
@@ -1,492 +0,0 @@
|
||||
package sqle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/liquidata-inc/ld/dolt/go/store/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/dtestutils"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table"
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/typed/noms"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
//var UUIDS = []uuid.UUID{
|
||||
// uuid.Must(uuid.Parse("00000000-0000-0000-0000-000000000000")),
|
||||
// uuid.Must(uuid.Parse("00000000-0000-0000-0000-000000000001")),
|
||||
// uuid.Must(uuid.Parse("00000000-0000-0000-0000-000000000002"))}
|
||||
//var Names = []string{"Bill Billerson", "John Johnson", "Rob Robertson"}
|
||||
//var Ages = []uint64{32, 25, 21}
|
||||
//var Titles = []string{"Senior Dufus", "Dufus", ""}
|
||||
//var MaritalStatus = []bool{true, false, false}
|
||||
|
||||
var tableName = "people"
|
||||
|
||||
// Smoke tests, values are printed to console
|
||||
func TestSqlSelect(t *testing.T) {
|
||||
tests := []struct {
|
||||
query string
|
||||
expectedRes int
|
||||
}{
|
||||
// {"select * from doesnt_exist where age = 32", 1},
|
||||
{"select * from people", 0},
|
||||
{"select * from people where age = 32", 0},
|
||||
{"select * from people where title = 'Senior Dufus'", 0},
|
||||
{"select * from people where name = 'Bill Billerson'", 0},
|
||||
{"select * from people where name = 'John Johnson'", 0},
|
||||
{"select * from people where age = 25", 0},
|
||||
{"select * from people where 25 = age", 0},
|
||||
{"select * from people where is_married = false", 0},
|
||||
{"select * from people where age < 30", 0},
|
||||
{"select * from people where age > 24", 0},
|
||||
{"select * from people where age >= 25", 0},
|
||||
{"select * from people where name <= 'John Johnson'", 0},
|
||||
{"select * from people where name <> 'John Johnson'", 0},
|
||||
{"select age, is_married from people where name <> 'John Johnson'", 0},
|
||||
{"select age, is_married from people where name <> 'John Johnson' limit 1", 0},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Smoke tests, values are printed to console
|
||||
func TestSqlShow(t *testing.T) {
|
||||
tests := []struct {
|
||||
query string
|
||||
expectedRes int
|
||||
}{
|
||||
{"show tables", 0},
|
||||
{"show create table people", 0},
|
||||
{"show all tables", 1},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expectedRes int
|
||||
}{
|
||||
{"no query", []string{"-q", ""}, 1},
|
||||
}
|
||||
t.Skip("Skipping broken test")
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, test.args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests of the create table SQL command, mostly a smoke test for errors in the command line handler. Most tests of
|
||||
// create table SQL command are in the sql package.
|
||||
func TestCreateTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
query string
|
||||
expectedRes int
|
||||
}{
|
||||
{"create table people (id int)", 1}, // no primary key
|
||||
{"create table", 1}, // bad syntax
|
||||
{"create table (id int ", 1}, // bad syntax
|
||||
{"create table people (id int primary key)", 0},
|
||||
{"create table people (id int primary key, age int)", 0},
|
||||
{"create table people (id int primary key, age int, first varchar(80), is_married bit)", 0},
|
||||
{"create table people (`id` int, `age` int, `first` varchar(80), `last` varchar(80), `title` varchar(80), `is_married` bit, primary key (`id`, `age`))", 0},
|
||||
}
|
||||
|
||||
t.Skip("Skipping broken test")
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
dEnv := dtestutils.CreateTestEnv()
|
||||
working, err := dEnv.WorkingRoot(context.Background())
|
||||
assert.Nil(t, err, "Unexpected error")
|
||||
assert.False(t, working.HasTable(context.Background(), tableName), "table exists before creating it")
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
|
||||
working, err = dEnv.WorkingRoot(context.Background())
|
||||
assert.Nil(t, err, "Unexpected error")
|
||||
if test.expectedRes == 0 {
|
||||
assert.True(t, working.HasTable(context.Background(), tableName), "table doesn't exist after creating it")
|
||||
} else {
|
||||
assert.False(t, working.HasTable(context.Background(), tableName), "table shouldn't exist after error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests of the create table SQL command, mostly a smoke test for errors in the command line handler. Most tests of
|
||||
// create table SQL command are in the sql package.
|
||||
func TestShowTables(t *testing.T) {
|
||||
tests := []struct {
|
||||
query string
|
||||
expectedRes int
|
||||
}{
|
||||
{"show ", 1}, // bad syntax
|
||||
{"show table", 1}, // bad syntax
|
||||
{"show tables", 0},
|
||||
{"show create table people", 0},
|
||||
{"show create table dne", 1},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Tests of the alter table SQL command, mostly a smoke test for errors in the command line handler. Most tests of
|
||||
// create table SQL command are in the sql package.
|
||||
func TestAlterTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
query string
|
||||
expectedRes int
|
||||
}{
|
||||
{"alter table", 1}, // bad syntax
|
||||
{"alter table people rename", 1}, // bad syntax
|
||||
{"alter table dne rename id to newId", 1}, // unknown column
|
||||
{"alter table people rename id to newId", 0}, // no primary key
|
||||
{"alter table people rename to newPeople", 0},
|
||||
{"rename table people to newPeople", 0},
|
||||
{"alter table people add column (newCol int not null default 10)", 0},
|
||||
{"alter table people drop column title", 0},
|
||||
}
|
||||
|
||||
t.Skip("Skipping broken test")
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests of the drop table SQL command, mostly a smoke test for errors in the command line handler. Most tests of
|
||||
// create table SQL command are in the sql package.
|
||||
func TestDropTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
query string
|
||||
expectedRes int
|
||||
}{
|
||||
{"drop table", 1},
|
||||
{"drop table people", 0},
|
||||
{"drop table dne", 1},
|
||||
{"drop table if exists dne", 0},
|
||||
}
|
||||
|
||||
t.Skip("Skipping broken test")
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests of the insert SQL command, mostly a smoke test for errors in the command line handler. Most tests of
|
||||
// insert SQL command are in the sql package.
|
||||
func TestInsert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
expectedRes int
|
||||
expectedIds []uuid.UUID
|
||||
}{
|
||||
{
|
||||
name: "no primary key",
|
||||
query: "insert into people (title) values ('hello')",
|
||||
expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "bad syntax",
|
||||
query: "insert into table", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "bad syntax",
|
||||
query: "insert into people (id) values", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "table doesn't exist",
|
||||
query: "insert into dne (id) values (00000000-0000-0000-0000-000000000005)", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "insert one row",
|
||||
query: `insert into people (id, name, age, is_married) values
|
||||
('00000000-0000-0000-0000-000000000005', 'Frank Frankerson', 10, false)`,
|
||||
expectedIds: []uuid.UUID{uuid.MustParse("00000000-0000-0000-0000-000000000005")},
|
||||
},
|
||||
{
|
||||
name: "insert one row all columns",
|
||||
query: `insert into people (id, name, age, is_married, title) values
|
||||
('00000000-0000-0000-0000-000000000005', 'Frank Frankerson', 10, false, 'Goon')`,
|
||||
expectedIds: []uuid.UUID{uuid.MustParse("00000000-0000-0000-0000-000000000005")},
|
||||
},
|
||||
{
|
||||
name: "insert two rows all columns",
|
||||
query: `insert into people (id, name, age, is_married, title) values
|
||||
('00000000-0000-0000-0000-000000000005', 'Frank Frankerson', 10, false, 'Goon'),
|
||||
('00000000-0000-0000-0000-000000000006', 'Kobe Buffalomeat', 30, false, 'Linebacker')`,
|
||||
expectedIds: []uuid.UUID{
|
||||
uuid.MustParse("00000000-0000-0000-0000-000000000005"),
|
||||
uuid.MustParse("00000000-0000-0000-0000-000000000006"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing required column",
|
||||
query: `insert into people (id, name, age) values
|
||||
('00000000-0000-0000-0000-000000000005', 'Frank Frankerson', 10)`,
|
||||
expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "existing primary key",
|
||||
query: `insert into people (id, name, age, is_married, title) values
|
||||
('00000000-0000-0000-0000-000000000000', 'Frank Frankerson', 10, false, 'Goon')`,
|
||||
expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "insert ignore",
|
||||
query: `insert ignore into people (id, name, age, is_married, title) values
|
||||
('00000000-0000-0000-0000-000000000000', 'Frank Frankerson', 10, false, 'Goon')`,
|
||||
expectedIds: []uuid.UUID{uuid.MustParse("00000000-0000-0000-0000-000000000000")},
|
||||
},
|
||||
}
|
||||
|
||||
t.Skip("Skipping broken test")
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
|
||||
if result == 0 {
|
||||
root, err := dEnv.WorkingRoot(context.Background())
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Assert that all expected IDs exist after the insert
|
||||
for _, expectedid := range test.expectedIds {
|
||||
table, _ := root.GetTable(context.Background(), tableName)
|
||||
taggedVals := row.TaggedValues{dtestutils.IdTag: types.UUID(expectedid)}
|
||||
key := taggedVals.NomsTupleForTags([]uint64{dtestutils.IdTag}, true)
|
||||
_, ok := table.GetRow(context.Background(), key.Value(context.Background()).(types.Tuple), dtestutils.TypedSchema)
|
||||
assert.True(t, ok, "expected id not found")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests of the update SQL command, mostly a smoke test for errors in the command line handler. Most tests of
|
||||
// update SQL command are in the sql package.
|
||||
func TestUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
expectedRes int
|
||||
expectedIds []uuid.UUID
|
||||
expectedAges []uint
|
||||
}{
|
||||
{
|
||||
name: "bad syntax",
|
||||
query: "update table", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "bad syntax",
|
||||
query: "update people set id", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "table doesn't exist",
|
||||
query: "update dne set id = '00000000-0000-0000-0000-000000000005'", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "update one row",
|
||||
query: `update people set age = 1 where id = '00000000-0000-0000-0000-000000000002'`,
|
||||
expectedIds: []uuid.UUID{uuid.MustParse("00000000-0000-0000-0000-000000000002")},
|
||||
expectedAges: []uint{1},
|
||||
},
|
||||
{
|
||||
name: "insert two rows, two columns",
|
||||
query: `update people set age = 1, is_married = true where age > 21`,
|
||||
expectedIds: []uuid.UUID{
|
||||
uuid.MustParse("00000000-0000-0000-0000-000000000000"),
|
||||
uuid.MustParse("00000000-0000-0000-0000-000000000001"),
|
||||
},
|
||||
expectedAges: []uint{1, 1},
|
||||
},
|
||||
{
|
||||
name: "null constraint violation",
|
||||
query: `update people set name = null where id ='00000000-0000-0000-0000-000000000000'`,
|
||||
expectedRes: 1,
|
||||
},
|
||||
}
|
||||
|
||||
t.Skip("Skipping broken test")
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
|
||||
if result == 0 {
|
||||
root, err := dEnv.WorkingRoot(context.Background())
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Assert that all rows have been updated
|
||||
for i, expectedid := range test.expectedIds {
|
||||
table, _ := root.GetTable(context.Background(), tableName)
|
||||
taggedVals := row.TaggedValues{dtestutils.IdTag: types.UUID(expectedid)}
|
||||
key := taggedVals.NomsTupleForTags([]uint64{dtestutils.IdTag}, true)
|
||||
row, ok := table.GetRow(ctx, key.Value(ctx).(types.Tuple), dtestutils.TypedSchema)
|
||||
assert.True(t, ok, "expected id not found")
|
||||
ageVal, _ := row.GetColVal(dtestutils.AgeTag)
|
||||
assert.Equal(t, test.expectedAges[i], uint(ageVal.(types.Uint)))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests of the delete SQL command, mostly a smoke test for errors in the command line handler. Most tests of
|
||||
// delete SQL command are in the sql package.
|
||||
func TestDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
expectedRes int
|
||||
deletedIds []uuid.UUID
|
||||
}{
|
||||
{
|
||||
name: "bad syntax",
|
||||
query: "delete table", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "bad syntax",
|
||||
query: "delete from people where", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "table doesn't exist",
|
||||
query: "delete from dne", expectedRes: 1,
|
||||
},
|
||||
{
|
||||
name: "delete one row",
|
||||
query: `delete from people where id = '00000000-0000-0000-0000-000000000002'`,
|
||||
deletedIds: []uuid.UUID{uuid.MustParse("00000000-0000-0000-0000-000000000002")},
|
||||
},
|
||||
{
|
||||
name: "delete two rows",
|
||||
query: `delete from people where age > 21`,
|
||||
deletedIds: []uuid.UUID{
|
||||
uuid.MustParse("00000000-0000-0000-0000-000000000000"),
|
||||
uuid.MustParse("00000000-0000-0000-0000-000000000001"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Skip("Skipping broken test")
|
||||
for _, test := range tests {
|
||||
t.Run(test.query, func(t *testing.T) {
|
||||
dEnv := createEnvWithSeedData(t)
|
||||
ctx := context.Background()
|
||||
|
||||
args := []string{"-q", test.query}
|
||||
|
||||
commandStr := "dolt sqle"
|
||||
result := Sql(commandStr, args, dEnv)
|
||||
assert.Equal(t, test.expectedRes, result)
|
||||
|
||||
if result == 0 {
|
||||
root, err := dEnv.WorkingRoot(context.Background())
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Assert that all rows have been deleted
|
||||
for _, expectedid := range test.deletedIds {
|
||||
table, _ := root.GetTable(context.Background(), tableName)
|
||||
taggedVals := row.TaggedValues{dtestutils.IdTag: types.UUID(expectedid)}
|
||||
key := taggedVals.NomsTupleForTags([]uint64{dtestutils.IdTag}, true)
|
||||
_, ok := table.GetRow(ctx, key.Value(ctx).(types.Tuple), dtestutils.TypedSchema)
|
||||
assert.False(t, ok, "row not deleted")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createEnvWithSeedData(t *testing.T) *env.DoltEnv {
|
||||
dEnv := dtestutils.CreateTestEnv()
|
||||
imt, sch := dtestutils.CreateTestDataTable(true)
|
||||
|
||||
rd := table.NewInMemTableReader(imt)
|
||||
wr := noms.NewNomsMapCreator(context.Background(), dEnv.DoltDB.ValueReadWriter(), sch)
|
||||
|
||||
_, _, err := table.PipeRows(context.Background(), rd, wr, false)
|
||||
rd.Close(context.Background())
|
||||
wr.Close(context.Background())
|
||||
|
||||
if err != nil {
|
||||
t.Error("Failed to seed initial data", err)
|
||||
}
|
||||
|
||||
err = dEnv.PutTableToWorking(context.Background(), *wr.GetMap(), wr.GetSchema(), tableName)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Unable to put initial value of table in in mem noms db", err)
|
||||
}
|
||||
|
||||
return dEnv
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands/credcmds"
|
||||
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands/sqle"
|
||||
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands/sqlserver"
|
||||
"github.com/pkg/profile"
|
||||
"os"
|
||||
@@ -31,7 +30,6 @@ var doltCommand = cli.GenSubCommandHandler([]*cli.Command{
|
||||
{Name: "reset", Desc: "Remove table changes from the list of staged table changes.", Func: commands.Reset, ReqRepo: true},
|
||||
{Name: "commit", Desc: "Record changes to the repository.", Func: commands.Commit, ReqRepo: true},
|
||||
{Name: "sql", Desc: "Run a SQL query against tables in repository.", Func: commands.Sql, ReqRepo: true},
|
||||
{Name: "sqle", Desc: "Run a SQL query against tables in repository.", Func: sqle.Sql, ReqRepo: true, HideFromHelp: true},
|
||||
{Name: "sql-server", Desc: "Starts a MySQL-compatible server.", Func: sqlserver.SqlServer, ReqRepo: true},
|
||||
{Name: "log", Desc: "Show commit logs.", Func: commands.Log, ReqRepo: true},
|
||||
{Name: "diff", Desc: "Diff a table.", Func: commands.Diff, ReqRepo: true},
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/table/untyped/resultset"
|
||||
"github.com/liquidata-inc/ld/dolt/go/store/types"
|
||||
"strconv"
|
||||
"time"
|
||||
"vitess.io/vitess/go/vt/sqlparser"
|
||||
)
|
||||
|
||||
@@ -527,7 +526,7 @@ func createSelectPipeline(ctx context.Context, root *doltdb.RootValue, selectStm
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := pipeline.NewPartialPipeline(inFuncForChannel(cpChan))
|
||||
p := pipeline.NewPartialPipeline(pipeline.InFuncForChannel(cpChan))
|
||||
p.AddStage(pipeline.NewNamedTransform("where", createWhereFn(selectStmt)))
|
||||
if selectStmt.orderBy != nil {
|
||||
p.AddStage(pipeline.NamedTransform{Name: "order by", Func: newSortingTransform(selectStmt.orderBy.Less)})
|
||||
@@ -541,38 +540,6 @@ func createSelectPipeline(ctx context.Context, root *doltdb.RootValue, selectStm
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// inFuncForChannel returns an InFunc that reads off the channel given. Probably belongs in the pipeline package
|
||||
// eventually.
|
||||
func inFuncForChannel(cpChan <-chan row.Row) pipeline.InFunc {
|
||||
return func(p *pipeline.Pipeline, ch chan<- pipeline.RowWithProps, badRowChan chan<- *pipeline.TransformRowFailure, noMoreChan <-chan struct{}) {
|
||||
defer close(ch)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-noMoreChan:
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if p.IsStopping() {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case r, ok := <-cpChan:
|
||||
if ok {
|
||||
ch <- pipeline.RowWithProps{Row: r, Props: pipeline.NoProps}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
// wake up and check stop condition
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a transform function to limit the set of rows to the value specified by the statement. Must only be applied
|
||||
// if a limit > 0 is specified.
|
||||
func createLimitAndOffsetFn(statement *SelectStatement, p *pipeline.Pipeline) pipeline.TransformRowFunc {
|
||||
|
||||
@@ -70,8 +70,30 @@ func SqlValToNomsVal(val interface{}) types.Value {
|
||||
}
|
||||
|
||||
switch e := val.(type) {
|
||||
case bool:
|
||||
return types.Bool(e)
|
||||
case int:
|
||||
return types.Int(e)
|
||||
case int8:
|
||||
return types.Int(e)
|
||||
case int16:
|
||||
return types.Int(e)
|
||||
case int32:
|
||||
return types.Int(e)
|
||||
case int64:
|
||||
return types.Int(e)
|
||||
case uint:
|
||||
return types.Uint(e)
|
||||
case uint8:
|
||||
return types.Uint(e)
|
||||
case uint16:
|
||||
return types.Uint(e)
|
||||
case uint32:
|
||||
return types.Uint(e)
|
||||
case uint64:
|
||||
return types.Uint(e)
|
||||
case float32:
|
||||
return types.Float(e)
|
||||
case float64:
|
||||
return types.Float(e)
|
||||
case string:
|
||||
@@ -79,12 +101,8 @@ func SqlValToNomsVal(val interface{}) types.Value {
|
||||
return types.UUID(u)
|
||||
}
|
||||
return types.String(e)
|
||||
case uint64:
|
||||
return types.Uint(e)
|
||||
case bool:
|
||||
return types.Bool(e)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected type %v", val))
|
||||
panic(fmt.Sprintf("Unexpected type <%T> val <%v>", val, val))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/row"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InFuncForChannel returns an InFunc that reads off the channel given. Probably belongs in the pipeline package
|
||||
// eventually.
|
||||
func InFuncForChannel(cpChan <-chan row.Row) InFunc {
|
||||
return func(p *Pipeline, ch chan<- RowWithProps, badRowChan chan<- *TransformRowFailure, noMoreChan <-chan struct{}) {
|
||||
defer close(ch)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-noMoreChan:
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if p.IsStopping() {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case r, ok := <-cpChan:
|
||||
if ok {
|
||||
ch <- RowWithProps{Row: r, Props: NoProps}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
// wake up and check stop condition
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user