Merged and commands

This commit is contained in:
Daylon Wilkins
2019-07-15 15:26:08 -07:00
parent 314f6cfaf7
commit d8343e0bff
7 changed files with 107 additions and 860 deletions
+45 -44
View File
@@ -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))
}
-283
View File
@@ -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
}
-492
View File
@@ -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
}
-2
View File
@@ -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},
+1 -34
View File
@@ -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 {
+23 -5
View File
@@ -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
}
}
}
}