Commit 185c0ab2 authored by Radu Berinde's avatar Radu Berinde

opt: ON UPDATE cascades for Upsert

This change implements ON UPDATE actions for Upsert operations. The existing
machinery for Update can be used without modification.

Release note: None
parent cca10937
......@@ -3786,3 +3786,215 @@ SELECT * FROM self_abcd ORDER BY (a, b, c, d)
# Clean up.
statement ok
DROP TABLE self_abcd
subtest UpsertCascade
statement ok
CREATE TABLE parent (pk INT PRIMARY KEY, p INT UNIQUE)
statement ok
CREATE TABLE child (pk INT PRIMARY KEY, p INT REFERENCES parent(p) ON UPDATE CASCADE)
statement ok
INSERT INTO parent VALUES (1, 1), (2, 2);
INSERT INTO child VALUES (1, 1), (2, 1), (3, 2), (4, 2)
statement ok
UPSERT INTO parent VALUES (2, 20), (3, 3)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 20
4 20
statement ok
INSERT INTO parent VALUES (1, 1), (4, 4) ON CONFLICT (pk) DO UPDATE SET p = parent.pk * 10
query II rowsort
SELECT * FROM child
----
1 10
2 10
3 20
4 20
statement ok
INSERT INTO parent VALUES (100, 20) ON CONFLICT(p) DO UPDATE SET p = 50
query II rowsort
SELECT * FROM child
----
1 10
2 10
3 50
4 50
statement ok
DROP TABLE child, parent
subtest UpsertMultiColCascade
statement ok
CREATE TABLE parent (pk INT PRIMARY KEY, p INT, q INT, UNIQUE (p,q))
statement ok
CREATE TABLE child (pk INT PRIMARY KEY, p INT, q INT, CONSTRAINT fk FOREIGN KEY (p,q) REFERENCES parent(p,q) ON UPDATE CASCADE)
statement ok
INSERT INTO parent VALUES (1, 1, 1), (2, 2, 2);
INSERT INTO child VALUES (1, 1, 1), (2, 1, 1), (3, 2, 2), (4, 2, 2)
statement ok
UPSERT INTO parent(pk, p) VALUES (1, 1)
query III rowsort
SELECT * FROM child
----
1 1 1
2 1 1
3 2 2
4 2 2
statement ok
UPSERT INTO parent(pk, q) VALUES (2, 20)
query III rowsort
SELECT * FROM child
----
1 1 1
2 1 1
3 2 20
4 2 20
statement ok
UPSERT INTO parent VALUES (1, 10, 10)
query III rowsort
SELECT * FROM child
----
1 10 10
2 10 10
3 2 20
4 2 20
statement ok
DROP TABLE child, parent
subtest UpsertSetNull
statement ok
CREATE TABLE parent (pk INT PRIMARY KEY, p INT UNIQUE)
statement ok
CREATE TABLE child (pk INT PRIMARY KEY, p INT REFERENCES parent(p) ON UPDATE SET NULL)
statement ok
INSERT INTO parent VALUES (1, 1), (2, 2);
INSERT INTO child VALUES (1, 1), (2, 1), (3, 2), (4, 2)
statement ok
UPSERT INTO parent VALUES (2, 20), (3, 3)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 NULL
4 NULL
# Verify that updating to the same value does not trigger the action.
statement ok
UPSERT INTO parent VALUES (1, 1)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 NULL
4 NULL
# Verify that a partial update that does not touch the FK column
# does not trigger the action.
statement ok
UPSERT INTO parent(pk) VALUES (1)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 NULL
4 NULL
statement ok
INSERT INTO parent VALUES (100, 1) ON CONFLICT(p) DO UPDATE SET p = 50
query II rowsort
SELECT * FROM child
----
1 NULL
2 NULL
3 NULL
4 NULL
statement ok
DROP TABLE child, parent
subtest UpsertSetDefault
statement ok
CREATE TABLE parent (pk INT PRIMARY KEY, p INT UNIQUE)
statement ok
CREATE TABLE child (pk INT PRIMARY KEY, p INT DEFAULT 1 REFERENCES parent(p) ON UPDATE SET DEFAULT)
statement ok
INSERT INTO parent VALUES (1, 1), (2, 2);
INSERT INTO child VALUES (1, 1), (2, 1), (3, 2), (4, 2)
# Verify that updating to the same value does not trigger the action.
statement ok
UPSERT INTO parent VALUES (2, 2)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 2
4 2
# Verify that a partial update that does not touch the FK column
# does not trigger the action.
statement ok
UPSERT INTO parent(pk) VALUES (2)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 2
4 2
statement ok
UPSERT INTO parent VALUES (2, 20), (3, 3)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 1
4 1
statement error foreign key violation: values \[1\] in columns \[p\] referenced in table "child"
INSERT INTO parent VALUES (100, 1) ON CONFLICT(p) DO UPDATE SET p = 50
statement ok
DROP TABLE child, parent
......@@ -4044,3 +4044,215 @@ SELECT * FROM self_abcd ORDER BY (a, b, c, d)
# Clean up.
statement ok
DROP TABLE self_abcd
subtest UpsertCascade
statement ok
CREATE TABLE parent (pk INT PRIMARY KEY, p INT UNIQUE)
statement ok
CREATE TABLE child (pk INT PRIMARY KEY, p INT REFERENCES parent(p) ON UPDATE CASCADE)
statement ok
INSERT INTO parent VALUES (1, 1), (2, 2);
INSERT INTO child VALUES (1, 1), (2, 1), (3, 2), (4, 2)
statement ok
UPSERT INTO parent VALUES (2, 20), (3, 3)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 20
4 20
statement ok
INSERT INTO parent VALUES (1, 1), (4, 4) ON CONFLICT (pk) DO UPDATE SET p = parent.pk * 10
query II rowsort
SELECT * FROM child
----
1 10
2 10
3 20
4 20
statement ok
INSERT INTO parent VALUES (100, 20) ON CONFLICT(p) DO UPDATE SET p = 50
query II rowsort
SELECT * FROM child
----
1 10
2 10
3 50
4 50
statement ok
DROP TABLE child, parent
subtest UpsertMultiColCascade
statement ok
CREATE TABLE parent (pk INT PRIMARY KEY, p INT, q INT, UNIQUE (p,q))
statement ok
CREATE TABLE child (pk INT PRIMARY KEY, p INT, q INT, CONSTRAINT fk FOREIGN KEY (p,q) REFERENCES parent(p,q) ON UPDATE CASCADE)
statement ok
INSERT INTO parent VALUES (1, 1, 1), (2, 2, 2);
INSERT INTO child VALUES (1, 1, 1), (2, 1, 1), (3, 2, 2), (4, 2, 2)
statement ok
UPSERT INTO parent(pk, p) VALUES (1, 1)
query III rowsort
SELECT * FROM child
----
1 1 1
2 1 1
3 2 2
4 2 2
statement ok
UPSERT INTO parent(pk, q) VALUES (2, 20)
query III rowsort
SELECT * FROM child
----
1 1 1
2 1 1
3 2 20
4 2 20
statement ok
UPSERT INTO parent VALUES (1, 10, 10)
query III rowsort
SELECT * FROM child
----
1 10 10
2 10 10
3 2 20
4 2 20
statement ok
DROP TABLE child, parent
subtest UpsertSetNull
statement ok
CREATE TABLE parent (pk INT PRIMARY KEY, p INT UNIQUE)
statement ok
CREATE TABLE child (pk INT PRIMARY KEY, p INT REFERENCES parent(p) ON UPDATE SET NULL)
statement ok
INSERT INTO parent VALUES (1, 1), (2, 2);
INSERT INTO child VALUES (1, 1), (2, 1), (3, 2), (4, 2)
statement ok
UPSERT INTO parent VALUES (2, 20), (3, 3)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 NULL
4 NULL
# Verify that updating to the same value does not trigger the action.
statement ok
UPSERT INTO parent VALUES (1, 1)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 NULL
4 NULL
# Verify that a partial update that does not touch the FK column
# does not trigger the action.
statement ok
UPSERT INTO parent(pk) VALUES (1)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 NULL
4 NULL
statement ok
INSERT INTO parent VALUES (100, 1) ON CONFLICT(p) DO UPDATE SET p = 50
query II rowsort
SELECT * FROM child
----
1 NULL
2 NULL
3 NULL
4 NULL
statement ok
DROP TABLE child, parent
subtest UpsertSetDefault
statement ok
CREATE TABLE parent (pk INT PRIMARY KEY, p INT UNIQUE)
statement ok
CREATE TABLE child (pk INT PRIMARY KEY, p INT DEFAULT 1 REFERENCES parent(p) ON UPDATE SET DEFAULT)
statement ok
INSERT INTO parent VALUES (1, 1), (2, 2);
INSERT INTO child VALUES (1, 1), (2, 1), (3, 2), (4, 2)
# Verify that updating to the same value does not trigger the action.
statement ok
UPSERT INTO parent VALUES (2, 2)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 2
4 2
# Verify that a partial update that does not touch the FK column
# does not trigger the action.
statement ok
UPSERT INTO parent(pk) VALUES (2)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 2
4 2
statement ok
UPSERT INTO parent VALUES (2, 20), (3, 3)
query II rowsort
SELECT * FROM child
----
1 1
2 1
3 1
4 1
statement error update on table "child" violates foreign key constraint "fk_p_ref_parent"\nDETAIL: Key \(p\)=\(1\) is not present in table "parent"\.
INSERT INTO parent VALUES (100, 1) ON CONFLICT(p) DO UPDATE SET p = 50
statement ok
DROP TABLE child, parent
......@@ -411,6 +411,10 @@ func (b *Builder) buildUpsert(ups *memo.UpsertExpr) (execPlan, error) {
return execPlan{}, err
}
if err := b.buildFKCascades(ups.WithID, ups.FKCascades); err != nil {
return execPlan{}, err
}
// If UPSERT returns rows, they contain all non-mutation columns from the
// table, in the same order they're defined in the table. Each output column
// value is taken from an insert, fetch, or update column, depending on the
......
......@@ -511,6 +511,13 @@ func (cb *onUpdateCascadeBuilder) Build(
// the columns that contain the old FK values, followed by the columns that
// contain the new FK values.
//
// Note that for Upserts we need to only perform actions for rows that
// correspond to updates (and not inserts). Normally these would be selected by
// a "canaryCol IS NOT NULL" filters. However, that is not necessary because for
// inserted rows the "old" values are all NULL and won't match anything in the
// inner-join anyway. This reasoning is very similar to that of FK checks for
// Upserts (see buildFKChecksForUpsert).
//
func (b *Builder) buildUpdateCascadeMutationInput(
childTable cat.Table,
childTableAlias *tree.TableName,
......
......@@ -421,10 +421,36 @@ func (mb *mutationBuilder) buildFKChecksForUpsert() {
}
if a := h.fk.UpdateReferenceAction(); a != tree.Restrict && a != tree.NoAction {
// Bail, so that exec FK checks pick up on FK checks and perform them.
mb.setFKFallback()
telemetry.Inc(sqltelemetry.ForeignKeyCascadesUseCounter)
return
if !mb.b.evalCtx.SessionData.OptimizerFKCascades {
// Bail, so that exec FK checks pick up on FK checks and perform them.
mb.setFKFallback()
telemetry.Inc(sqltelemetry.ForeignKeyCascadesUseCounter)
return
}
builder := newOnUpdateCascadeBuilder(mb.tab, i, h.otherTab, a)
oldCols := make(opt.ColList, len(h.tabOrdinals))
newCols := make(opt.ColList, len(h.tabOrdinals))
for i, tabOrd := range h.tabOrdinals {
fetchOrd := mb.fetchOrds[tabOrd]
// Here we don't need to use the upsertOrds because the rows that
// correspond to inserts will be ignored in the cascade.
updateOrd := mb.updateOrds[tabOrd]
if updateOrd == -1 {
updateOrd = fetchOrd
}
oldCols[i] = mb.scopeOrdToColID(fetchOrd)
newCols[i] = mb.scopeOrdToColID(updateOrd)
}
mb.cascades = append(mb.cascades, memo.FKCascade{
FKName: h.fk.Name(),
Builder: builder,
WithID: mb.withID,
OldValues: oldCols,
NewValues: newCols,
})
continue
}
// Construct an Except expression for the set difference between "old" FK
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment