Skip to content

Commit

Permalink
sql: allow cascading action when default is set implicitly to null
Browse files Browse the repository at this point in the history
Add support for implicitly setting the default value of a column
to null, for cascading tables for both ON DELETE and ON UPDATE.

The problem was in updateRows within cascader.go when the column ids
were being updated for a table. If a user did not explicitly specify
NULL as the default value, the default expression was dereferenced,
which was null. Instead of deferencing said value, and setting it to the
column id, we now just set the column id to nil if the default
expression is also nil. This is the same behavior for the explicit case.

Resolves: #38975

Release note (sql change): Now supports the implicit setting of NULL
values for foreign keys.
  • Loading branch information
Tyler314 committed Jul 27, 2019
1 parent cfdaadc commit ae33c25
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 89 deletions.
13 changes: 0 additions & 13 deletions pkg/sql/create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,19 +593,6 @@ func ResolveFK(
}
}

// Don't add a SET DEFAULT action on an index that has any column that does
// not have a DEFAULT expression.
if d.Actions.Delete == tree.SetDefault || d.Actions.Update == tree.SetDefault {
for _, sourceColumn := range srcCols {
if sourceColumn.DefaultExpr == nil {
col := qualifyFKColErrorWithDB(ctx, txn, tbl.TableDesc(), sourceColumn.Name)
return pgerror.Newf(pgcode.InvalidForeignKey,
"cannot add a SET DEFAULT cascading action on column %q which has no DEFAULT expression", col,
)
}
}
}

ref := sqlbase.ForeignKeyReference{
Table: target.ID,
Index: targetIdxID,
Expand Down
99 changes: 66 additions & 33 deletions pkg/sql/logictest/testdata/logic_test/fk
Original file line number Diff line number Diff line change
Expand Up @@ -1201,29 +1201,24 @@ CREATE TABLE a (
);

# Create a table with no DEFAULT expressions column and a SET DEFAULT action.
statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.delete_no_default" which has no DEFAULT expression
CREATE TABLE no_default_table (
statement ok
CREATE TABLE delete_no_default_table (
id INT PRIMARY KEY
,delete_no_default INT REFERENCES a ON DELETE SET DEFAULT
);

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.update_no_default" which has no DEFAULT expression
CREATE TABLE no_default_table (
statement ok
CREATE TABLE update_no_default_table (
id INT PRIMARY KEY
,update_no_default INT NOT NULL REFERENCES a ON UPDATE SET DEFAULT
);

# Create a table where the primary key has a SET DEFAULT action.
statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.primary_key_table.id" which has no DEFAULT expression
CREATE TABLE primary_key_table (
statement ok
CREATE TABLE primary_key_table_set_default (
id INT PRIMARY KEY REFERENCES a ON DELETE SET DEFAULT
);

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.primary_key_table.id" which has no DEFAULT expression
CREATE TABLE primary_key_table (
id INT PRIMARY KEY REFERENCES a ON UPDATE SET DEFAULT
);

# Add a SET DEFAULT action after the to a column with no DEFAULT expression.
statement ok
CREATE TABLE no_default_table (
Expand All @@ -1232,12 +1227,12 @@ CREATE TABLE no_default_table (
,update_no_default INT
);

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.delete_no_default" which has no DEFAULT expression
statement ok
ALTER TABLE no_default_table ADD CONSTRAINT no_default_delete_set_default
FOREIGN KEY (delete_no_default) REFERENCES a (id)
ON DELETE SET DEFAULT;

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.update_no_default" which has no DEFAULT expression
statement ok
ALTER TABLE no_default_table ADD CONSTRAINT no_default_update_set_default
FOREIGN KEY (update_no_default) REFERENCES a (id)
ON UPDATE SET DEFAULT;
Expand All @@ -1253,19 +1248,14 @@ CREATE TABLE primary_key_table (
id INT PRIMARY KEY
);

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.primary_key_table.id" which has no DEFAULT expression
statement ok
ALTER TABLE primary_key_table ADD CONSTRAINT no_default_delete_set_default
FOREIGN KEY (id) REFERENCES a (id)
ON DELETE SET DEFAULT;

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.primary_key_table.id" which has no DEFAULT expression
ALTER TABLE primary_key_table ADD CONSTRAINT no_default_update_set_default
FOREIGN KEY (id) REFERENCES a (id)
ON UPDATE SET DEFAULT;

# Clean up the tables used so far.
statement ok
DROP TABLE primary_key_table, a;
DROP TABLE primary_key_table, delete_no_default_table, update_no_default_table, primary_key_table_set_default, a;

# Now test composite foreign keys
statement ok
Expand All @@ -1276,7 +1266,7 @@ CREATE TABLE a (
);

# Create a table with a column without a DEFAULT expression and a SET DEFAULT action.
statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.ref1" which has no DEFAULT expression
statement ok
CREATE TABLE no_default_table (
id INT PRIMARY KEY
,ref1 INT
Expand All @@ -1285,45 +1275,86 @@ CREATE TABLE no_default_table (
,FOREIGN KEY (ref1, ref2) REFERENCES a (id2, id1) ON DELETE SET DEFAULT
);

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.ref1" which has no DEFAULT expression
CREATE TABLE no_default_table (
statement ok
INSERT INTO a VALUES (1, 2)

statement ok
INSERT INTO a VALUES (3, 4)

statement ok
INSERT INTO no_default_table VALUES (6, 2, 1)

query III colnames
SELECT * FROM no_default_table
----
id ref1 ref2
6 2 1

statement ok
DELETE FROM a WHERE id1=1

query III colnames
SELECT * FROM no_default_table
----
id ref1 ref2
6 NULL NULL

statement ok
CREATE TABLE no_default_table_on_update (
id INT PRIMARY KEY
,ref1 INT
,ref2 INT
,INDEX (ref1, ref2)
,FOREIGN KEY (ref1, ref2) REFERENCES a (id2, id1) ON UPDATE SET DEFAULT
);

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.ref1" which has no DEFAULT expression
CREATE TABLE no_default_table (
statement ok
INSERT INTO no_default_table_on_update VALUES (0, 4, 3)

query III colnames
SELECT * FROM no_default_table_on_update
----
id ref1 ref2
0 4 3

statement ok
UPDATE a SET id1=33, id2=44 WHERE id1=3;

query III colnames
SELECT * FROM no_default_table_on_update
----
id ref1 ref2
0 NULL NULL

statement ok
CREATE TABLE no_default_table_ref2_default_on_delete (
id INT PRIMARY KEY
,ref1 INT
,ref2 INT DEFAULT 1
,INDEX (ref1, ref2)
,FOREIGN KEY (ref1, ref2) REFERENCES a (id2, id1) ON DELETE SET DEFAULT
);


statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.ref1" which has no DEFAULT expression
CREATE TABLE no_default_table (
statement ok
CREATE TABLE no_default_table_ref2_default_on_update (
id INT PRIMARY KEY
,ref1 INT
,ref2 INT DEFAULT 1
,INDEX (ref1, ref2)
,FOREIGN KEY (ref1, ref2) REFERENCES a (id2, id1) ON UPDATE SET DEFAULT
);

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.ref2" which has no DEFAULT expression
CREATE TABLE no_default_table (
statement ok
CREATE TABLE no_default_table_ref1_default_on_delete (
id INT PRIMARY KEY
,ref1 INT DEFAULT 1
,ref2 INT
,INDEX (ref1, ref2)
,FOREIGN KEY (ref1, ref2) REFERENCES a (id2, id1) ON DELETE SET DEFAULT
);

statement error pq: cannot add a SET DEFAULT cascading action on column "test.public.no_default_table.ref2" which has no DEFAULT expression
CREATE TABLE no_default_table (
statement ok
CREATE TABLE no_default_table_ref1_default_on_update (
id INT PRIMARY KEY
,ref1 INT DEFAULT 1
,ref2 INT
Expand All @@ -1333,7 +1364,9 @@ CREATE TABLE no_default_table (

# Clean up after the test.
statement ok
DROP TABLE a;
DROP TABLE a, no_default_table, no_default_table_on_update, no_default_table_ref2_default_on_delete,
no_default_table_ref2_default_on_update, no_default_table_ref1_default_on_delete,
no_default_table_ref1_default_on_update

# Test that no column can have more than 1 FK constraint
subtest column_uniqueness
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/logictest/testdata/logic_test/table
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ query TT
SHOW CREATE TABLE test.null_default
----
test.public.null_default CREATE TABLE null_default (
ts TIMESTAMP NULL DEFAULT NULL,
ts TIMESTAMP NULL,
FAMILY "primary" (ts, rowid)
)

Expand Down
5 changes: 5 additions & 0 deletions pkg/sql/row/cascader.go
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,11 @@ func (c *cascader) updateRows(
if err != nil {
return nil, nil, nil, 0, err
}
// If the default expression is nil, treat it as a SET NULL case
if column.DefaultExpr == nil {
referencingIndexValuesByColIDs[columnID] = tree.DNull
continue
}
parsedExpr, err := parser.ParseExpr(*column.DefaultExpr)
if err != nil {
return nil, nil, nil, 0, err
Expand Down
10 changes: 0 additions & 10 deletions pkg/sql/sqlbase/structured.go
Original file line number Diff line number Diff line change
Expand Up @@ -2643,16 +2643,6 @@ func (desc *ImmutableTableDescriptor) MakeFirstMutationPublic(
// ColumnNeedsBackfill returns true if adding the given column requires a
// backfill (dropping a column always requires a backfill).
func ColumnNeedsBackfill(desc *ColumnDescriptor) bool {
// Consider the case where the user explicitly states the default value of a
// new column to be NULL. desc.DefaultExpr is not nil, but the string "NULL"
if desc.DefaultExpr != nil {
if defaultExpr, err := parser.ParseExpr(*desc.DefaultExpr); err != nil {
panic(errors.NewAssertionErrorWithWrappedErrf(err,
"failed to parse default expression %s", *desc.DefaultExpr))
} else if defaultExpr == tree.DNull {
return false
}
}
return desc.DefaultExpr != nil || !desc.Nullable || desc.IsComputed()
}

Expand Down
27 changes: 0 additions & 27 deletions pkg/sql/sqlbase/structured_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1307,30 +1307,3 @@ func TestKeysPerRow(t *testing.T) {
})
}
}

func TestColumnNeedsBackfill(t *testing.T) {
// Define variable strings here such that we can pass their address below
null := "NULL"
four := "4:::INT8"
// Create Column Descriptors that reflect the definition of a column with a
// default value of NULL that was set implicitly, one that was set explicitly,
// and one that has an INT default value, respectively.
implicitNull := &ColumnDescriptor{Name: "im", ID: 2, DefaultExpr: nil, Nullable: true, ComputeExpr: nil}
explicitNull := &ColumnDescriptor{Name: "ex", ID: 3, DefaultExpr: &null, Nullable: true, ComputeExpr: nil}
defaultNotNull := &ColumnDescriptor{Name: "four", ID: 4, DefaultExpr: &four, Nullable: true, ComputeExpr: nil}
// Verify that a backfill doesn't occur according to the ColumnNeedsBackfill
// function for the default NULL values, and that it does occur for an INT
// default value.
if ColumnNeedsBackfill(implicitNull) != false {
t.Fatal("Expected implicit SET DEFAULT NULL to not require a backfill," +
" ColumnNeedsBackfill states that it does.")
}
if ColumnNeedsBackfill(explicitNull) != false {
t.Fatal("Expected explicit SET DEFAULT NULL to not require a backfill," +
" ColumnNeedsBackfill states that it does.")
}
if ColumnNeedsBackfill(defaultNotNull) != true {
t.Fatal("Expected explicit SET DEFAULT NULL to require a backfill," +
" ColumnNeedsBackfill states that it does not.")
}
}
18 changes: 13 additions & 5 deletions pkg/sql/sqlbase/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,20 @@ func MakeColumnDefDescs(
); err != nil {
return nil, nil, nil, err
}
// We keep the type checked expression so that the type annotation
// gets properly stored.
d.DefaultExpr.Expr = typedExpr

s := tree.Serialize(d.DefaultExpr.Expr)
col.DefaultExpr = &s
// Set the default expression of the column descriptor to nil, if it is in
// fact "NULL". It would be "NULL" instead of nil is the client explicitly
// set the default to NULL.
if d.DefaultExpr.Expr == tree.DNull {
d.DefaultExpr.Expr = nil
col.DefaultExpr = nil
} else {
// Otherwise, we keep the type checked expression so that the type
// annotation gets properly stored.
d.DefaultExpr.Expr = typedExpr
s := tree.Serialize(d.DefaultExpr.Expr)
col.DefaultExpr = &s
}
}

if d.IsComputed() {
Expand Down

0 comments on commit ae33c25

Please sign in to comment.