mirror of
https://github.com/dolthub/dolt.git
synced 2026-05-05 11:21:58 -05:00
grouped sql statment generation into a file, moved tests from sqlExportWriter to sql pkg
This commit is contained in:
@@ -16,69 +16,10 @@ package sql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
dtypes "github.com/liquidata-inc/dolt/go/libraries/doltcore/sqle/types"
|
||||
)
|
||||
|
||||
// SchemaAsCreateStmt takes a Schema and returns a string representing a SQL create table command that could be used to
|
||||
// create this table
|
||||
func SchemaAsCreateStmt(tableName string, sch schema.Schema) string {
|
||||
sb := &strings.Builder{}
|
||||
fmt.Fprintf(sb, "CREATE TABLE %s (\n", QuoteIdentifier(tableName))
|
||||
|
||||
firstLine := true
|
||||
sch.GetAllCols().IterInSortedOrder(func(tag uint64, col schema.Column) (stop bool) {
|
||||
if firstLine {
|
||||
firstLine = false
|
||||
} else {
|
||||
sb.WriteString(",\n")
|
||||
}
|
||||
|
||||
s := FmtCol(2, 0, 0, col)
|
||||
sb.WriteString(s)
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
firstPK := true
|
||||
err := sch.GetPKCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
|
||||
if firstPK {
|
||||
sb.WriteString(",\n PRIMARY KEY (")
|
||||
firstPK = false
|
||||
} else {
|
||||
sb.WriteRune(',')
|
||||
}
|
||||
sb.WriteString(QuoteIdentifier(col.Name))
|
||||
return false, nil
|
||||
})
|
||||
|
||||
// TODO: fix panics
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sb.WriteString(")\n);")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func TableDropStmt(tableName string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("DROP TABLE ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
b.WriteString(";")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func TableDropIfExistsStmt(tableName string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("DROP TABLE IF EXISTS ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
b.WriteString(";")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// FmtCol converts a column to a string with a given indent space count, name width, and type width. If nameWidth or
|
||||
// typeWidth are 0 or less than the length of the name or type, then the length of the name or type will be used
|
||||
func FmtCol(indent, nameWidth, typeWidth int, col schema.Column) string {
|
||||
@@ -109,11 +50,6 @@ func FmtColWithNameAndType(indent, nameWidth, typeWidth int, colName, typeStr st
|
||||
return colStr + fmt.Sprintf(" COMMENT 'tag:%d'", col.Tag)
|
||||
}
|
||||
|
||||
// Quotes the identifier given with backticks.
|
||||
func QuoteIdentifier(s string) string {
|
||||
return "`" + s + "`"
|
||||
}
|
||||
|
||||
// FmtColPrimaryKey creates a string representing a primary key constraint within a sql create table statement with a
|
||||
// given indent.
|
||||
func FmtColPrimaryKey(indent int, colStr string) string {
|
||||
|
||||
@@ -16,7 +16,6 @@ package sql
|
||||
|
||||
import (
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/sql/sqltestutil"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
|
||||
"testing"
|
||||
@@ -24,25 +23,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const expectedSQL = "CREATE TABLE `table_name` (\n" +
|
||||
" `id` BIGINT NOT NULL COMMENT 'tag:0',\n" +
|
||||
" `first` LONGTEXT NOT NULL COMMENT 'tag:1',\n" +
|
||||
" `last` LONGTEXT NOT NULL COMMENT 'tag:2',\n" +
|
||||
" `is_married` BOOLEAN COMMENT 'tag:3',\n" +
|
||||
" `age` BIGINT COMMENT 'tag:4',\n" +
|
||||
" `rating` DOUBLE COMMENT 'tag:6',\n" +
|
||||
" `uuid` LONGTEXT COMMENT 'tag:7',\n" +
|
||||
" `num_episodes` BIGINT UNSIGNED COMMENT 'tag:8',\n" +
|
||||
" PRIMARY KEY (`id`)\n" +
|
||||
");"
|
||||
|
||||
func TestSchemaAsCreateStmt(t *testing.T) {
|
||||
tSchema := sqltestutil.PeopleTestSchema
|
||||
str := SchemaAsCreateStmt("table_name", tSchema)
|
||||
|
||||
assert.Equal(t, expectedSQL, str)
|
||||
}
|
||||
|
||||
func TestFmtCol(t *testing.T) {
|
||||
tests := []struct {
|
||||
Col schema.Column
|
||||
|
||||
@@ -18,11 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/row"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"vitess.io/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
||||
@@ -143,39 +139,6 @@ func ExecuteDelete(ctx context.Context, db *doltdb.DoltDB, root *doltdb.RootValu
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func RowAsDeleteStmt(r row.Row, tableName string, tableSch schema.Schema) (string, error) {
|
||||
var b strings.Builder
|
||||
b.WriteString("DELETE FROM ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
|
||||
b.WriteString(" WHERE (")
|
||||
seenOne := false
|
||||
_, err := r.IterSchema(tableSch, func(tag uint64, val types.Value) (stop bool, err error) {
|
||||
col := tableSch.GetAllCols().TagToCol[tag]
|
||||
if col.IsPartOfPK {
|
||||
if seenOne {
|
||||
b.WriteString(" AND ")
|
||||
}
|
||||
sqlString, err := ValueAsSqlString(val)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
b.WriteString(QuoteIdentifier(col.Name))
|
||||
b.WriteRune('=')
|
||||
b.WriteString(sqlString)
|
||||
seenOne = true
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(");")
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func errDelete(errorFmt string, args ...interface{}) (*DeleteResult, error) {
|
||||
return nil, errors.New(fmt.Sprintf(errorFmt, args...))
|
||||
}
|
||||
|
||||
@@ -167,53 +167,6 @@ func ExecuteInsert(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RowAsInsertStmt(r row.Row, tableName string, tableSch schema.Schema) (string, error) {
|
||||
var b strings.Builder
|
||||
b.WriteString("INSERT INTO ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
b.WriteString(" ")
|
||||
|
||||
b.WriteString("(")
|
||||
seenOne := false
|
||||
err := tableSch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
|
||||
if seenOne {
|
||||
b.WriteRune(',')
|
||||
}
|
||||
b.WriteString(QuoteIdentifier(col.Name))
|
||||
seenOne = true
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(")")
|
||||
|
||||
b.WriteString(" VALUES (")
|
||||
seenOne = false
|
||||
_, err = r.IterSchema(tableSch, func(tag uint64, val types.Value) (stop bool, err error) {
|
||||
if seenOne {
|
||||
b.WriteRune(',')
|
||||
}
|
||||
sqlString, err := ValueAsSqlString(val)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
b.WriteString(sqlString)
|
||||
seenOne = true
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(");")
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// Returns a primary key summary of the row given
|
||||
func getPrimaryKeyString(r row.Row, tableSch schema.Schema) string {
|
||||
var sb strings.Builder
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
// Copyright 2019 Liquidata, 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 sql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/row"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
"strings"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
)
|
||||
|
||||
// SchemaAsCreateStmt takes a Schema and returns a string representing a SQL create table command that could be used to
|
||||
// create this table
|
||||
func SchemaAsCreateStmt(tableName string, sch schema.Schema) string {
|
||||
sb := &strings.Builder{}
|
||||
fmt.Fprintf(sb, "CREATE TABLE %s (\n", QuoteIdentifier(tableName))
|
||||
|
||||
firstLine := true
|
||||
sch.GetAllCols().IterInSortedOrder(func(tag uint64, col schema.Column) (stop bool) {
|
||||
if firstLine {
|
||||
firstLine = false
|
||||
} else {
|
||||
sb.WriteString(",\n")
|
||||
}
|
||||
|
||||
s := FmtCol(2, 0, 0, col)
|
||||
sb.WriteString(s)
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
firstPK := true
|
||||
err := sch.GetPKCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
|
||||
if firstPK {
|
||||
sb.WriteString(",\n PRIMARY KEY (")
|
||||
firstPK = false
|
||||
} else {
|
||||
sb.WriteRune(',')
|
||||
}
|
||||
sb.WriteString(QuoteIdentifier(col.Name))
|
||||
return false, nil
|
||||
})
|
||||
|
||||
// TODO: fix panics
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sb.WriteString(")\n);")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func TableDropStmt(tableName string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("DROP TABLE ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
b.WriteString(";")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func TableDropIfExistsStmt(tableName string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("DROP TABLE IF EXISTS ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
b.WriteString(";")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func RowAsInsertStmt(r row.Row, tableName string, tableSch schema.Schema) (string, error) {
|
||||
var b strings.Builder
|
||||
b.WriteString("INSERT INTO ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
b.WriteString(" ")
|
||||
|
||||
b.WriteString("(")
|
||||
seenOne := false
|
||||
err := tableSch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
|
||||
if seenOne {
|
||||
b.WriteRune(',')
|
||||
}
|
||||
b.WriteString(QuoteIdentifier(col.Name))
|
||||
seenOne = true
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(")")
|
||||
|
||||
b.WriteString(" VALUES (")
|
||||
seenOne = false
|
||||
_, err = r.IterSchema(tableSch, func(tag uint64, val types.Value) (stop bool, err error) {
|
||||
if seenOne {
|
||||
b.WriteRune(',')
|
||||
}
|
||||
sqlString, err := valueAsSqlString(val)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
b.WriteString(sqlString)
|
||||
seenOne = true
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(");")
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func RowAsDeleteStmt(r row.Row, tableName string, tableSch schema.Schema) (string, error) {
|
||||
var b strings.Builder
|
||||
b.WriteString("DELETE FROM ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
|
||||
b.WriteString(" WHERE (")
|
||||
seenOne := false
|
||||
_, err := r.IterSchema(tableSch, func(tag uint64, val types.Value) (stop bool, err error) {
|
||||
col := tableSch.GetAllCols().TagToCol[tag]
|
||||
if col.IsPartOfPK {
|
||||
if seenOne {
|
||||
b.WriteString(" AND ")
|
||||
}
|
||||
sqlString, err := valueAsSqlString(val)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
b.WriteString(QuoteIdentifier(col.Name))
|
||||
b.WriteRune('=')
|
||||
b.WriteString(sqlString)
|
||||
seenOne = true
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(");")
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func RowAsUpdateStmt(r row.Row, tableName string, tableSch schema.Schema) (string, error) {
|
||||
var b strings.Builder
|
||||
b.WriteString("UPDATE ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
b.WriteString(" ")
|
||||
|
||||
b.WriteString("SET ")
|
||||
seenOne := false
|
||||
_, err := r.IterSchema(tableSch, func(tag uint64, val types.Value) (stop bool, err error) {
|
||||
col := tableSch.GetAllCols().TagToCol[tag]
|
||||
if !col.IsPartOfPK {
|
||||
if seenOne {
|
||||
b.WriteRune(',')
|
||||
}
|
||||
sqlString, err := valueAsSqlString(val)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
b.WriteString(QuoteIdentifier(col.Name))
|
||||
b.WriteRune('=')
|
||||
b.WriteString(sqlString)
|
||||
seenOne = true
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(" WHERE (")
|
||||
seenOne = false
|
||||
_, err = r.IterSchema(tableSch, func(tag uint64, val types.Value) (stop bool, err error) {
|
||||
col := tableSch.GetAllCols().TagToCol[tag]
|
||||
if col.IsPartOfPK {
|
||||
if seenOne {
|
||||
b.WriteString(" AND ")
|
||||
}
|
||||
sqlString, err := valueAsSqlString(val)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
b.WriteString(QuoteIdentifier(col.Name))
|
||||
b.WriteRune('=')
|
||||
b.WriteString(sqlString)
|
||||
seenOne = true
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(");")
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// Quotes the identifier given with backticks.
|
||||
func QuoteIdentifier(s string) string {
|
||||
return "`" + s + "`"
|
||||
}
|
||||
|
||||
func valueAsSqlString(value types.Value) (string, error) {
|
||||
if types.IsNull(value) {
|
||||
return "NULL", nil
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case types.BoolKind:
|
||||
if value.(types.Bool) {
|
||||
return "TRUE", nil
|
||||
} else {
|
||||
return "FALSE", nil
|
||||
}
|
||||
case types.UUIDKind:
|
||||
convFn, err := doltcore.GetConvFunc(value.Kind(), types.StringKind)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str, _ := convFn(value)
|
||||
return doubleQuot + string(str.(types.String)) + doubleQuot, nil
|
||||
case types.StringKind:
|
||||
s := string(value.(types.String))
|
||||
s = strings.ReplaceAll(s, doubleQuot, "\\\"")
|
||||
return doubleQuot + s + doubleQuot, nil
|
||||
default:
|
||||
convFn, err := doltcore.GetConvFunc(value.Kind(), types.StringKind)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str, err := convFn(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(str.(types.String)), nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright 2019 Liquidata, 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 sql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/dtestutils"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/row"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/sql/sqltestutil"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const expectedCreateSQL = "CREATE TABLE `table_name` (\n" +
|
||||
" `id` BIGINT NOT NULL COMMENT 'tag:0',\n" +
|
||||
" `first` LONGTEXT NOT NULL COMMENT 'tag:1',\n" +
|
||||
" `last` LONGTEXT NOT NULL COMMENT 'tag:2',\n" +
|
||||
" `is_married` BOOLEAN COMMENT 'tag:3',\n" +
|
||||
" `age` BIGINT COMMENT 'tag:4',\n" +
|
||||
" `rating` DOUBLE COMMENT 'tag:6',\n" +
|
||||
" `uuid` LONGTEXT COMMENT 'tag:7',\n" +
|
||||
" `num_episodes` BIGINT UNSIGNED COMMENT 'tag:8',\n" +
|
||||
" PRIMARY KEY (`id`)\n" +
|
||||
");"
|
||||
const expectedDropSql = "DROP TABLE `table_name`;"
|
||||
const expectedDropIfExistsSql = "DROP TABLE IF EXISTS `table_name`;"
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
row row.Row
|
||||
sch schema.Schema
|
||||
expectedOutput string
|
||||
}
|
||||
|
||||
func TestSchemaAsCreateStmt(t *testing.T) {
|
||||
tSchema := sqltestutil.PeopleTestSchema
|
||||
str := SchemaAsCreateStmt("table_name", tSchema)
|
||||
|
||||
assert.Equal(t, expectedCreateSQL, str)
|
||||
}
|
||||
|
||||
func TestTableDropStmt(t *testing.T) {
|
||||
str := TableDropStmt("table_name")
|
||||
|
||||
assert.Equal(t, expectedDropSql, str)
|
||||
}
|
||||
|
||||
func TestTableDropIfExistsStmt(t *testing.T) {
|
||||
str := TableDropIfExistsStmt("table_name")
|
||||
|
||||
assert.Equal(t, expectedDropIfExistsSql, str)
|
||||
}
|
||||
|
||||
func TestRowAsInsertStmt(t *testing.T) {
|
||||
id := uuid.MustParse("00000000-0000-0000-0000-000000000000")
|
||||
tableName := "people"
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "simple row",
|
||||
row: dtestutils.NewTypedRow(id, "some guy", 100, false, strPointer("normie")),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
`VALUES ("00000000-0000-0000-0000-000000000000","some guy",100,FALSE,"normie");`,
|
||||
},
|
||||
{
|
||||
name: "embedded quotes",
|
||||
row: dtestutils.NewTypedRow(id, `It's "Mister Perfect" to you`, 100, false, strPointer("normie")),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
`VALUES ("00000000-0000-0000-0000-000000000000","It's \"Mister Perfect\" to you",100,FALSE,"normie");`,
|
||||
},
|
||||
{
|
||||
name: "null values",
|
||||
row: dtestutils.NewTypedRow(id, "some guy", 100, false, nil),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
`VALUES ("00000000-0000-0000-0000-000000000000","some guy",100,FALSE,NULL);`,
|
||||
},
|
||||
}
|
||||
|
||||
trickySch := dtestutils.CreateSchema(
|
||||
schema.NewColumn("a name with spaces", 0, types.FloatKind, false),
|
||||
schema.NewColumn("anotherColumn", 1, types.IntKind, true),
|
||||
)
|
||||
|
||||
tests = append(tests, test{
|
||||
name: "negative values and columns with spaces",
|
||||
row: dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)),
|
||||
sch: trickySch,
|
||||
expectedOutput: "INSERT INTO `people` (`a name with spaces`,`anotherColumn`) VALUES (-3.14,-42);",
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
stmt, err := RowAsInsertStmt(tt.row, tableName, tt.sch)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedOutput, stmt)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowAsDeleteStmt(t *testing.T) {
|
||||
tableName := "tricky"
|
||||
trickySch := dtestutils.CreateSchema(
|
||||
schema.NewColumn("anotherCol", 0, types.FloatKind, false),
|
||||
schema.NewColumn("a name with spaces", 1, types.IntKind, true),
|
||||
)
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "negative values and columns with spaces",
|
||||
row: dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)),
|
||||
sch: trickySch,
|
||||
expectedOutput: "DELETE FROM `tricky` WHERE (`a name with spaces`=-42);",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
stmt, err := RowAsDeleteStmt(tt.row, tableName, tt.sch)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedOutput, stmt)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowAsUpdateStmt(t *testing.T) {
|
||||
id := uuid.MustParse("00000000-0000-0000-0000-000000000000")
|
||||
tableName := "people"
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "simple row",
|
||||
row: dtestutils.NewTypedRow(id, "some guy", 100, false, strPointer("normie")),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "UPDATE `people` SET `name`=\"some guy\",`age`=100,`is_married`=FALSE,`title`=\"normie\" WHERE (`id`=\"00000000-0000-0000-0000-000000000000\");",
|
||||
},
|
||||
{
|
||||
name: "embedded quotes",
|
||||
row: dtestutils.NewTypedRow(id, `It's "Mister Perfect" to you`, 100, false, strPointer("normie")),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "UPDATE `people` SET `name`=\"It's \\\"Mister Perfect\\\" to you\",`age`=100,`is_married`=FALSE,`title`=\"normie\" WHERE (`id`=\"00000000-0000-0000-0000-000000000000\");",
|
||||
},
|
||||
{
|
||||
name: "null values",
|
||||
row: dtestutils.NewTypedRow(id, "some guy", 100, false, nil),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "UPDATE `people` SET `name`=\"some guy\",`age`=100,`is_married`=FALSE,`title`=NULL WHERE (`id`=\"00000000-0000-0000-0000-000000000000\");",
|
||||
},
|
||||
}
|
||||
|
||||
trickySch := dtestutils.CreateSchema(
|
||||
schema.NewColumn("a name with spaces", 0, types.FloatKind, false),
|
||||
schema.NewColumn("anotherColumn", 1, types.IntKind, true),
|
||||
)
|
||||
|
||||
tests = append(tests, test{
|
||||
name: "negative values and columns with spaces",
|
||||
row: dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)),
|
||||
sch: trickySch,
|
||||
expectedOutput: "UPDATE `people` SET `a name with spaces`=-3.14 WHERE (`anotherColumn`=-42);",
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
stmt, err := RowAsUpdateStmt(tt.row, tableName, tt.sch)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedOutput, stmt)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func strPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
@@ -18,10 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"vitess.io/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
||||
@@ -224,64 +221,6 @@ func ExecuteUpdate(ctx context.Context, db *doltdb.DoltDB, root *doltdb.RootValu
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func RowAsUpdateStmt(r row.Row, tableName string, tableSch schema.Schema) (string, error) {
|
||||
var b strings.Builder
|
||||
b.WriteString("UPDATE ")
|
||||
b.WriteString(QuoteIdentifier(tableName))
|
||||
b.WriteString(" ")
|
||||
|
||||
b.WriteString("SET ")
|
||||
seenOne := false
|
||||
_, err := r.IterSchema(tableSch, func(tag uint64, val types.Value) (stop bool, err error) {
|
||||
col := tableSch.GetAllCols().TagToCol[tag]
|
||||
if !col.IsPartOfPK {
|
||||
if seenOne {
|
||||
b.WriteRune(',')
|
||||
}
|
||||
sqlString, err := ValueAsSqlString(val)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
b.WriteString(QuoteIdentifier(col.Name))
|
||||
b.WriteRune('=')
|
||||
b.WriteString(sqlString)
|
||||
seenOne = true
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(" WHERE (")
|
||||
seenOne = false
|
||||
_, err = r.IterSchema(tableSch, func(tag uint64, val types.Value) (stop bool, err error) {
|
||||
col := tableSch.GetAllCols().TagToCol[tag]
|
||||
if col.IsPartOfPK {
|
||||
if seenOne {
|
||||
b.WriteString(" AND ")
|
||||
}
|
||||
sqlString, err := ValueAsSqlString(val)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
b.WriteString(QuoteIdentifier(col.Name))
|
||||
b.WriteRune('=')
|
||||
b.WriteString(sqlString)
|
||||
seenOne = true
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.WriteString(");")
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func errUpdate(errorFmt string, args ...interface{}) (*UpdateResult, error) {
|
||||
return nil, errors.New(fmt.Sprintf(errorFmt, args...))
|
||||
}
|
||||
|
||||
@@ -16,11 +16,8 @@ package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"strconv"
|
||||
"vitess.io/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/row"
|
||||
@@ -147,42 +144,6 @@ func LiteralValueGetter(value types.Value) *RowValGetter {
|
||||
}
|
||||
}
|
||||
|
||||
func ValueAsSqlString(value types.Value) (string, error) {
|
||||
if types.IsNull(value) {
|
||||
return "NULL", nil
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case types.BoolKind:
|
||||
if value.(types.Bool) {
|
||||
return "TRUE", nil
|
||||
} else {
|
||||
return "FALSE", nil
|
||||
}
|
||||
case types.UUIDKind:
|
||||
convFn, err := doltcore.GetConvFunc(value.Kind(), types.StringKind)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str, _ := convFn(value)
|
||||
return doubleQuot + string(str.(types.String)) + doubleQuot, nil
|
||||
case types.StringKind:
|
||||
s := string(value.(types.String))
|
||||
s = strings.ReplaceAll(s, doubleQuot, "\\\"")
|
||||
return doubleQuot + s + doubleQuot, nil
|
||||
default:
|
||||
convFn, err := doltcore.GetConvFunc(value.Kind(), types.StringKind)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str, err := convFn(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(str.(types.String)), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a RowValGetter for the column given, or an error
|
||||
func getterForColumn(qc QualifiedColumn, inputSchemas map[string]schema.Schema) (*RowValGetter, error) {
|
||||
tableSch, ok := inputSchemas[qc.TableName]
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/row"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/sql"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
)
|
||||
|
||||
type StringBuilderCloser struct {
|
||||
@@ -44,184 +43,11 @@ type test struct {
|
||||
expectedOutput string
|
||||
}
|
||||
|
||||
func TestWriteRow(t *testing.T) {
|
||||
id := uuid.MustParse("00000000-0000-0000-0000-000000000000")
|
||||
tableName := "people"
|
||||
|
||||
dropCreateStatement := "DROP TABLE IF EXISTS `people`;\n" + sql.SchemaAsCreateStmt(tableName, dtestutils.TypedSchema)
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "simple row",
|
||||
rows: rs(dtestutils.NewTypedRow(id, "some guy", 100, false, strPointer("normie"))),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: dropCreateStatement + "\n" + "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
`VALUES ("00000000-0000-0000-0000-000000000000","some guy",100,FALSE,"normie");` +
|
||||
"\n",
|
||||
},
|
||||
{
|
||||
name: "embedded quotes",
|
||||
rows: rs(dtestutils.NewTypedRow(id, `It's "Mister Perfect" to you`, 100, false, strPointer("normie"))),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: dropCreateStatement + "\n" + "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
`VALUES ("00000000-0000-0000-0000-000000000000","It's \"Mister Perfect\" to you",100,FALSE,"normie");` +
|
||||
"\n",
|
||||
},
|
||||
{
|
||||
name: "two rows",
|
||||
rows: rs(
|
||||
dtestutils.NewTypedRow(id, "some guy", 100, false, strPointer("normie")),
|
||||
dtestutils.NewTypedRow(id, "guy personson", 0, true, strPointer("officially a person"))),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: dropCreateStatement + "\n" +
|
||||
"INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
`VALUES ("00000000-0000-0000-0000-000000000000","some guy",100,FALSE,"normie");` + "\n" +
|
||||
"INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
`VALUES ("00000000-0000-0000-0000-000000000000","guy personson",0,TRUE,"officially a person");` + "\n",
|
||||
},
|
||||
{
|
||||
name: "null values",
|
||||
rows: rs(dtestutils.NewTypedRow(id, "some guy", 100, false, nil)),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: dropCreateStatement + "\n" + "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
`VALUES ("00000000-0000-0000-0000-000000000000","some guy",100,FALSE,NULL);` +
|
||||
"\n",
|
||||
},
|
||||
}
|
||||
|
||||
trickySch := dtestutils.CreateSchema(
|
||||
schema.NewColumn("a name with spaces", 0, types.FloatKind, false),
|
||||
schema.NewColumn("anotherColumn", 1, types.IntKind, true),
|
||||
)
|
||||
dropCreateTricky := "DROP TABLE IF EXISTS `people`;\n" + sql.SchemaAsCreateStmt(tableName, trickySch)
|
||||
|
||||
tests = append(tests, test{
|
||||
name: "negative values and columns with spaces",
|
||||
rows: rs(dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42))),
|
||||
sch: trickySch,
|
||||
expectedOutput: dropCreateTricky + "\n" + "INSERT INTO `people` (`a name with spaces`,`anotherColumn`) " +
|
||||
`VALUES (-3.14,-42);` +
|
||||
"\n",
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var stringWr StringBuilderCloser
|
||||
w := &SqlExportWriter{
|
||||
tableName: tableName,
|
||||
sch: tt.sch,
|
||||
wr: &stringWr,
|
||||
}
|
||||
|
||||
for _, r := range tt.rows {
|
||||
assert.NoError(t, w.WriteRow(context.Background(), r))
|
||||
}
|
||||
assert.Equal(t, tt.expectedOutput, stringWr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRow(t *testing.T) {
|
||||
tableName := "tricky"
|
||||
trickySch := dtestutils.CreateSchema(
|
||||
schema.NewColumn("anotherCol", 0, types.FloatKind, false),
|
||||
schema.NewColumn("a name with spaces", 1, types.IntKind, true),
|
||||
)
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "negative values and columns with spaces",
|
||||
rows: rs(dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42))),
|
||||
sch: trickySch,
|
||||
expectedOutput: "DELETE FROM `tricky` WHERE (`a name with spaces`=-42);" + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var stringWr StringBuilderCloser
|
||||
w := &SqlExportWriter{
|
||||
tableName: tableName,
|
||||
sch: tt.sch,
|
||||
wr: &stringWr,
|
||||
}
|
||||
|
||||
for _, r := range tt.rows {
|
||||
assert.NoError(t, w.WriteDeleteRow(context.Background(), r))
|
||||
}
|
||||
assert.Equal(t, tt.expectedOutput, stringWr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRow(t *testing.T) {
|
||||
id := uuid.MustParse("00000000-0000-0000-0000-000000000000")
|
||||
tableName := "people"
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "simple row",
|
||||
rows: rs(dtestutils.NewTypedRow(id, "some guy", 100, false, strPointer("normie"))),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "UPDATE `people` SET `name`=\"some guy\",`age`=100,`is_married`=FALSE,`title`=\"normie\" WHERE (`id`=\"00000000-0000-0000-0000-000000000000\");" + "\n",
|
||||
},
|
||||
{
|
||||
name: "embedded quotes",
|
||||
rows: rs(dtestutils.NewTypedRow(id, `It's "Mister Perfect" to you`, 100, false, strPointer("normie"))),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "UPDATE `people` SET `name`=\"It's \\\"Mister Perfect\\\" to you\",`age`=100,`is_married`=FALSE,`title`=\"normie\" WHERE (`id`=\"00000000-0000-0000-0000-000000000000\");" + "\n",
|
||||
},
|
||||
{
|
||||
name: "two rows",
|
||||
rows: rs(
|
||||
dtestutils.NewTypedRow(id, "some guy", 100, false, strPointer("normie")),
|
||||
dtestutils.NewTypedRow(id, "guy personson", 0, true, strPointer("officially a person"))),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "UPDATE `people` SET `name`=\"some guy\",`age`=100,`is_married`=FALSE,`title`=\"normie\" WHERE (`id`=\"00000000-0000-0000-0000-000000000000\");" + "\n" +
|
||||
"UPDATE `people` SET `name`=\"guy personson\",`age`=0,`is_married`=TRUE,`title`=\"officially a person\" WHERE (`id`=\"00000000-0000-0000-0000-000000000000\");" + "\n",
|
||||
},
|
||||
{
|
||||
name: "null values",
|
||||
rows: rs(dtestutils.NewTypedRow(id, "some guy", 100, false, nil)),
|
||||
sch: dtestutils.TypedSchema,
|
||||
expectedOutput: "UPDATE `people` SET `name`=\"some guy\",`age`=100,`is_married`=FALSE,`title`=NULL WHERE (`id`=\"00000000-0000-0000-0000-000000000000\");" + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
trickySch := dtestutils.CreateSchema(
|
||||
schema.NewColumn("a name with spaces", 0, types.FloatKind, false),
|
||||
schema.NewColumn("anotherColumn", 1, types.IntKind, true),
|
||||
)
|
||||
|
||||
tests = append(tests, test{
|
||||
name: "negative values and columns with spaces",
|
||||
rows: rs(dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42))),
|
||||
sch: trickySch,
|
||||
expectedOutput: "UPDATE `people` SET `a name with spaces`=-3.14 WHERE (`anotherColumn`=-42);" + "\n",
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var stringWr StringBuilderCloser
|
||||
w := &SqlExportWriter{
|
||||
tableName: tableName,
|
||||
sch: tt.sch,
|
||||
wr: &stringWr,
|
||||
}
|
||||
|
||||
for _, r := range tt.rows {
|
||||
assert.NoError(t, w.WriteUpdateRow(context.Background(), r))
|
||||
}
|
||||
assert.Equal(t, tt.expectedOutput, stringWr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndToEnd(t *testing.T) {
|
||||
id := uuid.MustParse("00000000-0000-0000-0000-000000000000")
|
||||
tableName := "people"
|
||||
|
||||
dropCreateStatement := "DROP TABLE IF EXISTS `people`;\n" + sql.SchemaAsCreateStmt(tableName, dtestutils.TypedSchema)
|
||||
dropCreateStatement := sql.TableDropIfExistsStmt(tableName) + "\n" + sql.SchemaAsCreateStmt(tableName, dtestutils.TypedSchema)
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user