mirror of
https://github.com/dolthub/dolt.git
synced 2026-05-04 19:41:26 -05:00
Foreign Keys groundwork implementation
This commit is contained in:
committed by
Daylon Wilkins
parent
e4abb55c4b
commit
b91178ce7a
File diff suppressed because it is too large
Load Diff
@@ -458,6 +458,37 @@ SQL
|
||||
! [[ "$output" =~ 'INDEX `idx_bud` (`v2`,`v1`)' ]] || false
|
||||
}
|
||||
|
||||
@test "index: Disallow 'dolt_' name prefix" {
|
||||
run dolt sql -q "CREATE INDEX dolt_idx_v1 ON onepk(v1)"
|
||||
[ "$status" -eq "1" ]
|
||||
run dolt sql -q "ALTER TABLE onepk ADD INDEX dolt_idx_v1 (v1)"
|
||||
[ "$status" -eq "1" ]
|
||||
}
|
||||
|
||||
@test "index: Disallow dropping 'dolt_' indexes" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
id INT PRIMARY KEY
|
||||
);
|
||||
CREATE TABLE child (
|
||||
id INT PRIMARY KEY,
|
||||
parent_id INT,
|
||||
FOREIGN KEY (parent_id)
|
||||
REFERENCES parent(id)
|
||||
);
|
||||
SQL
|
||||
|
||||
run dolt index ls child
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "dolt_fk_1(parent_id) HIDDEN" ]] || false
|
||||
run dolt sql -q "DROP INDEX dolt_fk_1 ON child"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ 'internal index' ]] || false
|
||||
run dolt sql -q "ALTER TABLE child DROP INDEX dolt_fk_1"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ 'internal index' ]] || false
|
||||
}
|
||||
|
||||
@test "index: DROP INDEX" {
|
||||
dolt sql <<SQL
|
||||
CREATE INDEX idx_v1 ON onepk(v1);
|
||||
|
||||
@@ -487,7 +487,7 @@ func sqlSchemaDiff(ctx context.Context, td diff.TableDelta) errhand.VerboseError
|
||||
if td.IsDrop() {
|
||||
cli.Println(sqlfmt.DropTableStmt(td.FromName))
|
||||
} else if td.IsAdd() {
|
||||
cli.Println(sqlfmt.CreateTableStmtWithTags(td.ToName, toSch))
|
||||
cli.Println(sqlfmt.CreateTableStmtWithTags(td.ToName, toSch, td.ToForeignKeys))
|
||||
} else {
|
||||
if td.FromName != td.ToName {
|
||||
cli.Println(sqlfmt.RenameTableStmt(td.FromName, td.ToName))
|
||||
|
||||
@@ -109,7 +109,11 @@ func (cmd LsCmd) Exec(ctx context.Context, commandStr string, args []string, dEn
|
||||
output = append(output, fmt.Sprintf("%s:", tableName))
|
||||
}
|
||||
for _, index := range sch.Indexes().AllIndexes() {
|
||||
output = append(output, fmt.Sprintf(" %s(%s)", index.Name(), strings.Join(index.ColumnNames(), ", ")))
|
||||
if !index.IsHidden() {
|
||||
output = append(output, fmt.Sprintf(" %s(%s)", index.Name(), strings.Join(index.ColumnNames(), ", ")))
|
||||
} else {
|
||||
output = append(output, fmt.Sprintf(" %s(%s) HIDDEN", index.Name(), strings.Join(index.ColumnNames(), ", ")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,11 +159,23 @@ func exportTblSchema(ctx context.Context, tblName string, root *doltdb.RootValue
|
||||
return errhand.BuildDError("error: failed to get schema for table %s", tblName).AddCause(err).Build()
|
||||
}
|
||||
|
||||
fkc, err := root.GetForeignKeyCollection(ctx)
|
||||
|
||||
if err != nil {
|
||||
return errhand.BuildDError("error: failed to read foreign key struct").AddCause(err).Build()
|
||||
}
|
||||
|
||||
declaresFk, err := fkc.KeysForDisplay(ctx, tblName, root)
|
||||
|
||||
if err != nil {
|
||||
return errhand.BuildDError("error: failed to assemble foreign key information").AddCause(err).Build()
|
||||
}
|
||||
|
||||
var stmt string
|
||||
if withTags {
|
||||
stmt = sqlfmt.CreateTableStmtWithTags(tblName, sch)
|
||||
stmt = sqlfmt.CreateTableStmtWithTags(tblName, sch, declaresFk)
|
||||
} else {
|
||||
stmt = sqlfmt.CreateTableStmt(tblName, sch)
|
||||
stmt = sqlfmt.CreateTableStmt(tblName, sch, declaresFk)
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(wr, stmt)
|
||||
|
||||
@@ -285,7 +285,7 @@ func importSchema(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars
|
||||
}
|
||||
|
||||
tblName := impArgs.tableName
|
||||
cli.Println(sqlfmt.CreateTableStmtWithTags(tblName, sch))
|
||||
cli.Println(sqlfmt.CreateTableStmtWithTags(tblName, sch, nil))
|
||||
|
||||
if !apr.Contains(dryRunFlag) {
|
||||
tbl, tblExists, err := root.GetTable(ctx, tblName)
|
||||
|
||||
@@ -144,7 +144,15 @@ func printSchemas(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env
|
||||
if !ok {
|
||||
notFound = append(notFound, tblName)
|
||||
} else {
|
||||
verr = printTblSchema(ctx, cmStr, tblName, tbl)
|
||||
fkc, err := root.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return errhand.BuildDError("error: failed to read foreign key struct").AddCause(err).Build()
|
||||
}
|
||||
declaresFk, err := fkc.KeysForDisplay(ctx, tblName, root)
|
||||
if err != nil {
|
||||
return errhand.BuildDError("error: failed to assemble foreign key information").AddCause(err).Build()
|
||||
}
|
||||
verr = printTblSchema(ctx, cmStr, tblName, tbl, declaresFk)
|
||||
cli.Println()
|
||||
}
|
||||
}
|
||||
@@ -157,7 +165,7 @@ func printSchemas(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env
|
||||
return verr
|
||||
}
|
||||
|
||||
func printTblSchema(ctx context.Context, cmStr string, tblName string, tbl *doltdb.Table) errhand.VerboseError {
|
||||
func printTblSchema(ctx context.Context, cmStr string, tblName string, tbl *doltdb.Table, foreignKeys []*doltdb.DisplayForeignKey) errhand.VerboseError {
|
||||
cli.Println(bold.Sprint(tblName), "@", cmStr)
|
||||
sch, err := tbl.GetSchema(ctx)
|
||||
|
||||
@@ -165,6 +173,6 @@ func printTblSchema(ctx context.Context, cmStr string, tblName string, tbl *dolt
|
||||
return errhand.BuildDError("unable to get schema").AddCause(err).Build()
|
||||
}
|
||||
|
||||
cli.Println(sqlfmt.CreateTableStmtWithTags(tblName, sch))
|
||||
cli.Println(sqlfmt.CreateTableStmtWithTags(tblName, sch, foreignKeys))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ require (
|
||||
github.com/kch42/buzhash v0.0.0-20160816060738-9bdec3dec7c6
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/liquidata-inc/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20200320155049-a8e482faeffd
|
||||
github.com/liquidata-inc/go-mysql-server v0.5.1-0.20200608182739-104505874162
|
||||
github.com/liquidata-inc/go-mysql-server v0.5.1-0.20200612104507-c618c3006b3a
|
||||
github.com/liquidata-inc/ishell v0.0.0-20190514193646-693241f1f2a0
|
||||
github.com/liquidata-inc/mmap-go v1.0.3
|
||||
github.com/liquidata-inc/sqllogictest/go v0.0.0-20200320151923-b11801f10e15
|
||||
@@ -89,6 +89,6 @@ require (
|
||||
|
||||
replace github.com/liquidata-inc/dolt/go/gen/proto/dolt/services/eventsapi => ./gen/proto/dolt/services/eventsapi
|
||||
|
||||
replace vitess.io/vitess => github.com/liquidata-inc/vitess v0.0.0-20200430040751-192bb76ecd8b
|
||||
replace vitess.io/vitess => github.com/liquidata-inc/vitess v0.0.0-20200604002149-44dae2034412
|
||||
|
||||
go 1.13
|
||||
|
||||
@@ -392,10 +392,8 @@ github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/liquidata-inc/go-mysql-server v0.5.1-0.20200605170723-cd4f84b39578 h1:fDv4huMp1Ubh/qVkkStpKxE1LgEIaLLQIkN5NUwAMBE=
|
||||
github.com/liquidata-inc/go-mysql-server v0.5.1-0.20200605170723-cd4f84b39578/go.mod h1:Qo0l83LdX5Z77p0tTLyJTrttZywFm0S+RYo6Shi97tw=
|
||||
github.com/liquidata-inc/go-mysql-server v0.5.1-0.20200608182739-104505874162 h1:qwjGQ6+8zWiGya5qWlHqChJZ/b/cqjc87zBDBJG6f3k=
|
||||
github.com/liquidata-inc/go-mysql-server v0.5.1-0.20200608182739-104505874162/go.mod h1:Qo0l83LdX5Z77p0tTLyJTrttZywFm0S+RYo6Shi97tw=
|
||||
github.com/liquidata-inc/go-mysql-server v0.5.1-0.20200612104507-c618c3006b3a h1:5SpDyX3lyHneYJwXtBw8cuj8dIgBo5K8ReIEpDkO9sE=
|
||||
github.com/liquidata-inc/go-mysql-server v0.5.1-0.20200612104507-c618c3006b3a/go.mod h1:KpDrkND/K8cIC0xAQx6EKsi5swStwzqJWdlQ2XRZetM=
|
||||
github.com/liquidata-inc/ishell v0.0.0-20190514193646-693241f1f2a0 h1:phMgajKClMUiIr+hF2LGt8KRuUa2Vd2GI1sNgHgSXoU=
|
||||
github.com/liquidata-inc/ishell v0.0.0-20190514193646-693241f1f2a0/go.mod h1:YC1rI9k5gx8D02ljlbxDfZe80s/iq8bGvaaQsvR+qxs=
|
||||
github.com/liquidata-inc/mmap-go v1.0.3 h1:2LndAeAtup9rpvUmu4wZSYCsjCQ0Zpc+NqE+6+PnT7g=
|
||||
@@ -404,6 +402,8 @@ github.com/liquidata-inc/sqllogictest/go v0.0.0-20200320151923-b11801f10e15 h1:H
|
||||
github.com/liquidata-inc/sqllogictest/go v0.0.0-20200320151923-b11801f10e15/go.mod h1:kKRVtyuomkqz15YFRpS0OT8kpsU8y/F3jyiZtvALdKU=
|
||||
github.com/liquidata-inc/vitess v0.0.0-20200430040751-192bb76ecd8b h1:bD5IABLonIOYGXmXnEtHgJgdZArEh7yi9mUdp4e30JM=
|
||||
github.com/liquidata-inc/vitess v0.0.0-20200430040751-192bb76ecd8b/go.mod h1:vn/QvIl/1+N6+qjheejcLt8jmX2kQSQwFinzZuoY1VY=
|
||||
github.com/liquidata-inc/vitess v0.0.0-20200604002149-44dae2034412 h1:zuc2t2NXPnVWVhuXGKNMS8RXO0NJVruQuoCB+M+OvAs=
|
||||
github.com/liquidata-inc/vitess v0.0.0-20200604002149-44dae2034412/go.mod h1:zCpP+FJseQinxNM5gkVPahLzv7lt3YI0aSvrT5k95Qk=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
|
||||
@@ -263,10 +263,11 @@ func GetDocDiffs(ctx context.Context, dEnv *env.DoltEnv) (*DocDiffs, *DocDiffs,
|
||||
}
|
||||
|
||||
type TableDelta struct {
|
||||
FromName string
|
||||
ToName string
|
||||
FromTable *doltdb.Table
|
||||
ToTable *doltdb.Table
|
||||
FromName string
|
||||
ToName string
|
||||
FromTable *doltdb.Table
|
||||
ToTable *doltdb.Table
|
||||
ToForeignKeys []*doltdb.DisplayForeignKey // In the event that a table is an add, we'll display the FKs as well
|
||||
}
|
||||
|
||||
// GetTableDeltas returns a list of TableDelta objects for each table that changed between fromRoot and toRoot.
|
||||
@@ -312,14 +313,25 @@ func GetTableDeltas(ctx context.Context, fromRoot, toRoot *doltdb.RootValue) ([]
|
||||
pkTag := sch.GetPKCols().GetColumns()[0].Tag
|
||||
oldName, ok := fromTableNames[pkTag]
|
||||
|
||||
fkc, err := toRoot.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
toDisplayFks, err := fkc.KeysForDisplay(ctx, name, toRoot)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
deltas = append(deltas, TableDelta{ToName: name, ToTable: table})
|
||||
deltas = append(deltas, TableDelta{ToName: name, ToTable: table, ToForeignKeys: toDisplayFks})
|
||||
} else if oldName != name || fromTableHashes[pkTag] != th {
|
||||
deltas = append(deltas, TableDelta{
|
||||
ToName: name,
|
||||
FromName: fromTableNames[pkTag],
|
||||
ToTable: table,
|
||||
FromTable: fromTable[pkTag],
|
||||
ToName: name,
|
||||
FromName: fromTableNames[pkTag],
|
||||
ToTable: table,
|
||||
FromTable: fromTable[pkTag],
|
||||
ToForeignKeys: toDisplayFks,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/cmd/dolt/errhand"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/row"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
@@ -179,7 +180,15 @@ func PrintSqlTableDiffs(ctx context.Context, r1, r2 *doltdb.RootValue, wr io.Wri
|
||||
if sch, err := tbl.GetSchema(ctx); err != nil {
|
||||
return errors.New("error unable to get schema for table " + tblName)
|
||||
} else {
|
||||
stmt := sqlfmt.CreateTableStmtWithTags(tblName, sch)
|
||||
fkc, err := r1.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return errhand.BuildDError("error: failed to read foreign key struct").AddCause(err).Build()
|
||||
}
|
||||
declaresFk, err := fkc.KeysForDisplay(ctx, tblName, r1)
|
||||
if err != nil {
|
||||
return errhand.BuildDError("error: failed to assemble foreign key information").AddCause(err).Build()
|
||||
}
|
||||
stmt := sqlfmt.CreateTableStmtWithTags(tblName, sch, declaresFk)
|
||||
if err = iohelp.WriteLine(wr, stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestSqlTableDiffAdd(t *testing.T) {
|
||||
|
||||
var stringWr StringBuilderCloser
|
||||
_ = PrintSqlTableDiffs(ctx, newRoot, oldRoot, &stringWr)
|
||||
expectedOutput := sqlfmt.CreateTableStmtWithTags("addTable", sch) + "\n"
|
||||
expectedOutput := sqlfmt.CreateTableStmtWithTags("addTable", sch, nil) + "\n"
|
||||
assert.Equal(t, expectedOutput, stringWr.String())
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func TestSqlTableDiffAddThenInsert(t *testing.T) {
|
||||
|
||||
var stringWr StringBuilderCloser
|
||||
_ = PrintSqlTableDiffs(ctx, newRoot, oldRoot, &stringWr)
|
||||
expectedOutput := sqlfmt.CreateTableStmtWithTags("addTable", sch) + "\n"
|
||||
expectedOutput := sqlfmt.CreateTableStmtWithTags("addTable", sch, nil) + "\n"
|
||||
expectedOutput = expectedOutput +
|
||||
"INSERT INTO `addTable` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
"VALUES ('00000000-0000-0000-0000-000000000000','Big Billy',77,FALSE,'Doctor');\n"
|
||||
@@ -149,7 +149,7 @@ func TestSqlTableDiffRenameChangedTable(t *testing.T) {
|
||||
_ = PrintSqlTableDiffs(ctx, newRoot, oldRoot, &stringWr)
|
||||
expectedOutput := "DROP TABLE `renameTable`;\n"
|
||||
expectedOutput = expectedOutput +
|
||||
sqlfmt.CreateTableStmtWithTags("newTableName", sch) + "\n" +
|
||||
sqlfmt.CreateTableStmtWithTags("newTableName", sch, nil) + "\n" +
|
||||
"INSERT INTO `newTableName` (`id`,`name`,`age`,`is_married`,`title`) " +
|
||||
"VALUES ('00000000-0000-0000-0000-000000000000','Big Billy',77,FALSE,'Doctor');\n"
|
||||
assert.Equal(t, expectedOutput, stringWr.String())
|
||||
|
||||
@@ -324,7 +324,7 @@ func (ddb *DoltDB) ReadRootValue(ctx context.Context, h hash.Hash) (*RootValue,
|
||||
|
||||
if val != nil {
|
||||
if rootSt, ok := val.(types.Struct); ok && rootSt.Name() == ddbRootStructName {
|
||||
return &RootValue{ddb.db, rootSt}, nil
|
||||
return &RootValue{ddb.db, rootSt, nil}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +53,9 @@ func createTestSchema(t *testing.T) schema.Schema {
|
||||
schema.NewColumn("empty", emptyTag, types.IntKind, false),
|
||||
)
|
||||
sch := schema.SchemaFromCols(colColl)
|
||||
_, err := sch.Indexes().AddIndexByColTags(testSchemaIndexName, []uint64{firstTag, lastTag}, false, "")
|
||||
_, err := sch.Indexes().AddIndexByColTags(testSchemaIndexName, []uint64{firstTag, lastTag}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
_, err = sch.Indexes().AddIndexByColTags(testSchemaIndexAge, []uint64{ageTag}, false, "")
|
||||
_, err = sch.Indexes().AddIndexByColTags(testSchemaIndexAge, []uint64{ageTag}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
return sch
|
||||
}
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
// Copyright 2020 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 doltdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/store/marshal"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
)
|
||||
|
||||
type ForeignKeyCollection struct {
|
||||
foreignKeys map[string]*ForeignKey
|
||||
}
|
||||
|
||||
type ForeignKeyReferenceOption byte
|
||||
|
||||
const (
|
||||
ForeignKeyReferenceOption_DefaultAction ForeignKeyReferenceOption = iota
|
||||
ForeignKeyReferenceOption_Cascade
|
||||
ForeignKeyReferenceOption_NoAction
|
||||
ForeignKeyReferenceOption_Restrict
|
||||
ForeignKeyReferenceOption_SetNull
|
||||
)
|
||||
|
||||
// DisplayForeignKey is a representation of a Foreign Key that is meant for display, such as when displaying a schema.
|
||||
type DisplayForeignKey struct {
|
||||
Name string
|
||||
TableName string
|
||||
TableIndex string
|
||||
TableColumns []string
|
||||
ReferencedTableName string
|
||||
ReferencedTableIndex string
|
||||
ReferencedTableColumns []string
|
||||
OnUpdate ForeignKeyReferenceOption
|
||||
OnDelete ForeignKeyReferenceOption
|
||||
}
|
||||
|
||||
// ForeignKey is the complete, internal representation of a Foreign Key.
|
||||
type ForeignKey struct {
|
||||
Name string `noms:"name" json:"name"`
|
||||
TableName string `noms:"tbl_name" json:"tbl_name"`
|
||||
TableIndex string `noms:"tbl_index" json:"tbl_index"`
|
||||
TableColumns []uint64 `noms:"tbl_cols" json:"tbl_cols"`
|
||||
ReferencedTableName string `noms:"ref_tbl_name" json:"ref_tbl_name"`
|
||||
ReferencedTableIndex string `noms:"ref_tbl_index" json:"ref_tbl_index"`
|
||||
ReferencedTableColumns []uint64 `noms:"ref_tbl_cols" json:"ref_tbl_cols"`
|
||||
OnUpdate ForeignKeyReferenceOption `noms:"on_update" json:"on_update"`
|
||||
OnDelete ForeignKeyReferenceOption `noms:"on_delete" json:"on_delete"`
|
||||
}
|
||||
|
||||
// LoadForeignKeyCollection returns a new ForeignKeyCollection using the provided map returned previously by GetMap.
|
||||
func LoadForeignKeyCollection(ctx context.Context, fkMap types.Map) (*ForeignKeyCollection, error) {
|
||||
fkc := &ForeignKeyCollection{
|
||||
foreignKeys: make(map[string]*ForeignKey),
|
||||
}
|
||||
err := fkMap.IterAll(ctx, func(_, value types.Value) error {
|
||||
foreignKey := &ForeignKey{}
|
||||
err := marshal.Unmarshal(ctx, fkMap.Format(), value, foreignKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fkc.foreignKeys[foreignKey.Name] = foreignKey
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fkc, nil
|
||||
}
|
||||
|
||||
// AddKey adds the given foreign key to the collection. Checks that the given name is unique in the collection.
|
||||
// All other validation should occur before being added to the collection.
|
||||
func (fkc *ForeignKeyCollection) AddKey(key *ForeignKey) error {
|
||||
if key.Name == "" {
|
||||
key.Name = fmt.Sprintf("fk_%s_%s_1", key.TableName, key.ReferencedTableName)
|
||||
for i := 2; fkc.Contains(key.Name); i++ {
|
||||
key.Name = fmt.Sprintf("fk_%s_%s_%d", key.TableName, key.ReferencedTableName, i)
|
||||
}
|
||||
} else if fkc.Contains(key.Name) {
|
||||
return fmt.Errorf("a foreign key with the name `%s` already exists", key.Name)
|
||||
}
|
||||
|
||||
fkc.foreignKeys[key.Name] = key
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllKeys returns a slice, sorted by name ascending, containing all of the foreign keys in this collection.
|
||||
func (fkc *ForeignKeyCollection) AllKeys() []*ForeignKey {
|
||||
fks := make([]*ForeignKey, len(fkc.foreignKeys))
|
||||
i := 0
|
||||
for _, fk := range fkc.foreignKeys {
|
||||
fks[i] = fk
|
||||
i++
|
||||
}
|
||||
sort.Slice(fks, func(i, j int) bool {
|
||||
return fks[i].Name < fks[j].Name
|
||||
})
|
||||
return fks
|
||||
}
|
||||
|
||||
// Contains returns whether the given foreign key name already exists for this collection.
|
||||
func (fkc *ForeignKeyCollection) Contains(foreignKeyName string) bool {
|
||||
_, ok := fkc.foreignKeys[foreignKeyName]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Count returns the number of indexes in this collection.
|
||||
func (fkc *ForeignKeyCollection) Count() int {
|
||||
return len(fkc.foreignKeys)
|
||||
}
|
||||
|
||||
// KeysForDisplay returns display-ready foreign keys that the given table declares. The results are intended only
|
||||
// for displaying key information to a user, and SHOULD NOT be used elsewhere. The results are sorted by name ascending.
|
||||
func (fkc *ForeignKeyCollection) KeysForDisplay(ctx context.Context, tableName string, root *RootValue) ([]*DisplayForeignKey, error) {
|
||||
var declaresFk []*DisplayForeignKey
|
||||
for _, foreignKey := range fkc.foreignKeys {
|
||||
if foreignKey.TableName == tableName {
|
||||
tableColumns, err := fkc.columnTagsToNames(ctx, foreignKey.TableName, foreignKey.Name, foreignKey.TableColumns, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refTableColumns, err := fkc.columnTagsToNames(ctx, foreignKey.ReferencedTableName, foreignKey.Name, foreignKey.ReferencedTableColumns, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
declaresFk = append(declaresFk, &DisplayForeignKey{
|
||||
Name: foreignKey.Name,
|
||||
TableName: foreignKey.TableName,
|
||||
TableIndex: foreignKey.TableIndex,
|
||||
TableColumns: tableColumns,
|
||||
ReferencedTableName: foreignKey.ReferencedTableName,
|
||||
ReferencedTableIndex: foreignKey.ReferencedTableIndex,
|
||||
ReferencedTableColumns: refTableColumns,
|
||||
OnUpdate: foreignKey.OnUpdate,
|
||||
OnDelete: foreignKey.OnDelete,
|
||||
})
|
||||
}
|
||||
}
|
||||
sort.Slice(declaresFk, func(i, j int) bool {
|
||||
return declaresFk[i].Name < declaresFk[j].Name
|
||||
})
|
||||
return declaresFk, nil
|
||||
}
|
||||
|
||||
// Get returns the foreign key with the given name, or nil if it does not exist.
|
||||
func (fkc *ForeignKeyCollection) Get(foreignKeyName string) *ForeignKey {
|
||||
return fkc.foreignKeys[foreignKeyName]
|
||||
}
|
||||
|
||||
// Map returns the collection as a Noms Map for persistence.
|
||||
func (fkc *ForeignKeyCollection) Map(ctx context.Context, vrw types.ValueReadWriter) (types.Map, error) {
|
||||
fkMap, err := types.NewMap(ctx, vrw)
|
||||
if err != nil {
|
||||
return types.EmptyMap, err
|
||||
}
|
||||
fkMapEditor := fkMap.Edit()
|
||||
for _, foreignKey := range fkc.foreignKeys {
|
||||
val, err := marshal.Marshal(ctx, vrw, *foreignKey)
|
||||
if err != nil {
|
||||
return types.EmptyMap, err
|
||||
}
|
||||
fkMapEditor.Set(types.String(foreignKey.Name), val)
|
||||
}
|
||||
return fkMapEditor.Map(ctx)
|
||||
}
|
||||
|
||||
// KeysForTable returns all foreign keys that reference the given table in some capacity. The returned array
|
||||
// declaredFk contains all foreign keys in which this table declared the foreign key. The array referencedByFk contains
|
||||
// all foreign keys in which this table is the referenced table. If the table contains a self-referential foreign key,
|
||||
// it will be present in both declaresFk and referencedByFk. Each array is sorted by name ascending.
|
||||
func (fkc *ForeignKeyCollection) KeysForTable(tableName string) (declaredFk, referencedByFk []*ForeignKey) {
|
||||
for _, foreignKey := range fkc.foreignKeys {
|
||||
if foreignKey.TableName == tableName {
|
||||
declaredFk = append(declaredFk, foreignKey)
|
||||
}
|
||||
if foreignKey.ReferencedTableName == tableName {
|
||||
referencedByFk = append(referencedByFk, foreignKey)
|
||||
}
|
||||
}
|
||||
sort.Slice(declaredFk, func(i, j int) bool {
|
||||
return declaredFk[i].Name < declaredFk[j].Name
|
||||
})
|
||||
sort.Slice(referencedByFk, func(i, j int) bool {
|
||||
return referencedByFk[i].Name < referencedByFk[j].Name
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveKey removes a foreign key from the collection. It does not remove the associated indexes from their
|
||||
// respective tables.
|
||||
func (fkc *ForeignKeyCollection) RemoveKey(foreignKeyName string) (*ForeignKey, error) {
|
||||
fk, ok := fkc.foreignKeys[foreignKeyName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("`%s` does not exist as a foreign key", foreignKeyName)
|
||||
}
|
||||
delete(fkc.foreignKeys, foreignKeyName)
|
||||
return fk, nil
|
||||
}
|
||||
|
||||
// RemoveTables removes all foreign keys associated with the given tables, if permitted. The operation assumes that ALL
|
||||
// tables to be removed are in a single call, as splitting tables into different calls may result in unintended errors.
|
||||
func (fkc *ForeignKeyCollection) RemoveTables(ctx context.Context, root *RootValue, tables ...string) (*RootValue, error) {
|
||||
tableSet := make(map[string]struct{})
|
||||
for _, table := range tables {
|
||||
tableSet[table] = struct{}{}
|
||||
}
|
||||
for _, foreignKey := range fkc.foreignKeys {
|
||||
_, declaringTable := tableSet[foreignKey.TableName]
|
||||
_, referenceTable := tableSet[foreignKey.ReferencedTableName]
|
||||
if referenceTable && !declaringTable {
|
||||
return nil, fmt.Errorf("unable to remove `%s` since it is referenced from table `%s`", foreignKey.ReferencedTableName, foreignKey.TableName)
|
||||
}
|
||||
if declaringTable {
|
||||
if !referenceTable {
|
||||
tbl, ok, err := root.GetTable(ctx, foreignKey.ReferencedTableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("table `%s` not found, unable to remove foreign key `%s`", foreignKey.ReferencedTableName, foreignKey.Name)
|
||||
}
|
||||
sch, err := tbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = sch.Indexes().RemoveIndex(foreignKey.TableIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tbl, err = tbl.UpdateSchema(ctx, sch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root, err = root.PutTable(ctx, foreignKey.ReferencedTableName, tbl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
delete(fkc.foreignKeys, foreignKey.Name)
|
||||
}
|
||||
}
|
||||
return root.PutForeignKeyCollection(ctx, fkc)
|
||||
}
|
||||
|
||||
// RenameTable updates all foreign key entries in the collection with the updated table name. Does not check for name
|
||||
// collisions.
|
||||
func (fkc *ForeignKeyCollection) RenameTable(oldTableName, newTableName string) {
|
||||
for _, foreignKey := range fkc.foreignKeys {
|
||||
if foreignKey.TableName == oldTableName {
|
||||
foreignKey.TableName = newTableName
|
||||
}
|
||||
if foreignKey.ReferencedTableName == oldTableName {
|
||||
foreignKey.ReferencedTableName = newTableName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the SQL reference option in uppercase.
|
||||
func (refOp ForeignKeyReferenceOption) String() string {
|
||||
switch refOp {
|
||||
case ForeignKeyReferenceOption_DefaultAction:
|
||||
return "NONE SPECIFIED"
|
||||
case ForeignKeyReferenceOption_Cascade:
|
||||
return "CASCADE"
|
||||
case ForeignKeyReferenceOption_NoAction:
|
||||
return "NO ACTION"
|
||||
case ForeignKeyReferenceOption_Restrict:
|
||||
return "RESTRICT"
|
||||
case ForeignKeyReferenceOption_SetNull:
|
||||
return "SET NULL"
|
||||
default:
|
||||
return "INVALID"
|
||||
}
|
||||
}
|
||||
|
||||
// columnTagsToNames loads all of the column names for the tags given from the root given.
|
||||
func (fkc *ForeignKeyCollection) columnTagsToNames(ctx context.Context, tableName string, fkName string, colTags []uint64, root *RootValue) ([]string, error) {
|
||||
tbl, ok, err := root.GetTable(ctx, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("foreign key `%s` declares non-existent table `%s`", fkName, tableName)
|
||||
}
|
||||
tableSch, err := tbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tableColumns := make([]string, len(colTags))
|
||||
for i := range colTags {
|
||||
col, ok := tableSch.GetAllCols().GetByTag(colTags[i])
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("foreign key `%s` declares non-existent column with tag `%d`", fkName, colTags[i])
|
||||
}
|
||||
tableColumns[i] = col.Name
|
||||
}
|
||||
return tableColumns, nil
|
||||
}
|
||||
|
||||
// copy returns an exact copy of the calling collection. As collections are meant to be modified in-place, this ensures
|
||||
// that the original collection is not affected by any operations applied to the copied collection.
|
||||
func (fkc *ForeignKeyCollection) copy() *ForeignKeyCollection {
|
||||
copiedForeignKeys := make(map[string]*ForeignKey)
|
||||
for _, key := range fkc.foreignKeys {
|
||||
valueKey := *key // value types are copied, so this essentially copies all fields (the slices never change so it's okay)
|
||||
copiedForeignKeys[valueKey.Name] = &valueKey
|
||||
}
|
||||
return &ForeignKeyCollection{copiedForeignKeys}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func TestIndexEditorConcurrency(t *testing.T) {
|
||||
schema.NewColumn("v2", 2, types.IntKind, false))
|
||||
require.NoError(t, err)
|
||||
tableSch := schema.SchemaFromCols(colColl)
|
||||
index, err := tableSch.Indexes().AddIndexByColNames("idx_concurrency", []string{"v1"}, false, "")
|
||||
index, err := tableSch.Indexes().AddIndexByColNames("idx_concurrency", []string{"v1"}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
indexSch := index.Schema()
|
||||
emptyMap, err := types.NewMap(context.Background(), db)
|
||||
@@ -131,7 +131,7 @@ func TestIndexEditorConcurrencyPostInsert(t *testing.T) {
|
||||
schema.NewColumn("v2", 2, types.IntKind, false))
|
||||
require.NoError(t, err)
|
||||
tableSch := schema.SchemaFromCols(colColl)
|
||||
index, err := tableSch.Indexes().AddIndexByColNames("idx_concurrency", []string{"v1"}, false, "")
|
||||
index, err := tableSch.Indexes().AddIndexByColNames("idx_concurrency", []string{"v1"}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
indexSch := index.Schema()
|
||||
emptyMap, err := types.NewMap(context.Background(), db)
|
||||
@@ -215,7 +215,7 @@ func TestIndexEditorConcurrencyUnique(t *testing.T) {
|
||||
schema.NewColumn("v2", 2, types.IntKind, false))
|
||||
require.NoError(t, err)
|
||||
tableSch := schema.SchemaFromCols(colColl)
|
||||
index, err := tableSch.Indexes().AddIndexByColNames("idx_concurrency", []string{"v1"}, true, "")
|
||||
index, err := tableSch.Indexes().AddIndexByColNames("idx_concurrency", []string{"v1"}, schema.IndexProperties{IsUnique: true, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
indexSch := index.Schema()
|
||||
emptyMap, err := types.NewMap(context.Background(), db)
|
||||
@@ -302,7 +302,7 @@ func TestIndexEditorWriteAfterFlush(t *testing.T) {
|
||||
schema.NewColumn("v2", 2, types.IntKind, false))
|
||||
require.NoError(t, err)
|
||||
tableSch := schema.SchemaFromCols(colColl)
|
||||
index, err := tableSch.Indexes().AddIndexByColNames("idx_concurrency", []string{"v1"}, false, "")
|
||||
index, err := tableSch.Indexes().AddIndexByColNames("idx_concurrency", []string{"v1"}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
indexSch := index.Schema()
|
||||
emptyMap, err := types.NewMap(context.Background(), db)
|
||||
|
||||
@@ -34,12 +34,14 @@ const (
|
||||
|
||||
tablesKey = "tables"
|
||||
superSchemasKey = "super_schemas"
|
||||
foreignKeyKey = "foreign_key"
|
||||
)
|
||||
|
||||
// RootValue defines the structure used inside all Liquidata noms dbs
|
||||
type RootValue struct {
|
||||
vrw types.ValueReadWriter
|
||||
valueSt types.Struct
|
||||
fkc *ForeignKeyCollection // cache the first load
|
||||
}
|
||||
|
||||
type DocDetails struct {
|
||||
@@ -49,7 +51,7 @@ type DocDetails struct {
|
||||
File string
|
||||
}
|
||||
|
||||
func NewRootValue(ctx context.Context, vrw types.ValueReadWriter, tables map[string]hash.Hash, ssMap types.Map) (*RootValue, error) {
|
||||
func NewRootValue(ctx context.Context, vrw types.ValueReadWriter, tables map[string]hash.Hash, ssMap types.Map, fkMap types.Map) (*RootValue, error) {
|
||||
values := make([]types.Value, 2*len(tables))
|
||||
|
||||
index := 0
|
||||
@@ -80,11 +82,11 @@ func NewRootValue(ctx context.Context, vrw types.ValueReadWriter, tables map[str
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRootFromMaps(vrw, tblMap, ssMap)
|
||||
return newRootFromMaps(vrw, tblMap, ssMap, fkMap)
|
||||
}
|
||||
|
||||
func newRootValue(vrw types.ValueReadWriter, st types.Struct) *RootValue {
|
||||
return &RootValue{vrw, st}
|
||||
return &RootValue{vrw, st, nil}
|
||||
}
|
||||
|
||||
func emptyRootValue(ctx context.Context, vrw types.ValueReadWriter) (*RootValue, error) {
|
||||
@@ -100,13 +102,20 @@ func emptyRootValue(ctx context.Context, vrw types.ValueReadWriter) (*RootValue,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRootFromMaps(vrw, m, mm)
|
||||
mmm, err := types.NewMap(ctx, vrw)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRootFromMaps(vrw, m, mm, mmm)
|
||||
}
|
||||
|
||||
func newRootFromMaps(vrw types.ValueReadWriter, tblMap types.Map, ssMap types.Map) (*RootValue, error) {
|
||||
func newRootFromMaps(vrw types.ValueReadWriter, tblMap types.Map, ssMap types.Map, fkMap types.Map) (*RootValue, error) {
|
||||
sd := types.StructData{
|
||||
tablesKey: tblMap,
|
||||
superSchemasKey: ssMap,
|
||||
foreignKeyKey: fkMap,
|
||||
}
|
||||
|
||||
st, err := types.NewStruct(vrw.Format(), ddbRootStructName, sd)
|
||||
@@ -982,6 +991,20 @@ func (root *RootValue) RenameTable(ctx context.Context, oldName, newName string)
|
||||
return newRootValue(root.vrw, rootValSt), nil
|
||||
}
|
||||
|
||||
foreignKeyCollection, err := root.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
foreignKeyCollection.RenameTable(oldName, newName)
|
||||
fkMap, err := foreignKeyCollection.Map(ctx, root.vrw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootValSt, err = rootValSt.Set(foreignKeyKey, fkMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRootValue(root.vrw, rootValSt), nil
|
||||
}
|
||||
|
||||
@@ -1017,7 +1040,15 @@ func (root *RootValue) RemoveTables(ctx context.Context, tables ...string) (*Roo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRootValue(root.vrw, rootValSt), nil
|
||||
newRoot := newRootValue(root.vrw, rootValSt)
|
||||
|
||||
fkc, err := root.GetForeignKeyCollection(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fkc.RemoveTables(ctx, newRoot, tables...)
|
||||
}
|
||||
|
||||
// DocDiff returns the added, modified and removed docs when comparing a root value with an other (newer) value. If the other value,
|
||||
@@ -1068,6 +1099,56 @@ func (root *RootValue) DocDiff(ctx context.Context, other *RootValue, docDetails
|
||||
return a, m, r, nil
|
||||
}
|
||||
|
||||
// GetForeignKeyCollection returns the ForeignKeyCollection for this root. As collections are meant to be modified
|
||||
// in-place, each returned collection may freely be altered without affecting future returned collections from this root.
|
||||
func (root *RootValue) GetForeignKeyCollection(ctx context.Context) (*ForeignKeyCollection, error) {
|
||||
if root.fkc == nil {
|
||||
fkMap, err := root.GetForeignKeyCollectionMap(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root.fkc, err = LoadForeignKeyCollection(ctx, fkMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return root.fkc.copy(), nil
|
||||
}
|
||||
|
||||
// GetForeignKeyCollectionMap returns the persisted noms Map of the foreign key collection on this root. If the intent
|
||||
// is to retreive a ForeignKeyCollection in particular, it is advised to call GetForeignKeyCollection as it caches the
|
||||
// result for performance.
|
||||
func (root *RootValue) GetForeignKeyCollectionMap(ctx context.Context) (types.Map, error) {
|
||||
v, found, err := root.valueSt.MaybeGet(foreignKeyKey)
|
||||
if err != nil {
|
||||
return types.EmptyMap, err
|
||||
}
|
||||
|
||||
var fkMap types.Map
|
||||
if found {
|
||||
fkMap = v.(types.Map)
|
||||
} else {
|
||||
fkMap, err = types.NewMap(ctx, root.vrw)
|
||||
if err != nil {
|
||||
return types.EmptyMap, err
|
||||
}
|
||||
}
|
||||
return fkMap, nil
|
||||
}
|
||||
|
||||
// PutForeignKeyCollection returns a new root with the given foreign key collection.
|
||||
func (root *RootValue) PutForeignKeyCollection(ctx context.Context, fkc *ForeignKeyCollection) (*RootValue, error) {
|
||||
fkMap, err := fkc.Map(ctx, root.vrw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootValSt, err := root.valueSt.Set(foreignKeyKey, fkMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RootValue{root.vrw, rootValSt, fkc.copy()}, nil
|
||||
}
|
||||
|
||||
func getDocDetailsBtwnRoots(ctx context.Context, newTbl *Table, newSch schema.Schema, newTblFound bool, oldTbl *Table, oldSch schema.Schema, oldTblFound bool) ([]DocDetails, error) {
|
||||
var docDetailsBtwnRoots []DocDetails
|
||||
if newTblFound {
|
||||
|
||||
@@ -379,7 +379,7 @@ func TestIndexRebuildingUniqueSuccessOneCol(t *testing.T) {
|
||||
originalTable, err := createTableWithoutIndexRebuilding(context.Background(), db, schVal, rowData)
|
||||
require.NoError(t, err)
|
||||
|
||||
index, err := sch.Indexes().AddIndexByColTags("idx_v1", []uint64{2}, true, "")
|
||||
index, err := sch.Indexes().AddIndexByColTags("idx_v1", []uint64{2}, schema.IndexProperties{IsUnique: true, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
updatedTable, err := originalTable.UpdateSchema(context.Background(), sch)
|
||||
require.NoError(t, err)
|
||||
@@ -409,7 +409,7 @@ func TestIndexRebuildingUniqueSuccessTwoCol(t *testing.T) {
|
||||
originalTable, err := createTableWithoutIndexRebuilding(context.Background(), db, schVal, rowData)
|
||||
require.NoError(t, err)
|
||||
|
||||
index, err := sch.Indexes().AddIndexByColTags("idx_v1", []uint64{2, 3}, true, "")
|
||||
index, err := sch.Indexes().AddIndexByColTags("idx_v1", []uint64{2, 3}, schema.IndexProperties{IsUnique: true, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
updatedTable, err := originalTable.UpdateSchema(context.Background(), sch)
|
||||
require.NoError(t, err)
|
||||
@@ -439,7 +439,7 @@ func TestIndexRebuildingUniqueFailOneCol(t *testing.T) {
|
||||
originalTable, err := createTableWithoutIndexRebuilding(context.Background(), db, schVal, rowData)
|
||||
require.NoError(t, err)
|
||||
|
||||
index, err := sch.Indexes().AddIndexByColTags("idx_v1", []uint64{2}, true, "")
|
||||
index, err := sch.Indexes().AddIndexByColTags("idx_v1", []uint64{2}, schema.IndexProperties{IsUnique: true, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
updatedTable, err := originalTable.UpdateSchema(context.Background(), sch)
|
||||
require.NoError(t, err)
|
||||
@@ -470,7 +470,7 @@ func TestIndexRebuildingUniqueFailTwoCol(t *testing.T) {
|
||||
originalTable, err := createTableWithoutIndexRebuilding(context.Background(), db, schVal, rowData)
|
||||
require.NoError(t, err)
|
||||
|
||||
index, err := sch.Indexes().AddIndexByColTags("idx_v1", []uint64{2, 3}, true, "")
|
||||
index, err := sch.Indexes().AddIndexByColTags("idx_v1", []uint64{2, 3}, schema.IndexProperties{IsUnique: true, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
updatedTable, err := originalTable.UpdateSchema(context.Background(), sch)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -98,11 +98,11 @@ func init() {
|
||||
UntypedRows = append(UntypedRows, r)
|
||||
}
|
||||
|
||||
_, err := TypedSchema.Indexes().AddIndexByColTags(IndexName, []uint64{NameTag}, false, "")
|
||||
_, err := TypedSchema.Indexes().AddIndexByColTags(IndexName, []uint64{NameTag}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = UntypedSchema.Indexes().AddIndexByColTags(IndexName, []uint64{NameTag}, false, "")
|
||||
_, err = UntypedSchema.Indexes().AddIndexByColTags(IndexName, []uint64{NameTag}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+10
-4
@@ -216,6 +216,12 @@ func CheckoutBranch(ctx context.Context, dEnv *env.DoltEnv, brName string) error
|
||||
return err
|
||||
}
|
||||
|
||||
fkMap, err := newRoot.GetForeignKeyCollectionMap(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conflicts := set.NewStrSet([]string{})
|
||||
wrkTblHashes, err := tblHashesForCO(ctx, currRoots[HeadRoot], newRoot, currRoots[WorkingRoot], conflicts)
|
||||
|
||||
@@ -233,13 +239,13 @@ func CheckoutBranch(ctx context.Context, dEnv *env.DoltEnv, brName string) error
|
||||
return CheckoutWouldOverwrite{conflicts.AsSlice()}
|
||||
}
|
||||
|
||||
wrkHash, err := writeRoot(ctx, dEnv, wrkTblHashes, ssMap)
|
||||
wrkHash, err := writeRoot(ctx, dEnv, wrkTblHashes, ssMap, fkMap)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stgHash, err := writeRoot(ctx, dEnv, stgTblHashes, ssMap)
|
||||
stgHash, err := writeRoot(ctx, dEnv, stgTblHashes, ssMap, fkMap)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -334,14 +340,14 @@ func tblHashesForCO(ctx context.Context, oldRoot, newRoot, changedRoot *doltdb.R
|
||||
return resultMap, nil
|
||||
}
|
||||
|
||||
func writeRoot(ctx context.Context, dEnv *env.DoltEnv, tblHashes map[string]hash.Hash, ssMap types.Map) (hash.Hash, error) {
|
||||
func writeRoot(ctx context.Context, dEnv *env.DoltEnv, tblHashes map[string]hash.Hash, ssMap types.Map, fkMap types.Map) (hash.Hash, error) {
|
||||
for k, v := range tblHashes {
|
||||
if v == emptyHash {
|
||||
delete(tblHashes, k)
|
||||
}
|
||||
}
|
||||
|
||||
root, err := doltdb.NewRootValue(ctx, dEnv.DoltDB.ValueReadWriter(), tblHashes, ssMap)
|
||||
root, err := doltdb.NewRootValue(ctx, dEnv.DoltDB.ValueReadWriter(), tblHashes, ssMap, fkMap)
|
||||
if err != nil {
|
||||
if err == doltdb.ErrHashNotFound {
|
||||
return emptyHash, errors.New("corrupted database? Can't find hash of current table")
|
||||
|
||||
@@ -258,7 +258,7 @@ func init() {
|
||||
keyTuples[i] = mustTuple(types.NewTuple(types.Format_7_18, keyTag, id))
|
||||
}
|
||||
|
||||
index, _ = sch.Indexes().AddIndexByColTags("idx_name", []uint64{nameTag}, false, "")
|
||||
index, _ = sch.Indexes().AddIndexByColTags("idx_name", []uint64{nameTag}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
}
|
||||
|
||||
func setupMergeTest() (types.ValueReadWriter, *doltdb.Commit, *doltdb.Commit, types.Map, types.Map) {
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/cmd/dolt/errhand"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/table"
|
||||
@@ -154,7 +156,15 @@ func (dl FileDataLocation) NewCreatingWriter(ctx context.Context, mvOpts DataMov
|
||||
case JsonFile:
|
||||
return json.OpenJSONWriter(dl.Path, fs, outSch)
|
||||
case SqlFile:
|
||||
return sqlexport.OpenSQLExportWriter(dl.Path, mvOpts.SrcName(), fs, outSch)
|
||||
fkc, err := root.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return nil, errhand.BuildDError("error: failed to read foreign key struct").AddCause(err).Build()
|
||||
}
|
||||
declaredFks, err := fkc.KeysForDisplay(ctx, mvOpts.SrcName(), root)
|
||||
if err != nil {
|
||||
return nil, errhand.BuildDError("error: failed to assemble foreign key information").AddCause(err).Build()
|
||||
}
|
||||
return sqlexport.OpenSQLExportWriter(dl.Path, mvOpts.SrcName(), fs, outSch, declaredFks)
|
||||
}
|
||||
|
||||
panic("Invalid Data Format." + string(dl.Format))
|
||||
|
||||
@@ -363,7 +363,7 @@ func replayCommitWithNewTag(ctx context.Context, root, parentRoot, rebasedParent
|
||||
rebasedSch := schema.SchemaFromCols(schCC)
|
||||
|
||||
for _, index := range sch.Indexes().AllIndexes() {
|
||||
_, err = rebasedSch.Indexes().AddIndexByColNames(index.Name(), index.ColumnNames(), index.IsUnique(), index.Comment())
|
||||
_, err = rebasedSch.Indexes().AddIndexByColNames(index.Name(), index.ColumnNames(), schema.IndexProperties{IsUnique: index.IsUnique(), IsHidden: index.IsHidden(), Comment: index.Comment()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ var sch, _ = schema.SchemaFromPKAndNonPKCols(testKeyColColl, testNonKeyColColl)
|
||||
var index schema.Index
|
||||
|
||||
func init() {
|
||||
index, _ = sch.Indexes().AddIndexByColTags(indexName, []uint64{ageColTag}, false, "")
|
||||
index, _ = sch.Indexes().AddIndexByColTags(indexName, []uint64{ageColTag}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
}
|
||||
|
||||
func newTestRow() (Row, error) {
|
||||
|
||||
@@ -135,6 +135,7 @@ type encodedIndex struct {
|
||||
Tags []uint64 `noms:"tags" json:"tags"`
|
||||
Comment string `noms:"comment" json:"comment"`
|
||||
Unique bool `noms:"unique" json:"unique"`
|
||||
Hidden bool `noms:"hidden,omitempty" json:"hidden,omitempty"`
|
||||
}
|
||||
|
||||
type schemaData struct {
|
||||
@@ -165,6 +166,7 @@ func toSchemaData(sch schema.Schema) (schemaData, error) {
|
||||
Tags: index.IndexedColumnTags(),
|
||||
Comment: index.Comment(),
|
||||
Unique: index.IsUnique(),
|
||||
Hidden: index.IsHidden(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +193,7 @@ func (sd schemaData) decodeSchema() (schema.Schema, error) {
|
||||
|
||||
sch := schema.SchemaFromCols(colColl)
|
||||
for _, encodedIndex := range sd.IndexCollection {
|
||||
_, err = sch.Indexes().AddIndexByColTags(encodedIndex.Name, encodedIndex.Tags, encodedIndex.Unique, encodedIndex.Comment)
|
||||
_, err = sch.Indexes().AddIndexByColTags(encodedIndex.Name, encodedIndex.Tags, schema.IndexProperties{IsUnique: encodedIndex.Unique, IsHidden: encodedIndex.Hidden, Comment: encodedIndex.Comment})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func createTestSchema() schema.Schema {
|
||||
|
||||
colColl, _ := schema.NewColCollection(columns...)
|
||||
sch := schema.SchemaFromCols(colColl)
|
||||
_, _ = sch.Indexes().AddIndexByColTags("idx_age", []uint64{3}, false, "")
|
||||
_, _ = sch.Indexes().AddIndexByColTags("idx_age", []uint64{3}, schema.IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
return sch
|
||||
}
|
||||
|
||||
@@ -231,6 +231,7 @@ type testEncodedIndex struct {
|
||||
Tags []uint64 `noms:"tags" json:"tags"`
|
||||
Comment string `noms:"comment" json:"comment"`
|
||||
Unique bool `noms:"unique" json:"unique"`
|
||||
Hidden bool `noms:"hidden,omitempty" json:"hidden,omitempty"`
|
||||
}
|
||||
|
||||
type testSchemaData struct {
|
||||
@@ -276,7 +277,7 @@ func (tsd testSchemaData) decodeSchema() (schema.Schema, error) {
|
||||
sch := schema.SchemaFromCols(colColl)
|
||||
|
||||
for _, encodedIndex := range tsd.IndexCollection {
|
||||
_, err = sch.Indexes().AddIndexByColTags(encodedIndex.Name, encodedIndex.Tags, encodedIndex.Unique, encodedIndex.Comment)
|
||||
_, err = sch.Indexes().AddIndexByColTags(encodedIndex.Name, encodedIndex.Tags, schema.IndexProperties{IsUnique: encodedIndex.Unique, IsHidden: encodedIndex.Hidden, Comment: encodedIndex.Comment})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ type Index interface {
|
||||
GetColumn(tag uint64) (Column, bool)
|
||||
// IndexedColumnTags returns the tags of the columns in the index.
|
||||
IndexedColumnTags() []uint64
|
||||
// IsHidden returns whether the index is hidden and managed internally, such as for a foreign key. Such indexes do
|
||||
// not cause column nor tag collisions with other indexes.
|
||||
IsHidden() bool
|
||||
// IsUnique returns whether the index enforces the UNIQUE constraint.
|
||||
IsUnique() bool
|
||||
// Name returns the name of the index.
|
||||
@@ -45,6 +48,7 @@ type indexImpl struct {
|
||||
tags []uint64
|
||||
allTags []uint64
|
||||
indexColl *indexCollectionImpl
|
||||
isHidden bool
|
||||
isUnique bool
|
||||
comment string
|
||||
}
|
||||
@@ -77,6 +81,10 @@ func (ix *indexImpl) IndexedColumnTags() []uint64 {
|
||||
return ix.tags
|
||||
}
|
||||
|
||||
func (ix *indexImpl) IsHidden() bool {
|
||||
return ix.isHidden
|
||||
}
|
||||
|
||||
func (ix *indexImpl) IsUnique() bool {
|
||||
return ix.isUnique
|
||||
}
|
||||
@@ -122,6 +130,7 @@ func (ix *indexImpl) copy() *indexImpl {
|
||||
tags: tags,
|
||||
allTags: allTags,
|
||||
indexColl: ix.indexColl,
|
||||
isHidden: ix.isHidden,
|
||||
isUnique: ix.isUnique,
|
||||
comment: ix.comment,
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package schema
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IndexCollection interface {
|
||||
@@ -24,9 +25,9 @@ type IndexCollection interface {
|
||||
// It does not perform any kind of checking, and is intended for schema modifications.
|
||||
AddIndex(indexes ...Index)
|
||||
// AddIndexByColNames adds an index with the given name and columns (in index order).
|
||||
AddIndexByColNames(indexName string, cols []string, isUnique bool, comment string) (Index, error)
|
||||
AddIndexByColNames(indexName string, cols []string, props IndexProperties) (Index, error)
|
||||
// AddIndexByColTags adds an index with the given name and column tags (in index order).
|
||||
AddIndexByColTags(indexName string, tags []uint64, isUnique bool, comment string) (Index, error)
|
||||
AddIndexByColTags(indexName string, tags []uint64, props IndexProperties) (Index, error)
|
||||
// AllIndexes returns a slice containing all of the indexes in this collection.
|
||||
AllIndexes() []Index
|
||||
// Contains returns whether the given index name already exists for this table.
|
||||
@@ -35,11 +36,11 @@ type IndexCollection interface {
|
||||
Count() int
|
||||
// Get returns the index with the given name, or nil if it does not exist.
|
||||
Get(indexName string) Index
|
||||
// HasIndexes returns whether this collection has any indexes.
|
||||
HasIndexes() bool
|
||||
// HasIndexOnColumns returns whether the collection contains an index that has this exact collection and ordering of columns.
|
||||
// Any hidden indexes are ignored.
|
||||
HasIndexOnColumns(cols ...string) bool
|
||||
// HasIndexOnTags returns whether the collection contains an index that has this exact collection and ordering of columns.
|
||||
// Any hidden indexes are ignored.
|
||||
HasIndexOnTags(tags ...uint64) bool
|
||||
// IndexesWithColumn returns all indexes that index the given column.
|
||||
IndexesWithColumn(columnName string) []Index
|
||||
@@ -56,6 +57,12 @@ type IndexCollection interface {
|
||||
RenameIndex(oldName, newName string) (Index, error)
|
||||
}
|
||||
|
||||
type IndexProperties struct {
|
||||
IsUnique bool
|
||||
IsHidden bool
|
||||
Comment string
|
||||
}
|
||||
|
||||
type indexCollectionImpl struct {
|
||||
colColl *ColCollection
|
||||
indexes map[string]*indexImpl
|
||||
@@ -104,31 +111,39 @@ func (ixc *indexCollectionImpl) AddIndex(indexes ...Index) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ixc *indexCollectionImpl) AddIndexByColNames(indexName string, cols []string, isUnique bool, comment string) (Index, error) {
|
||||
func (ixc *indexCollectionImpl) AddIndexByColNames(indexName string, cols []string, props IndexProperties) (Index, error) {
|
||||
tags, ok := ixc.columnNamesToTags(cols)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("the table does not contain at least one of the following columns: `%v`", cols)
|
||||
}
|
||||
return ixc.AddIndexByColTags(indexName, tags, isUnique, comment)
|
||||
return ixc.AddIndexByColTags(indexName, tags, props)
|
||||
}
|
||||
|
||||
func (ixc *indexCollectionImpl) AddIndexByColTags(indexName string, tags []uint64, isUnique bool, comment string) (Index, error) {
|
||||
func (ixc *indexCollectionImpl) AddIndexByColTags(indexName string, tags []uint64, props IndexProperties) (Index, error) {
|
||||
if props.IsHidden && !strings.HasPrefix(indexName, "dolt_") {
|
||||
return nil, fmt.Errorf("hidden indexes must be prefixed with `dolt_`")
|
||||
} else if !props.IsHidden && strings.HasPrefix(indexName, "dolt_") {
|
||||
return nil, fmt.Errorf("indexes cannot be prefixed with `dolt_`")
|
||||
}
|
||||
if ixc.Contains(indexName) {
|
||||
return nil, fmt.Errorf("`%s` already exists as an index for this table", indexName)
|
||||
}
|
||||
if !ixc.tagsExist(tags...) {
|
||||
return nil, fmt.Errorf("tags %v do not exist on this table", tags)
|
||||
}
|
||||
if ixc.HasIndexOnTags(tags...) {
|
||||
return nil, fmt.Errorf("cannot create a duplicate index on this table")
|
||||
if !props.IsHidden {
|
||||
if ixc.HasIndexOnTags(tags...) {
|
||||
return nil, fmt.Errorf("cannot create a duplicate index on this table")
|
||||
}
|
||||
}
|
||||
index := &indexImpl{
|
||||
indexColl: ixc,
|
||||
name: indexName,
|
||||
tags: tags,
|
||||
allTags: combineAllTags(tags, ixc.pks),
|
||||
isUnique: isUnique,
|
||||
comment: comment,
|
||||
isHidden: props.IsHidden,
|
||||
isUnique: props.IsUnique,
|
||||
comment: props.Comment,
|
||||
}
|
||||
ixc.indexes[indexName] = index
|
||||
for _, tag := range tags {
|
||||
@@ -167,13 +182,6 @@ func (ixc *indexCollectionImpl) Get(indexName string) Index {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ixc *indexCollectionImpl) HasIndexes() bool {
|
||||
if len(ixc.indexes) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ixc *indexCollectionImpl) HasIndexOnColumns(cols ...string) bool {
|
||||
tags := make([]uint64, len(cols))
|
||||
for i, col := range cols {
|
||||
@@ -188,7 +196,7 @@ func (ixc *indexCollectionImpl) HasIndexOnColumns(cols ...string) bool {
|
||||
|
||||
func (ixc *indexCollectionImpl) HasIndexOnTags(tags ...uint64) bool {
|
||||
idx := ixc.containsColumnTagCollection(tags...)
|
||||
if idx == nil {
|
||||
if idx == nil || idx.isHidden {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -198,7 +198,7 @@ func TestIndexCollectionAddIndexByColNames(t *testing.T) {
|
||||
assert.False(t, indexColl.HasIndexOnTags(testIndex.index.IndexedColumnTags()...))
|
||||
assert.Nil(t, indexColl.Get(testIndex.index.Name()))
|
||||
|
||||
resIndex, err := indexColl.AddIndexByColNames(testIndex.index.Name(), testIndex.cols, testIndex.index.IsUnique(), testIndex.index.Comment())
|
||||
resIndex, err := indexColl.AddIndexByColNames(testIndex.index.Name(), testIndex.cols, IndexProperties{IsUnique: testIndex.index.IsUnique(), IsHidden: false, Comment: testIndex.index.Comment()})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testIndex.index, resIndex)
|
||||
assert.Equal(t, testIndex.index, indexColl.Get(resIndex.Name()))
|
||||
@@ -218,20 +218,20 @@ func TestIndexCollectionAddIndexByColNames(t *testing.T) {
|
||||
|
||||
t.Run("Pre-existing", func(t *testing.T) {
|
||||
for _, testIndex := range testIndexes {
|
||||
_, err := indexColl.AddIndexByColNames(testIndex.index.Name(), testIndex.cols, testIndex.index.IsUnique(), testIndex.index.Comment())
|
||||
_, err := indexColl.AddIndexByColNames(testIndex.index.Name(), testIndex.cols, IndexProperties{IsUnique: testIndex.index.IsUnique(), IsHidden: false, Comment: testIndex.index.Comment()})
|
||||
assert.NoError(t, err)
|
||||
_, err = indexColl.AddIndexByColNames("nonsense", testIndex.cols, testIndex.index.IsUnique(), testIndex.index.Comment())
|
||||
_, err = indexColl.AddIndexByColNames("nonsense", testIndex.cols, IndexProperties{IsUnique: testIndex.index.IsUnique(), IsHidden: false, Comment: testIndex.index.Comment()})
|
||||
assert.Error(t, err)
|
||||
_, err = indexColl.AddIndexByColNames(testIndex.index.Name(), []string{"v2"}, testIndex.index.IsUnique(), testIndex.index.Comment())
|
||||
_, err = indexColl.AddIndexByColNames(testIndex.index.Name(), []string{"v2"}, IndexProperties{IsUnique: testIndex.index.IsUnique(), IsHidden: false, Comment: testIndex.index.Comment()})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
indexColl.clear(t)
|
||||
})
|
||||
|
||||
t.Run("Non-existing Columns", func(t *testing.T) {
|
||||
_, err := indexColl.AddIndexByColNames("nonsense", []string{"v4"}, false, "")
|
||||
_, err := indexColl.AddIndexByColNames("nonsense", []string{"v4"}, IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
assert.Error(t, err)
|
||||
_, err = indexColl.AddIndexByColNames("nonsense", []string{"v1", "v2", "pk3"}, false, "")
|
||||
_, err = indexColl.AddIndexByColNames("nonsense", []string{"v1", "v2", "pk3"}, IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -282,7 +282,7 @@ func TestIndexCollectionAddIndexByColTags(t *testing.T) {
|
||||
assert.False(t, indexColl.HasIndexOnTags(testIndex.IndexedColumnTags()...))
|
||||
assert.Nil(t, indexColl.Get(testIndex.Name()))
|
||||
|
||||
resIndex, err := indexColl.AddIndexByColTags(testIndex.Name(), testIndex.tags, testIndex.IsUnique(), testIndex.Comment())
|
||||
resIndex, err := indexColl.AddIndexByColTags(testIndex.Name(), testIndex.tags, IndexProperties{IsUnique: testIndex.IsUnique(), IsHidden: false, Comment: testIndex.Comment()})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testIndex, resIndex)
|
||||
assert.Equal(t, testIndex, indexColl.Get(resIndex.Name()))
|
||||
@@ -302,20 +302,20 @@ func TestIndexCollectionAddIndexByColTags(t *testing.T) {
|
||||
|
||||
t.Run("Pre-existing", func(t *testing.T) {
|
||||
for _, testIndex := range testIndexes {
|
||||
_, err := indexColl.AddIndexByColTags(testIndex.Name(), testIndex.tags, testIndex.IsUnique(), testIndex.Comment())
|
||||
_, err := indexColl.AddIndexByColTags(testIndex.Name(), testIndex.tags, IndexProperties{IsUnique: testIndex.IsUnique(), IsHidden: false, Comment: testIndex.Comment()})
|
||||
assert.NoError(t, err)
|
||||
_, err = indexColl.AddIndexByColTags("nonsense", testIndex.tags, testIndex.IsUnique(), testIndex.Comment())
|
||||
_, err = indexColl.AddIndexByColTags("nonsense", testIndex.tags, IndexProperties{IsUnique: testIndex.IsUnique(), IsHidden: false, Comment: testIndex.Comment()})
|
||||
assert.Error(t, err)
|
||||
_, err = indexColl.AddIndexByColTags(testIndex.Name(), []uint64{4}, testIndex.IsUnique(), testIndex.Comment())
|
||||
_, err = indexColl.AddIndexByColTags(testIndex.Name(), []uint64{4}, IndexProperties{IsUnique: testIndex.IsUnique(), IsHidden: false, Comment: testIndex.Comment()})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
indexColl.clear(t)
|
||||
})
|
||||
|
||||
t.Run("Non-existing Tags", func(t *testing.T) {
|
||||
_, err := indexColl.AddIndexByColTags("nonsense", []uint64{6}, false, "")
|
||||
_, err := indexColl.AddIndexByColTags("nonsense", []uint64{6}, IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
assert.Error(t, err)
|
||||
_, err = indexColl.AddIndexByColTags("nonsense", []uint64{3, 4, 10}, false, "")
|
||||
_, err = indexColl.AddIndexByColTags("nonsense", []uint64{3, 4, 10}, IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -335,9 +335,9 @@ func TestIndexCollectionAllIndexes(t *testing.T) {
|
||||
name: "idx_z",
|
||||
tags: []uint64{3},
|
||||
})
|
||||
_, err = indexColl.AddIndexByColNames("idx_a", []string{"v2"}, false, "")
|
||||
_, err = indexColl.AddIndexByColNames("idx_a", []string{"v2"}, IndexProperties{IsUnique: false, IsHidden: false, Comment: ""})
|
||||
require.NoError(t, err)
|
||||
_, err = indexColl.AddIndexByColTags("idx_n", []uint64{5}, false, "hello there")
|
||||
_, err = indexColl.AddIndexByColTags("idx_n", []uint64{5}, IndexProperties{IsUnique: false, IsHidden: false, Comment: "hello there"})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []Index{
|
||||
@@ -368,42 +368,6 @@ func TestIndexCollectionAllIndexes(t *testing.T) {
|
||||
}, indexColl.AllIndexes())
|
||||
}
|
||||
|
||||
func TestIndexCollectionHasIndexes(t *testing.T) {
|
||||
colColl, err := NewColCollection(
|
||||
NewColumn("pk1", 1, types.IntKind, true, NotNullConstraint{}),
|
||||
NewColumn("pk2", 2, types.IntKind, true, NotNullConstraint{}),
|
||||
NewColumn("v1", 3, types.IntKind, false),
|
||||
NewColumn("v2", 4, types.UintKind, false),
|
||||
NewColumn("v3", 5, types.StringKind, false),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
indexColl := NewIndexCollection(colColl).(*indexCollectionImpl)
|
||||
|
||||
assert.False(t, indexColl.HasIndexes())
|
||||
|
||||
indexColl.AddIndex(&indexImpl{
|
||||
name: "idx_z",
|
||||
tags: []uint64{3},
|
||||
})
|
||||
assert.True(t, indexColl.HasIndexes())
|
||||
|
||||
_, err = indexColl.RemoveIndex("idx_z")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, indexColl.HasIndexes())
|
||||
|
||||
_, err = indexColl.AddIndexByColNames("idx_a", []string{"v2"}, false, "")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, indexColl.HasIndexes())
|
||||
|
||||
_, err = indexColl.RemoveIndex("idx_a")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, indexColl.HasIndexes())
|
||||
|
||||
_, err = indexColl.AddIndexByColTags("idx_n", []uint64{5}, false, "hello there")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, indexColl.HasIndexes())
|
||||
}
|
||||
|
||||
func TestIndexCollectionRemoveIndex(t *testing.T) {
|
||||
colColl, err := NewColCollection(
|
||||
NewColumn("pk1", 1, types.IntKind, true, NotNullConstraint{}),
|
||||
|
||||
@@ -56,7 +56,7 @@ type test struct {
|
||||
|
||||
func TestSchemaAsCreateStmt(t *testing.T) {
|
||||
tSchema := sqltestutil.PeopleTestSchema
|
||||
stmt := CreateTableStmtWithTags("table_name", tSchema)
|
||||
stmt := CreateTableStmtWithTags("table_name", tSchema, nil)
|
||||
|
||||
assert.Equal(t, expectedCreateSQL, stmt)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
)
|
||||
|
||||
@@ -68,22 +70,22 @@ func FmtColTagComment(tag uint64) string {
|
||||
}
|
||||
|
||||
// CreateTableStmtWithTags generates a SQL CREATE TABLE command
|
||||
func CreateTableStmt(tableName string, sch schema.Schema) string {
|
||||
func CreateTableStmt(tableName string, sch schema.Schema, foreignKeys []*doltdb.DisplayForeignKey) string {
|
||||
return createTableStmt(tableName, sch, func(col schema.Column) string {
|
||||
return FmtCol(2, 0, 0, col)
|
||||
})
|
||||
}, foreignKeys)
|
||||
}
|
||||
|
||||
// CreateTableStmtWithTags generates a SQL CREATE TABLE command that includes the column tags as comments
|
||||
func CreateTableStmtWithTags(tableName string, sch schema.Schema) string {
|
||||
func CreateTableStmtWithTags(tableName string, sch schema.Schema, foreignKeys []*doltdb.DisplayForeignKey) string {
|
||||
return createTableStmt(tableName, sch, func(col schema.Column) string {
|
||||
return FmtColWithTag(2, 0, 0, col)
|
||||
})
|
||||
}, foreignKeys)
|
||||
}
|
||||
|
||||
type fmtColFunc func(col schema.Column) string
|
||||
|
||||
func createTableStmt(tableName string, sch schema.Schema, fmtCol fmtColFunc) string {
|
||||
func createTableStmt(tableName string, sch schema.Schema, fmtCol fmtColFunc, foreignKeys []*doltdb.DisplayForeignKey) string {
|
||||
|
||||
sb := &strings.Builder{}
|
||||
sb.WriteString(fmt.Sprintf("CREATE TABLE %s (\n", QuoteIdentifier(tableName)))
|
||||
@@ -117,6 +119,9 @@ func createTableStmt(tableName string, sch schema.Schema, fmtCol fmtColFunc) str
|
||||
sb.WriteRune(')')
|
||||
|
||||
for _, index := range sch.Indexes().AllIndexes() {
|
||||
if index.IsHidden() {
|
||||
continue
|
||||
}
|
||||
sb.WriteString(",\n ")
|
||||
if index.IsUnique() {
|
||||
sb.WriteString("UNIQUE ")
|
||||
@@ -137,6 +142,36 @@ func createTableStmt(tableName string, sch schema.Schema, fmtCol fmtColFunc) str
|
||||
}
|
||||
}
|
||||
|
||||
for _, foreignKey := range foreignKeys {
|
||||
sb.WriteString(",\n CONSTRAINT ")
|
||||
sb.WriteString(QuoteIdentifier(foreignKey.Name))
|
||||
sb.WriteString(" FOREIGN KEY (")
|
||||
for i, fkColName := range foreignKey.TableColumns {
|
||||
if i != 0 {
|
||||
sb.WriteRune(',')
|
||||
}
|
||||
sb.WriteString(QuoteIdentifier(fkColName))
|
||||
}
|
||||
sb.WriteString(")\n REFERENCES ")
|
||||
sb.WriteString(QuoteIdentifier(foreignKey.ReferencedTableName))
|
||||
sb.WriteString(" (")
|
||||
for i, fkColName := range foreignKey.ReferencedTableColumns {
|
||||
if i != 0 {
|
||||
sb.WriteRune(',')
|
||||
}
|
||||
sb.WriteString(QuoteIdentifier(fkColName))
|
||||
}
|
||||
sb.WriteRune(')')
|
||||
if foreignKey.OnDelete != doltdb.ForeignKeyReferenceOption_DefaultAction {
|
||||
sb.WriteString("\n ON DELETE ")
|
||||
sb.WriteString(foreignKey.OnDelete.String())
|
||||
}
|
||||
if foreignKey.OnUpdate != doltdb.ForeignKeyReferenceOption_DefaultAction {
|
||||
sb.WriteString("\n ON UPDATE ")
|
||||
sb.WriteString(foreignKey.OnUpdate.String())
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString("\n);")
|
||||
|
||||
return sb.String()
|
||||
|
||||
@@ -706,7 +706,7 @@ var BasicSelectTests = []SelectTest{
|
||||
Query: "select * from dolt_log",
|
||||
ExpectedRows: []sql.Row{
|
||||
{
|
||||
"0e2b6g3oemme1je6g3l2bm3hr5mhgpa2",
|
||||
"p3hcpn726bhcsellrtitr85ckjotnv8d",
|
||||
"billy bob",
|
||||
"bigbillieb@fake.horse",
|
||||
time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
@@ -736,7 +736,7 @@ var BasicSelectTests = []SelectTest{
|
||||
ExpectedRows: []sql.Row{
|
||||
{
|
||||
"master",
|
||||
"0e2b6g3oemme1je6g3l2bm3hr5mhgpa2",
|
||||
"p3hcpn726bhcsellrtitr85ckjotnv8d",
|
||||
"billy bob", "bigbillieb@fake.horse",
|
||||
time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
"Initialize data repository",
|
||||
|
||||
@@ -18,8 +18,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/liquidata-inc/go-mysql-server/sql"
|
||||
"vitess.io/vitess/go/sqltypes"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/schema"
|
||||
@@ -258,6 +260,7 @@ type AlterableDoltTable struct {
|
||||
|
||||
var _ sql.AlterableTable = (*AlterableDoltTable)(nil)
|
||||
var _ sql.IndexAlterableTable = (*AlterableDoltTable)(nil)
|
||||
var _ sql.ForeignKeyAlterableTable = (*AlterableDoltTable)(nil)
|
||||
|
||||
// AddColumn implements sql.AlterableTable
|
||||
func (t *AlterableDoltTable) AddColumn(ctx *sql.Context, column *sql.Column, order *sql.ColumnOrder) error {
|
||||
@@ -434,58 +437,7 @@ func (t *AlterableDoltTable) ModifyColumn(ctx *sql.Context, columnName string, c
|
||||
|
||||
// CreateIndex implements sql.IndexAlterableTable
|
||||
func (t *AlterableDoltTable) CreateIndex(ctx *sql.Context, indexName string, using sql.IndexUsing, constraint sql.IndexConstraint, columns []sql.IndexColumn, comment string) error {
|
||||
if constraint != sql.IndexConstraint_None && constraint != sql.IndexConstraint_Unique {
|
||||
return fmt.Errorf("not yet supported")
|
||||
}
|
||||
|
||||
if !doltdb.IsValidTableName(indexName) {
|
||||
return fmt.Errorf("invalid index name `%s` as they must match the regular expression %s", indexName, doltdb.TableNameRegexStr)
|
||||
}
|
||||
|
||||
// get the real column names as CREATE INDEX columns are case-insensitive
|
||||
var realColNames []string
|
||||
allTableCols := t.sch.GetAllCols()
|
||||
for _, indexCol := range columns {
|
||||
tableCol, ok := allTableCols.GetByName(indexCol.Name)
|
||||
if !ok {
|
||||
tableCol, ok = allTableCols.GetByNameCaseInsensitive(indexCol.Name)
|
||||
if !ok {
|
||||
return fmt.Errorf("column `%s` does not exist for the table", indexCol.Name)
|
||||
}
|
||||
}
|
||||
realColNames = append(realColNames, tableCol.Name)
|
||||
}
|
||||
|
||||
// create the index metadata, will error if index names are taken or an index with the same columns in the same order exists
|
||||
_, err := t.sch.Indexes().AddIndexByColNames(indexName, realColNames, constraint == sql.IndexConstraint_Unique, comment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update the table schema with the new index
|
||||
newSchemaVal, err := encoding.MarshalSchemaAsNomsValue(ctx, t.table.ValueReadWriter(), t.sch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tableRowData, err := t.table.GetRowData(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
indexData, err := t.table.GetIndexData(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newTable, err := doltdb.NewTable(ctx, t.table.ValueReadWriter(), newSchemaVal, tableRowData, &indexData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the index row data and get a new root with the updated table
|
||||
indexRowData, err := newTable.RebuildIndexRowData(ctx, indexName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newTable, err = newTable.SetIndexRowData(ctx, indexName, indexRowData)
|
||||
newTable, _, _, err := t.createIndex(ctx, t.table, t.sch, false, indexName, using, constraint, columns, comment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -497,27 +449,23 @@ func (t *AlterableDoltTable) CreateIndex(ctx *sql.Context, indexName string, usi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return t.db.SetRoot(ctx, newRoot)
|
||||
err = t.db.SetRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.updateFromRoot(ctx, newRoot)
|
||||
}
|
||||
|
||||
// DropIndex implements sql.IndexAlterableTable
|
||||
func (t *AlterableDoltTable) DropIndex(ctx *sql.Context, indexName string) error {
|
||||
// RemoveIndex returns an error if the index does not exist, no need to do twice
|
||||
_, err := t.sch.Indexes().RemoveIndex(indexName)
|
||||
// We disallow removing internal dolt_ tables from SQL directly
|
||||
if strings.HasPrefix(indexName, "dolt_") {
|
||||
return fmt.Errorf("dolt internal indexes may not be dropped")
|
||||
}
|
||||
newTable, _, err := t.dropIndex(ctx, t.table, t.sch, indexName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newTable, err := t.table.UpdateSchema(ctx, t.sch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newTable, err = newTable.DeleteIndexRowData(ctx, indexName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err := t.db.GetRoot(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -526,7 +474,11 @@ func (t *AlterableDoltTable) DropIndex(ctx *sql.Context, indexName string) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.db.SetRoot(ctx, newRoot)
|
||||
err = t.db.SetRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.updateFromRoot(ctx, newRoot)
|
||||
}
|
||||
|
||||
// RenameIndex implements sql.IndexAlterableTable
|
||||
@@ -550,5 +502,311 @@ func (t *AlterableDoltTable) RenameIndex(ctx *sql.Context, fromIndexName string,
|
||||
return err
|
||||
}
|
||||
|
||||
return t.db.SetRoot(ctx, newRoot)
|
||||
err = t.db.SetRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.updateFromRoot(ctx, newRoot)
|
||||
}
|
||||
|
||||
func (t *AlterableDoltTable) CreateForeignKey(ctx *sql.Context, fkName string, columns []string, referencedTable string, referencedColumns []string,
|
||||
onUpdate, onDelete sql.ForeignKeyReferenceOption) error {
|
||||
if fkName != "" && !doltdb.IsValidTableName(fkName) {
|
||||
return fmt.Errorf("invalid foreign key name `%s` as it must match the regular expression %s", fkName, doltdb.TableNameRegexStr)
|
||||
}
|
||||
//TODO: move this into go-mysql-server
|
||||
if len(columns) != len(referencedColumns) {
|
||||
return fmt.Errorf("the foreign key must reference an equivalent number of columns")
|
||||
}
|
||||
|
||||
root, err := t.db.GetRoot(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refTable, refTableName, ok, err := root.GetTableInsensitive(ctx, referencedTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("referenced table `%s` does not exist", referencedTable)
|
||||
}
|
||||
refTableSch, err := refTable.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tblCols := make([]schema.Column, len(columns))
|
||||
colTags := make([]uint64, len(columns))
|
||||
sqlColNames := make([]sql.IndexColumn, len(columns))
|
||||
for i, col := range columns {
|
||||
tableCol, ok := t.sch.GetAllCols().GetByNameCaseInsensitive(col)
|
||||
if !ok {
|
||||
//TODO: fix go-mysql-server equivalent check, needs two vals
|
||||
return fmt.Errorf("table `%s` does not have column `%s`", t.name, col)
|
||||
}
|
||||
tblCols[i] = tableCol
|
||||
colTags[i] = tableCol.Tag
|
||||
sqlColNames[i] = sql.IndexColumn{
|
||||
Name: tableCol.Name,
|
||||
Length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
refColTags := make([]uint64, len(referencedColumns))
|
||||
sqlRefColNames := make([]sql.IndexColumn, len(referencedColumns))
|
||||
for i, col := range referencedColumns {
|
||||
tableCol, ok := refTableSch.GetAllCols().GetByNameCaseInsensitive(col)
|
||||
if !ok {
|
||||
return fmt.Errorf("table `%s` does not have column `%s`", refTableName, col)
|
||||
}
|
||||
refColTags[i] = tableCol.Tag
|
||||
sqlRefColNames[i] = sql.IndexColumn{
|
||||
Name: tableCol.Name,
|
||||
Length: 0,
|
||||
}
|
||||
if !tblCols[i].TypeInfo.Equals(tableCol.TypeInfo) {
|
||||
//TODO: make this more descriptive about our lack of support of different types for fks, figure it out later
|
||||
return fmt.Errorf("column type mismatch on `%s` and `%s`", columns[i], tableCol.Name)
|
||||
}
|
||||
sqlparserType := tableCol.TypeInfo.ToSqlType().Type()
|
||||
if sqlparserType == sqltypes.Blob || sqlparserType == sqltypes.Text {
|
||||
return fmt.Errorf("TEXT/BLOB are not valid types for foreign keys")
|
||||
}
|
||||
}
|
||||
|
||||
onUpdateRefOp, err := t.parseFkReferenceOption(onUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
onDeleteRefOp, err := t.parseFkReferenceOption(onDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uniqueIndexName := func(tblSch schema.Schema) string {
|
||||
indexName := "dolt_fk_1"
|
||||
for i := 2; tblSch.Indexes().Contains(indexName); i++ {
|
||||
indexName = fmt.Sprintf("dolt_fk_%d", i)
|
||||
}
|
||||
return indexName
|
||||
}
|
||||
newTable, _, tableIndex, err := t.createIndex(ctx, t.table, t.sch, true, uniqueIndexName(t.sch), sql.IndexUsing_Default, sql.IndexConstraint_None, sqlColNames, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRefTable, _, refTableIndex, err := t.createIndex(ctx, refTable, refTableSch, true, uniqueIndexName(refTableSch), sql.IndexUsing_Default, sql.IndexConstraint_None, sqlRefColNames, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newRoot, err := root.PutTable(ctx, t.name, newTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot, err = newRoot.PutTable(ctx, refTableName, newRefTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
foreignKeyCollection, err := newRoot.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = foreignKeyCollection.AddKey(&doltdb.ForeignKey{
|
||||
Name: fkName,
|
||||
TableName: t.name,
|
||||
TableIndex: tableIndex.Name(),
|
||||
TableColumns: colTags,
|
||||
ReferencedTableName: refTableName,
|
||||
ReferencedTableIndex: refTableIndex.Name(),
|
||||
ReferencedTableColumns: refColTags,
|
||||
OnUpdate: onUpdateRefOp,
|
||||
OnDelete: onDeleteRefOp,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot, err = newRoot.PutForeignKeyCollection(ctx, foreignKeyCollection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.db.SetRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.updateFromRoot(ctx, newRoot)
|
||||
}
|
||||
|
||||
func (t *AlterableDoltTable) DropForeignKey(ctx *sql.Context, fkName string) error {
|
||||
root, err := t.db.GetRoot(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fkc, err := root.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
foreignKey, err := fkc.RemoveKey(fkName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot, err := root.PutForeignKeyCollection(ctx, fkc)
|
||||
|
||||
newTable, _, err := t.dropIndex(ctx, t.table, t.sch, foreignKey.TableIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot, err = newRoot.PutTable(ctx, t.name, newTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
referencedTable, ok, err := newRoot.GetTable(ctx, foreignKey.ReferencedTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to find referenced table `%s`", foreignKey.ReferencedTableName)
|
||||
}
|
||||
referencedSch, err := referencedTable.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newOtherTable, _, err := t.dropIndex(ctx, referencedTable, referencedSch, foreignKey.ReferencedTableIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot, err = newRoot.PutTable(ctx, foreignKey.ReferencedTableName, newOtherTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.db.SetRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.updateFromRoot(ctx, newRoot)
|
||||
}
|
||||
|
||||
// createIndex creates the given index on the given table with the given schema. Although this is called on an instance
|
||||
// of AlterableDoltTable, it is only a method to free up the package namespace, and is not dependent (nor modifies) the
|
||||
// instance. Returns the updated table, updated schema, and created index.
|
||||
func (t *AlterableDoltTable) createIndex(
|
||||
ctx *sql.Context,
|
||||
tbl *doltdb.Table,
|
||||
tblSch schema.Schema,
|
||||
hidden bool,
|
||||
indexName string,
|
||||
using sql.IndexUsing,
|
||||
constraint sql.IndexConstraint,
|
||||
columns []sql.IndexColumn,
|
||||
comment string,
|
||||
) (*doltdb.Table, schema.Schema, schema.Index, error) {
|
||||
if constraint != sql.IndexConstraint_None && constraint != sql.IndexConstraint_Unique {
|
||||
return nil, nil, nil, fmt.Errorf("not yet supported")
|
||||
}
|
||||
|
||||
if !hidden && !doltdb.IsValidTableName(indexName) {
|
||||
return nil, nil, nil, fmt.Errorf("invalid index name `%s` as they must match the regular expression %s", indexName, doltdb.TableNameRegexStr)
|
||||
}
|
||||
|
||||
// get the real column names as CREATE INDEX columns are case-insensitive
|
||||
var realColNames []string
|
||||
allTableCols := tblSch.GetAllCols()
|
||||
for _, indexCol := range columns {
|
||||
tableCol, ok := allTableCols.GetByNameCaseInsensitive(indexCol.Name)
|
||||
if !ok {
|
||||
return nil, nil, nil, fmt.Errorf("column `%s` does not exist for the table", indexCol.Name)
|
||||
}
|
||||
realColNames = append(realColNames, tableCol.Name)
|
||||
}
|
||||
|
||||
// create the index metadata, will error if index names are taken or an index with the same columns in the same order exists
|
||||
index, err := tblSch.Indexes().AddIndexByColNames(indexName, realColNames, schema.IndexProperties{IsUnique: constraint == sql.IndexConstraint_Unique, IsHidden: hidden, Comment: comment})
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// update the table schema with the new index
|
||||
newSchemaVal, err := encoding.MarshalSchemaAsNomsValue(ctx, tbl.ValueReadWriter(), tblSch)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
tableRowData, err := tbl.GetRowData(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
indexData, err := tbl.GetIndexData(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
newTable, err := doltdb.NewTable(ctx, tbl.ValueReadWriter(), newSchemaVal, tableRowData, &indexData)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// set the index row data and get a new root with the updated table
|
||||
indexRowData, err := newTable.RebuildIndexRowData(ctx, index.Name())
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
newTable, err = newTable.SetIndexRowData(ctx, index.Name(), indexRowData)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return newTable, tblSch, index, nil
|
||||
}
|
||||
|
||||
// dropIndex drops the given index on the given table with the given schema. Although this is called on an instance
|
||||
// of AlterableDoltTable, it is only a method to free up the package namespace, and is not dependent (nor modifies) the
|
||||
// instance. Returns the updated table and updated schema.
|
||||
func (t *AlterableDoltTable) dropIndex(ctx *sql.Context, tbl *doltdb.Table, tblSch schema.Schema, indexName string) (*doltdb.Table, schema.Schema, error) {
|
||||
// RemoveIndex returns an error if the index does not exist, no need to do twice
|
||||
_, err := tblSch.Indexes().RemoveIndex(indexName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
newTable, err := tbl.UpdateSchema(ctx, tblSch)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
newTable, err = newTable.DeleteIndexRowData(ctx, indexName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return newTable, tblSch, nil
|
||||
}
|
||||
|
||||
func (t *AlterableDoltTable) parseFkReferenceOption(refOp sql.ForeignKeyReferenceOption) (doltdb.ForeignKeyReferenceOption, error) {
|
||||
switch refOp {
|
||||
case sql.ForeignKeyReferenceOption_DefaultAction:
|
||||
return doltdb.ForeignKeyReferenceOption_DefaultAction, nil
|
||||
case sql.ForeignKeyReferenceOption_Restrict:
|
||||
return doltdb.ForeignKeyReferenceOption_Restrict, nil
|
||||
case sql.ForeignKeyReferenceOption_Cascade:
|
||||
return doltdb.ForeignKeyReferenceOption_Cascade, nil
|
||||
case sql.ForeignKeyReferenceOption_NoAction:
|
||||
return doltdb.ForeignKeyReferenceOption_NoAction, nil
|
||||
case sql.ForeignKeyReferenceOption_SetNull:
|
||||
return doltdb.ForeignKeyReferenceOption_SetNull, nil
|
||||
case sql.ForeignKeyReferenceOption_SetDefault:
|
||||
return doltdb.ForeignKeyReferenceOption_DefaultAction, fmt.Errorf(`"SET DEFAULT" is not supported`)
|
||||
default:
|
||||
return doltdb.ForeignKeyReferenceOption_DefaultAction, fmt.Errorf("unknown foreign key reference option: %v", refOp)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *AlterableDoltTable) updateFromRoot(ctx *sql.Context, root *doltdb.RootValue) error {
|
||||
updatedTableSql, ok, err := t.db.getTable(ctx, root, t.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("table `%s` cannot find itself", t.name)
|
||||
}
|
||||
updatedTable := updatedTableSql.(*AlterableDoltTable)
|
||||
t.WritableDoltTable.DoltTable = updatedTable.WritableDoltTable.DoltTable
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/sqle/sqlfmt"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
||||
"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/sqle/sqlfmt"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/utils/filesys"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/utils/iohelp"
|
||||
)
|
||||
@@ -33,13 +33,14 @@ import (
|
||||
// SQL database.
|
||||
type SqlExportWriter struct {
|
||||
tableName string
|
||||
foreignKeys []*doltdb.DisplayForeignKey
|
||||
sch schema.Schema
|
||||
wr io.WriteCloser
|
||||
writtenFirstRow bool
|
||||
}
|
||||
|
||||
// OpenSQLExportWriter returns a new SqlWriter for the table given writing to a file with the path given.
|
||||
func OpenSQLExportWriter(path string, tableName string, fs filesys.WritableFS, sch schema.Schema) (*SqlExportWriter, error) {
|
||||
func OpenSQLExportWriter(path string, tableName string, fs filesys.WritableFS, sch schema.Schema, foreignKeys []*doltdb.DisplayForeignKey) (*SqlExportWriter, error) {
|
||||
err := fs.MkDirs(filepath.Dir(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -50,7 +51,7 @@ func OpenSQLExportWriter(path string, tableName string, fs filesys.WritableFS, s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SqlExportWriter{tableName: tableName, sch: sch, wr: wr}, nil
|
||||
return &SqlExportWriter{tableName: tableName, foreignKeys: foreignKeys, sch: sch, wr: wr}, nil
|
||||
}
|
||||
|
||||
func NewSQLDiffWriter(wr io.WriteCloser, tableName string, sch schema.Schema) (*SqlExportWriter, error) {
|
||||
@@ -83,7 +84,7 @@ func (w *SqlExportWriter) maybeWriteDropCreate() error {
|
||||
var b strings.Builder
|
||||
b.WriteString(sqlfmt.DropTableIfExistsStmt(w.tableName))
|
||||
b.WriteRune('\n')
|
||||
b.WriteString(sqlfmt.CreateTableStmtWithTags(w.tableName, w.sch))
|
||||
b.WriteString(sqlfmt.CreateTableStmtWithTags(w.tableName, w.sch, w.foreignKeys))
|
||||
if err := iohelp.WriteLine(w.wr, b.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestEndToEnd(t *testing.T) {
|
||||
id := uuid.MustParse("00000000-0000-0000-0000-000000000000")
|
||||
tableName := "people"
|
||||
|
||||
dropCreateStatement := sqlfmt.DropTableIfExistsStmt(tableName) + "\n" + sqlfmt.CreateTableStmtWithTags(tableName, dtestutils.TypedSchema)
|
||||
dropCreateStatement := sqlfmt.DropTableIfExistsStmt(tableName) + "\n" + sqlfmt.CreateTableStmtWithTags(tableName, dtestutils.TypedSchema, nil)
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user