mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-10 08:49:43 -06:00
Foreign key remnants
This commit is contained in:
committed by
Daylon Wilkins
parent
e6965be63d
commit
eb65425e87
@@ -634,6 +634,55 @@ SQL
|
||||
[[ "$output" =~ "does not have column" ]] || false
|
||||
}
|
||||
|
||||
@test "foreign-keys: CREATE TABLE SET NULL on non-nullable column" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
id BIGINT PRIMARY KEY,
|
||||
extra BIGINT
|
||||
);
|
||||
SQL
|
||||
|
||||
run dolt sql <<SQL
|
||||
CREATE TABLE child (
|
||||
id BIGINT PRIMARY KEY,
|
||||
parent_extra BIGINT NOT NULL,
|
||||
CONSTRAINT fk_name FOREIGN KEY (parent_extra)
|
||||
REFERENCES parent(extra)
|
||||
ON DELETE SET NULL
|
||||
);
|
||||
SQL
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "SET NULL" ]] || false
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
|
||||
run dolt sql <<SQL
|
||||
CREATE TABLE child (
|
||||
id BIGINT PRIMARY KEY,
|
||||
parent_extra BIGINT NOT NULL,
|
||||
CONSTRAINT fk_name FOREIGN KEY (parent_extra)
|
||||
REFERENCES parent(extra)
|
||||
ON UPDATE SET NULL
|
||||
);
|
||||
SQL
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "SET NULL" ]] || false
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
|
||||
run dolt sql <<SQL
|
||||
CREATE TABLE child (
|
||||
id BIGINT PRIMARY KEY,
|
||||
parent_extra BIGINT NOT NULL,
|
||||
CONSTRAINT fk_name FOREIGN KEY (parent_extra)
|
||||
REFERENCES parent(extra)
|
||||
ON DELETE SET NULL
|
||||
ON UPDATE SET NULL
|
||||
);
|
||||
SQL
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "SET NULL" ]] || false
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
}
|
||||
|
||||
@test "foreign-keys: ALTER TABLE Single Unnamed FOREIGN KEY" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
@@ -1149,6 +1198,54 @@ SQL
|
||||
[ "$status" -eq "1" ]
|
||||
}
|
||||
|
||||
@test "foreign-keys: ALTER TABLE SET NULL on non-nullable column" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
id BIGINT PRIMARY KEY,
|
||||
extra BIGINT
|
||||
);
|
||||
CREATE TABLE child (
|
||||
id BIGINT PRIMARY KEY,
|
||||
parent_extra BIGINT NOT NULL
|
||||
);
|
||||
SQL
|
||||
|
||||
run dolt sql -q "ALTER TABLE child ADD CONSTRAINT fk_name FOREIGN KEY (parent_extra) REFERENCES parent(extra) ON DELETE SET NULL"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "SET NULL" ]] || false
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
|
||||
run dolt sql -q "ALTER TABLE child ADD CONSTRAINT fk_name FOREIGN KEY (parent_extra) REFERENCES parent(extra) ON UPDATE SET NULL"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "SET NULL" ]] || false
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
|
||||
run dolt sql -q "ALTER TABLE child ADD CONSTRAINT fk_name FOREIGN KEY (parent_extra) REFERENCES parent(extra) ON DELETE SET NULL ON UPDATE SET NULL"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "SET NULL" ]] || false
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
}
|
||||
|
||||
@test "foreign-keys: ADD FOREIGN KEY fails on existing table when data would cause violation" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
pk BIGINT PRIMARY KEY,
|
||||
v1 BIGINT
|
||||
);
|
||||
CREATE TABLE child (
|
||||
pk BIGINT PRIMARY KEY,
|
||||
v1 BIGINT
|
||||
);
|
||||
INSERT INTO parent VALUES (1, 1), (2, 2);
|
||||
INSERT INTO child VALUES (1, 1), (2, 3);
|
||||
SQL
|
||||
|
||||
run dolt sql -q "ALTER TABLE child ADD CONSTRAINT fk_name FOREIGN KEY (v1) REFERENCES parent(v1)"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "violation" ]] || false
|
||||
[[ "$output" =~ "fk_name" ]] || false
|
||||
}
|
||||
|
||||
@test "foreign-keys: RENAME TABLE" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
@@ -1258,6 +1355,36 @@ SQL
|
||||
dolt table rm parent
|
||||
}
|
||||
|
||||
@test "foreign-keys: dolt table cp" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE one (
|
||||
id BIGINT PRIMARY KEY,
|
||||
extra BIGINT
|
||||
);
|
||||
CREATE TABLE two (
|
||||
id BIGINT PRIMARY KEY,
|
||||
one_extra BIGINT,
|
||||
FOREIGN KEY (one_extra)
|
||||
REFERENCES one(extra)
|
||||
);
|
||||
SQL
|
||||
|
||||
dolt table cp two two_new
|
||||
run dolt index ls two_new
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "No indexes" ]] || false
|
||||
run dolt schema show two_new
|
||||
[ "$status" -eq "0" ]
|
||||
! [[ "$output" =~ "FOREIGN KEY" ]] || false
|
||||
|
||||
run dolt index ls two
|
||||
[ "$status" -eq "0" ]
|
||||
! [[ "$output" =~ "No indexes" ]] || false
|
||||
run dolt schema show two
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "FOREIGN KEY" ]] || false
|
||||
}
|
||||
|
||||
@test "foreign-keys: ALTER TABLE RENAME COLUMN" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
@@ -1278,6 +1405,63 @@ SQL
|
||||
[[ `echo "$output" | tr -d "\n" | tr -s " "` =~ 'CONSTRAINT `fk_child_parent_1` FOREIGN KEY (`parent_id_new`) REFERENCES `parent` (`id_new`)' ]] || false
|
||||
}
|
||||
|
||||
@test "foreign-keys: DROP COLUMN" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
id BIGINT PRIMARY KEY,
|
||||
extra BIGINT
|
||||
);
|
||||
CREATE TABLE child (
|
||||
id BIGINT PRIMARY KEY,
|
||||
parent_extra BIGINT,
|
||||
CONSTRAINT fk_name FOREIGN KEY (parent_extra)
|
||||
REFERENCES parent(extra)
|
||||
);
|
||||
SQL
|
||||
|
||||
dolt add -A
|
||||
dolt commit -m "initial commit"
|
||||
run dolt sql -q "ALTER TABLE parent DROP COLUMN extra"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "extra" ]] || false
|
||||
dolt sql -q "ALTER TABLE child DROP FOREIGN KEY fk_name"
|
||||
dolt sql -q "ALTER TABLE parent DROP COLUMN extra"
|
||||
|
||||
dolt reset --hard
|
||||
run dolt sql -q "ALTER TABLE child DROP COLUMN parent_extra"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
dolt sql -q "ALTER TABLE child DROP FOREIGN KEY fk_name"
|
||||
dolt sql -q "ALTER TABLE child DROP COLUMN parent_extra"
|
||||
}
|
||||
|
||||
@test "foreign-keys: Disallow change column type when SET NULL" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
id BIGINT PRIMARY KEY,
|
||||
extra BIGINT
|
||||
);
|
||||
CREATE TABLE child (
|
||||
id BIGINT PRIMARY KEY,
|
||||
parent_extra BIGINT,
|
||||
CONSTRAINT fk_name FOREIGN KEY (parent_extra)
|
||||
REFERENCES parent(extra)
|
||||
ON DELETE SET NULL
|
||||
ON UPDATE SET NULL
|
||||
);
|
||||
SQL
|
||||
|
||||
run dolt sql -q "ALTER TABLE child CHANGE COLUMN parent_extra parent_extra BIGINT"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "SET NULL" ]] || false
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
|
||||
run dolt sql -q "ALTER TABLE child CHANGE COLUMN parent_extra parent_extra BIGINT NULL"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "SET NULL" ]] || false
|
||||
[[ "$output" =~ "parent_extra" ]] || false
|
||||
}
|
||||
|
||||
@test "foreign-keys: SQL CASCADE" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE one (
|
||||
@@ -1449,6 +1633,39 @@ SQL
|
||||
[[ "$output" =~ "violation" ]] || false
|
||||
}
|
||||
|
||||
@test "foreign-keys: SQL INSERT multiple keys violates only one" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE one (
|
||||
pk BIGINT PRIMARY KEY,
|
||||
v1 BIGINT,
|
||||
v2 BIGINT
|
||||
);
|
||||
CREATE TABLE two (
|
||||
pk BIGINT PRIMARY KEY,
|
||||
v1 BIGINT,
|
||||
v2 BIGINT,
|
||||
CONSTRAINT fk_name_1 FOREIGN KEY (v1)
|
||||
REFERENCES one(v1),
|
||||
CONSTRAINT fk_name_2 FOREIGN KEY (v2)
|
||||
REFERENCES one(v2)
|
||||
);
|
||||
INSERT INTO one VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3);
|
||||
INSERT INTO two VALUES (1, NULL, 1);
|
||||
SQL
|
||||
|
||||
run dolt sql -q "INSERT INTO two VALUES (2, NULL, 4)"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "violation" ]] || false
|
||||
[[ "$output" =~ "fk_name_2" ]] || false
|
||||
|
||||
run dolt sql -q "INSERT INTO two VALUES (3, 4, NULL)"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "violation" ]] || false
|
||||
[[ "$output" =~ "fk_name_1" ]] || false
|
||||
|
||||
dolt sql -q "INSERT INTO two VALUES (4, NULL, NULL)" # sanity check
|
||||
}
|
||||
|
||||
@test "foreign-keys: dolt table import" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent (
|
||||
@@ -1923,7 +2140,7 @@ SQL
|
||||
dolt checkout other
|
||||
|
||||
dolt sql <<SQL
|
||||
ALTER TABLE child DROP FOREIGN KEY fk_name;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
UPDATE parent SET v1 = v1 - 1;
|
||||
SQL
|
||||
dolt add -A
|
||||
@@ -1976,7 +2193,7 @@ SQL
|
||||
dolt checkout other
|
||||
|
||||
dolt sql <<SQL
|
||||
ALTER TABLE child DROP FOREIGN KEY fk_name;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
UPDATE parent SET v1 = v1 - 1;
|
||||
SQL
|
||||
dolt add -A
|
||||
@@ -2016,7 +2233,7 @@ SQL
|
||||
dolt checkout other
|
||||
|
||||
dolt sql <<SQL
|
||||
ALTER TABLE child DROP FOREIGN KEY fk_name;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
UPDATE child SET v1 = v1 + 1;
|
||||
SQL
|
||||
dolt add -A
|
||||
@@ -2069,7 +2286,7 @@ SQL
|
||||
dolt checkout other
|
||||
|
||||
dolt sql <<SQL
|
||||
ALTER TABLE child DROP FOREIGN KEY fk_name;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
UPDATE child SET v1 = v1 - 1;
|
||||
SQL
|
||||
dolt add -A
|
||||
@@ -2109,7 +2326,7 @@ SQL
|
||||
dolt checkout other
|
||||
|
||||
dolt sql <<SQL
|
||||
ALTER TABLE child DROP FOREIGN KEY fk_name;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
UPDATE parent SET v1 = v1 - 1;
|
||||
UPDATE child SET v1 = v1 + 1;
|
||||
SQL
|
||||
@@ -2164,7 +2381,7 @@ SQL
|
||||
dolt checkout other
|
||||
|
||||
dolt sql <<SQL
|
||||
ALTER TABLE child DROP FOREIGN KEY fk_name;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
UPDATE parent SET v1 = v1 - 1;
|
||||
UPDATE child SET v1 = v1 + 1;
|
||||
SQL
|
||||
|
||||
@@ -578,7 +578,7 @@ func createEnvWithSeedData(t *testing.T) *env.DoltEnv {
|
||||
}
|
||||
|
||||
wrSch := wr.GetSchema()
|
||||
wrSch.Indexes().Merge(sch.Indexes().AllIndexes()...)
|
||||
wrSch.Indexes().Merge(true, sch.Indexes().AllIndexes()...)
|
||||
err = dEnv.PutTableToWorking(context.Background(), *wr.GetMap(), wrSch, tableName)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -231,7 +231,7 @@ func newTableCopyDataMover(ctx context.Context, root *doltdb.RootValue, fs files
|
||||
}
|
||||
|
||||
newTblSch := schema.SchemaFromCols(cc)
|
||||
newTblSch.Indexes().Merge(oldTblSch.Indexes().AllIndexes()...)
|
||||
newTblSch.Indexes().Merge(false, oldTblSch.Indexes().AllIndexes()...)
|
||||
|
||||
transforms, err := mvdata.NameMapTransform(oldTblSch, newTblSch, make(rowconv.NameMapper))
|
||||
|
||||
|
||||
@@ -23,13 +23,14 @@
|
||||
package eventsapi
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
duration "github.com/golang/protobuf/ptypes/duration"
|
||||
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -4,6 +4,7 @@ package eventsapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
package eventsapi
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
package remotesapi
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -4,6 +4,7 @@ package remotesapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
package remotesapi
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -4,6 +4,7 @@ package remotesapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
|
||||
@@ -17,10 +17,12 @@ package doltdb
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"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/table/typed/noms"
|
||||
"github.com/liquidata-inc/dolt/go/store/marshal"
|
||||
"github.com/liquidata-inc/dolt/go/store/types"
|
||||
)
|
||||
@@ -298,6 +300,73 @@ func (fkc *ForeignKeyCollection) Stage(ctx context.Context, fksToAdd []*ForeignK
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateData ensures that the foreign key is valid by comparing the index data from the given table against the index
|
||||
// data from the referenced table.
|
||||
func (fk *ForeignKey) ValidateData(ctx context.Context, tableIndexData types.Map, refTableIndex schema.Index, refTableIndexData types.Map) error {
|
||||
if fk.ReferencedTableIndex != refTableIndex.Name() {
|
||||
return fmt.Errorf("cannot validate data as wrong referenced index was given: expected `%s` but received `%s`",
|
||||
fk.ReferencedTableIndex, refTableIndex.Name())
|
||||
}
|
||||
refTableIndexSch := refTableIndex.Schema()
|
||||
err := tableIndexData.Iter(ctx, func(key, value types.Value) (stop bool, err error) {
|
||||
indexTaggedValues, err := row.ParseTaggedValues(key.(types.Tuple))
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
refIndexKeyVals := make([]types.Value, len(fk.TableColumns)*2)
|
||||
for i, colTag := range fk.TableColumns {
|
||||
val, ok := indexTaggedValues[colTag]
|
||||
if !ok {
|
||||
return true, fmt.Errorf("cannot find value for tag `%d` on table `%s`", colTag, fk.TableName)
|
||||
}
|
||||
newTag := fk.ReferencedTableColumns[i]
|
||||
refIndexKeyVals[2*i] = types.Uint(newTag)
|
||||
refIndexKeyVals[2*i+1] = val
|
||||
}
|
||||
refIndexKey, err := types.NewTuple(refTableIndexData.Format(), refIndexKeyVals...)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
indexIter := noms.NewNomsRangeReader(refTableIndexSch, refTableIndexData,
|
||||
[]*noms.ReadRange{{Start: refIndexKey, Inclusive: true, Reverse: false, Check: func(tuple types.Tuple) (bool, error) {
|
||||
return tuple.StartsWith(refIndexKey), nil
|
||||
}}},
|
||||
)
|
||||
_, err = indexIter.ReadRow(ctx)
|
||||
if err == nil { // row exists
|
||||
return false, nil
|
||||
} else if err != io.EOF {
|
||||
return true, err
|
||||
} else {
|
||||
indexKeyStr, _ := types.EncodedValue(ctx, refIndexKey)
|
||||
return true, fmt.Errorf("foreign key violation on `%s`.`%s`: `%s`", fk.Name, fk.TableName, indexKeyStr)
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Equals returns whether the given foreign key is equivalent to another. As tags are unique, we can compare using those
|
||||
// and ignore the table names, ensuring equality even through table and column renames.
|
||||
func (fk *ForeignKey) Equals(other *ForeignKey) bool {
|
||||
if len(fk.TableColumns) != len(other.TableColumns) || len(fk.ReferencedTableColumns) != len(other.ReferencedTableColumns) {
|
||||
return false
|
||||
}
|
||||
for i := range fk.TableColumns {
|
||||
if fk.TableColumns[i] != other.TableColumns[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for i := range fk.ReferencedTableColumns {
|
||||
if fk.ReferencedTableColumns[i] != other.ReferencedTableColumns[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return fk.Name == other.Name &&
|
||||
fk.OnUpdate == other.OnUpdate &&
|
||||
fk.OnDelete == other.OnDelete
|
||||
}
|
||||
|
||||
// ValidateReferencedTableSchema verifies that the given schema matches the expectation of the referenced table.
|
||||
func (fk *ForeignKey) ValidateReferencedTableSchema(sch schema.Schema) error {
|
||||
allSchCols := sch.GetAllCols()
|
||||
|
||||
@@ -315,24 +315,12 @@ func (ste *SessionedTableEditor) validateForInsert(ctx context.Context, dRow row
|
||||
return nil
|
||||
}
|
||||
for _, foreignKey := range ste.referencedTables {
|
||||
// first check if it's all nulls, as they are always valid to INSERT
|
||||
allNulls := true
|
||||
for _, colTag := range foreignKey.TableColumns {
|
||||
val, ok := dRow.GetColVal(colTag)
|
||||
if ok && !types.IsNull(val) {
|
||||
allNulls = false
|
||||
}
|
||||
}
|
||||
if allNulls {
|
||||
return nil
|
||||
}
|
||||
|
||||
indexKey, hasNulls, err := ste.reduceRowAndConvert(ste.tableEditor.nbf, foreignKey.TableColumns, foreignKey.ReferencedTableColumns, dRow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasNulls {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
referencingSte, ok := ste.tableEditSession.tables[foreignKey.ReferencedTableName]
|
||||
if !ok {
|
||||
|
||||
@@ -114,7 +114,11 @@ func (merger *Merger) MergeTable(ctx context.Context, tblName string, tableEditS
|
||||
if h != mh {
|
||||
ms, err = calcTableMergeStats(ctx, tbl, mergeTbl)
|
||||
}
|
||||
|
||||
// force load the table editor since this counts as a change
|
||||
_, err := tableEditSession.GetTableEditor(ctx, tblName, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return mergeTbl, &ms, nil
|
||||
} else if mh == anch {
|
||||
return tbl, &MergeStats{Operation: TableUnmodified}, nil
|
||||
|
||||
@@ -17,6 +17,7 @@ package alterschema
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/liquidata-inc/dolt/go/libraries/doltcore/row"
|
||||
@@ -26,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// DropColumn drops a column from a table, and removes its associated cell values
|
||||
func DropColumn(ctx context.Context, tbl *doltdb.Table, colName string) (*doltdb.Table, error) {
|
||||
func DropColumn(ctx context.Context, tbl *doltdb.Table, colName string, foreignKeys []*doltdb.ForeignKey) (*doltdb.Table, error) {
|
||||
if tbl == nil {
|
||||
panic("invalid parameters")
|
||||
}
|
||||
@@ -48,6 +49,19 @@ func DropColumn(ctx context.Context, tbl *doltdb.Table, colName string) (*doltdb
|
||||
dropTag = col.Tag
|
||||
}
|
||||
|
||||
for _, foreignKey := range foreignKeys {
|
||||
for _, fkTag := range foreignKey.TableColumns {
|
||||
if dropTag == fkTag {
|
||||
return nil, fmt.Errorf("cannot drop column `%s` as it is used in foreign key `%d`", colName, dropTag)
|
||||
}
|
||||
}
|
||||
for _, fkTag := range foreignKey.ReferencedTableColumns {
|
||||
if dropTag == fkTag {
|
||||
return nil, fmt.Errorf("cannot drop column `%s` as it is used in foreign key `%d`", colName, dropTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, index := range tblSch.Indexes().IndexesWithColumn(colName) {
|
||||
_, err = tblSch.Indexes().RemoveIndex(index.Name())
|
||||
if err != nil {
|
||||
|
||||
@@ -112,12 +112,11 @@ func TestDropColumn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
root, err := dEnv.WorkingRoot(ctx)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
tbl, _, err := root.GetTable(ctx, tableName)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedTable, err := DropColumn(ctx, tbl, tt.colName)
|
||||
updatedTable, err := DropColumn(ctx, tbl, tt.colName, nil)
|
||||
if len(tt.expectedErr) > 0 {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
@@ -187,12 +186,11 @@ func TestDropColumnUsedByIndex(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
root, err := dEnv.WorkingRoot(ctx)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
tbl, _, err := root.GetTable(ctx, tableName)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedTable, err := DropColumn(ctx, tbl, tt.colName)
|
||||
updatedTable, err := DropColumn(ctx, tbl, tt.colName, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
sch, err := updatedTable.GetSchema(ctx)
|
||||
|
||||
@@ -53,7 +53,7 @@ type IndexCollection interface {
|
||||
// rather than by tag number, which allows an index from a different table to be added as long as they have matching
|
||||
// column names. If an index with the same name or column structure already exists, or the index contains different
|
||||
// columns, then it is skipped.
|
||||
Merge(indexes ...Index)
|
||||
Merge(includeHidden bool, indexes ...Index)
|
||||
// RemoveIndex removes an index from the table metadata.
|
||||
RemoveIndex(indexName string) (Index, error)
|
||||
// RenameIndex renames an index in the table metadata.
|
||||
@@ -237,13 +237,17 @@ func (ixc *indexCollectionImpl) IndexesWithTag(tag uint64) []Index {
|
||||
return indexes
|
||||
}
|
||||
|
||||
func (ixc *indexCollectionImpl) Merge(indexes ...Index) {
|
||||
func (ixc *indexCollectionImpl) Merge(includeHidden bool, indexes ...Index) {
|
||||
for _, index := range indexes {
|
||||
if !includeHidden && index.IsHidden() {
|
||||
continue
|
||||
}
|
||||
if tags, ok := ixc.columnNamesToTags(index.ColumnNames()); ok && !ixc.Contains(index.Name()) {
|
||||
newIndex := &indexImpl{
|
||||
name: index.Name(),
|
||||
tags: tags,
|
||||
indexColl: ixc,
|
||||
isHidden: index.IsHidden(),
|
||||
isUnique: index.IsUnique(),
|
||||
comment: index.Comment(),
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package sqle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/liquidata-inc/go-mysql-server/sql"
|
||||
|
||||
@@ -223,6 +224,25 @@ func (sess *DoltSession) Set(ctx context.Context, key string, typ sql.Type, valu
|
||||
return nil
|
||||
}
|
||||
|
||||
if key == "foreign_key_checks" {
|
||||
convertedVal, err := sql.Int64.Convert(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
intVal := convertedVal.(int64)
|
||||
if intVal == 0 {
|
||||
for _, tableEditSession := range sess.dbEditors {
|
||||
tableEditSession.Props.ForeignKeyChecksDisabled = true
|
||||
}
|
||||
} else if intVal == 1 {
|
||||
for _, tableEditSession := range sess.dbEditors {
|
||||
tableEditSession.Props.ForeignKeyChecksDisabled = false
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("variable 'foreign_key_checks' can't be set to the value of '%d'", intVal)
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Session.Set(ctx, key, typ, value)
|
||||
}
|
||||
|
||||
|
||||
@@ -371,7 +371,13 @@ func (t *AlterableDoltTable) DropColumn(ctx *sql.Context, columnName string) err
|
||||
return err
|
||||
}
|
||||
|
||||
updatedTable, err = alterschema.DropColumn(ctx, updatedTable, columnName)
|
||||
fkCollection, err := root.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
declaresFk, referencesFk := fkCollection.KeysForTable(t.name)
|
||||
|
||||
updatedTable, err = alterschema.DropColumn(ctx, updatedTable, columnName, append(declaresFk, referencesFk...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -425,6 +431,18 @@ func (t *AlterableDoltTable) ModifyColumn(ctx *sql.Context, columnName string, c
|
||||
}
|
||||
}
|
||||
|
||||
fkCollection, err := root.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
declaresFk, _ := fkCollection.KeysForTable(t.name)
|
||||
for _, foreignKey := range declaresFk {
|
||||
if (foreignKey.OnUpdate == doltdb.ForeignKeyReferenceOption_SetNull || foreignKey.OnDelete == doltdb.ForeignKeyReferenceOption_SetNull) &&
|
||||
col.IsNullable() {
|
||||
return fmt.Errorf("foreign key `%s` has SET NULL thus column `%s` cannot be altered to accept null values", foreignKey.Name, col.Name)
|
||||
}
|
||||
}
|
||||
|
||||
updatedTable, err := alterschema.ModifyColumn(ctx, table, existingCol, col, defVal, orderToOrder(order))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -547,6 +565,10 @@ func (t *AlterableDoltTable) CreateForeignKey(ctx *sql.Context, fkName string, c
|
||||
//TODO: fix go-mysql-server equivalent check, needs two vals
|
||||
return fmt.Errorf("table `%s` does not have column `%s`", t.name, col)
|
||||
}
|
||||
if (onUpdate == sql.ForeignKeyReferenceOption_SetNull || onDelete == sql.ForeignKeyReferenceOption_SetNull) &&
|
||||
!tableCol.IsNullable() {
|
||||
return fmt.Errorf("cannot use SET NULL as column `%s` is non-nullable", tableCol.Name)
|
||||
}
|
||||
tblCols[i] = tableCol
|
||||
colTags[i] = tableCol.Tag
|
||||
sqlColNames[i] = sql.IndexColumn{
|
||||
@@ -568,7 +590,6 @@ func (t *AlterableDoltTable) CreateForeignKey(ctx *sql.Context, fkName string, c
|
||||
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()
|
||||
@@ -615,7 +636,7 @@ func (t *AlterableDoltTable) CreateForeignKey(ctx *sql.Context, fkName string, c
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = foreignKeyCollection.AddKey(&doltdb.ForeignKey{
|
||||
foreignKey := &doltdb.ForeignKey{
|
||||
Name: fkName,
|
||||
TableName: t.name,
|
||||
TableIndex: tableIndex.Name(),
|
||||
@@ -625,7 +646,8 @@ func (t *AlterableDoltTable) CreateForeignKey(ctx *sql.Context, fkName string, c
|
||||
ReferencedTableColumns: refColTags,
|
||||
OnUpdate: onUpdateRefOp,
|
||||
OnDelete: onDeleteRefOp,
|
||||
})
|
||||
}
|
||||
err = foreignKeyCollection.AddKey(foreignKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -634,6 +656,19 @@ func (t *AlterableDoltTable) CreateForeignKey(ctx *sql.Context, fkName string, c
|
||||
return err
|
||||
}
|
||||
|
||||
tableIndexData, err := newTable.GetIndexRowData(ctx, tableIndex.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refTableIndexData, err := newRefTable.GetIndexRowData(ctx, refTableIndex.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = foreignKey.ValidateData(ctx, tableIndexData, refTableIndex, refTableIndexData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.db.SetRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user