Almost working table / row iterators

This commit is contained in:
Zach Musgrave
2019-06-20 14:13:31 -07:00
parent 81a9983096
commit e6e38ddb38
3 changed files with 618 additions and 7 deletions
+484
View File
@@ -0,0 +1,484 @@
package sqle
import (
"context"
"github.com/attic-labs/noms/go/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},
}
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},
}
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},
}
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},
}
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")},
},
}
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,
},
}
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"),
},
},
}
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
}
+89 -6
View File
@@ -2,9 +2,13 @@ package sqle
import (
"context"
"github.com/attic-labs/noms/go/types"
"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/schema"
"github.com/src-d/go-mysql-server/sql"
"io"
)
type Database struct {
@@ -13,8 +17,9 @@ type Database struct {
}
type DoltTable struct {
name string
name string
table *doltdb.Table
sch schema.Schema
}
func (t *DoltTable) Name() string {
@@ -30,12 +35,90 @@ func (t *DoltTable) Schema() sql.Schema {
return doltSchemaToSqlSchema(t.name, schema)
}
func (*DoltTable) Partitions(*sql.Context) (sql.PartitionIter, error) {
panic("implement me")
type doltTablePartitionIter struct {
table *DoltTable
i int
}
func (*DoltTable) PartitionRows(*sql.Context, sql.Partition) (sql.RowIter, error) {
panic("implement me")
func (itr *doltTablePartitionIter) Close() error {
return nil
}
func (itr *doltTablePartitionIter) Next() (sql.Partition, error) {
if itr.i > 0 {
return nil, io.EOF
}
itr.i++
return &doltTablePartition{itr.table}, nil
}
type doltTablePartition struct {
table *DoltTable
}
func (p doltTablePartition) Key() []byte {
return []byte(p.table.name)
}
// Returns the partitions for this table. We return a single partition, but could potentially get more performance by
// returning multiple.
func (t *DoltTable) Partitions(*sql.Context) (sql.PartitionIter, error) {
return &doltTablePartitionIter{table: t}, nil
}
// Returns the table rows for the partition given.
func (t *DoltTable) PartitionRows(ctx *sql.Context, p sql.Partition) (sql.RowIter, error) {
return newRowIterator(t, ctx), nil
}
type doltTableRowIter struct {
table *DoltTable
rowData types.Map
ctx *sql.Context
nomsIter types.MapIterator
}
func newRowIterator(tbl *DoltTable, ctx *sql.Context) *doltTableRowIter {
rowData := tbl.table.GetRowData(ctx.Context)
mapIter := rowData.Iterator(ctx.Context)
return &doltTableRowIter{table: tbl, rowData: rowData, ctx: ctx, nomsIter: mapIter}
}
func (itr *doltTableRowIter) Next() (sql.Row, error) {
key, val := itr.nomsIter.Next(itr.ctx.Context)
if key == nil && val == nil {
return nil, io.EOF
}
doltRow := row.FromNoms(itr.table.sch, key.(types.Tuple), val.(types.Tuple))
return doltRowToSqlRow(doltRow, itr.table.sch), nil
}
func doltRowToSqlRow(doltRow row.Row, sch schema.Schema) sql.Row {
colVals := make(sql.Row, sch.GetAllCols().Size())
i := 0
sch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool) {
value, _:= doltRow.GetColVal(tag)
colVals[i] = doltColValToSqlColVal(value)
i++
return false
})
return sql.NewRow(colVals)
}
func doltColValToSqlColVal(val types.Value) interface{} {
if types.IsNull(val) {
return nil
}
return nomsValToSqlVal(val)
}
func (itr *doltTableRowIter) Close() error {
return nil
}
func NewDatabase(name string, dEnv *env.DoltEnv) sql.Database {
@@ -63,7 +146,7 @@ func (db *Database) Tables() map[string]sql.Table {
if !ok {
panic("Error loading table " + name)
}
tables[name] = &DoltTable{name: name, table: table}
tables[name] = &DoltTable{name: name, table: table, sch: table.GetSchema(context.TODO())}
}
return tables
+45 -1
View File
@@ -15,7 +15,8 @@ func nomsTypeToSqlType(kind types.NomsKind) sql.Type {
case types.StringKind:
return sql.Text
case types.UUIDKind:
panic("TODO")
// TODO: make an actual uuid
return sql.Text
case types.IntKind:
return sql.Int64
case types.UintKind:
@@ -24,3 +25,46 @@ func nomsTypeToSqlType(kind types.NomsKind) sql.Type {
panic(fmt.Sprintf("Unexpected kind %v", kind))
}
}
func nomsValToSqlVal(val types.Value) interface{} {
switch val.Kind() {
case types.BoolKind:
return convertBool(val.(types.Bool))
case types.FloatKind:
return convertFloat(val.(types.Float))
case types.StringKind:
return convertString(val.(types.String))
case types.UUIDKind:
return convertUUID(val.(types.UUID))
case types.IntKind:
return convertInt(val.(types.Int))
case types.UintKind:
return convertUint(val.(types.Uint))
default:
panic(fmt.Sprintf("Unexpected kind %v", val.Kind()))
}
}
func convertUUID(u types.UUID) interface{} {
return u.String()
}
func convertUint(i types.Uint) interface{} {
return uint64(i)
}
func convertInt(i types.Int) interface{} {
return int64(i)
}
func convertString(i types.String) interface{} {
return string(i)
}
func convertFloat(f types.Float) interface{} {
return float64(f)
}
func convertBool(b types.Bool) interface{} {
return bool(b)
}