Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
38964: opt: support FK checks with arbitrary mutation input r=RaduBerinde a=RaduBerinde

We use the new WithRef infrastructure to refer to the mutation input
from FK check queries.

The mutation itself optionally acts similar to `With`, saving the
input to the mutation operator in a buffer.

This allows arbitrary mutation inputs (the hack that was in place only
really worked for simple cases, like constant VALUES). Note that we
still want to avoid buffering when possible; this is left as a TODO.

Release note: None

Co-authored-by: Radu Berinde <[email protected]>
  • Loading branch information
craig[bot] and RaduBerinde committed Jul 20, 2019
2 parents 22d48ca + c650afe commit 7c80217
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 268 deletions.
16 changes: 16 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/fk_opt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,19 @@ INSERT INTO child VALUES (1,1), (2,2), (3,3)

statement ok
INSERT INTO child VALUES (1,1), (2,2)

# Use data from a different table as input.
statement ok
CREATE TABLE xy (x INT, y INT)

statement ok
INSERT INTO xy VALUES (4, 4), (5, 5), (6, 6)

statement error insert or update on table "child" violates foreign key constraint "fk_p_ref_parent"\nDETAIL: Key \(p\)=\(4\) is not present in table "parent"\.
INSERT INTO child SELECT x,y FROM xy

statement ok
INSERT INTO parent SELECT x FROM xy

statement ok
INSERT INTO child SELECT x,y FROM xy
28 changes: 18 additions & 10 deletions pkg/sql/opt/exec/execbuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,6 @@ type Builder struct {
withExprs []builtWithExpr
}

// builtWithExpr is metadata regarding a With expression which has already been
// added to the set of subqueries for the query.
type builtWithExpr struct {
id opt.WithID
// outputCols maps the output ColumnIDs of the With expression to the ordinal
// positions they are output to. See execPlan.outputCols for more details.
outputCols opt.ColMap
bufferNode exec.Node
}

// New constructs an instance of the execution node builder using the
// given factory to construct nodes. The Build method will build the execution
// node tree from the given optimized expression tree.
Expand Down Expand Up @@ -149,3 +139,21 @@ func (b *Builder) BuildScalar(ivh *tree.IndexedVarHelper) (tree.TypedExpr, error
func (b *Builder) decorrelationError() error {
return errors.Errorf("could not decorrelate subquery")
}

// builtWithExpr is metadata regarding a With expression which has already been
// added to the set of subqueries for the query.
type builtWithExpr struct {
id opt.WithID
// outputCols maps the output ColumnIDs of the With expression to the ordinal
// positions they are output to. See execPlan.outputCols for more details.
outputCols opt.ColMap
bufferNode exec.Node
}

func (b *Builder) addBuiltWithExpr(id opt.WithID, outputCols opt.ColMap, bufferNode exec.Node) {
b.withExprs = append(b.withExprs, builtWithExpr{
id: id,
outputCols: outputCols,
bufferNode: bufferNode,
})
}
56 changes: 28 additions & 28 deletions pkg/sql/opt/exec/execbuilder/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package execbuilder

import (
"bytes"
"fmt"

"github.com/cockroachdb/cockroach/pkg/sql/lex"
"github.com/cockroachdb/cockroach/pkg/sql/opt"
Expand All @@ -25,20 +26,38 @@ import (
"github.com/cockroachdb/errors"
)

func (b *Builder) buildInsert(ins *memo.InsertExpr) (execPlan, error) {
// Build the input query and ensure that the input columns that correspond to
// the table columns are projected.
input, err := b.buildRelational(ins.Input)
func (b *Builder) buildMutationInput(
inputExpr memo.RelExpr, colList opt.ColList, p *memo.MutationPrivate,
) (execPlan, error) {
input, err := b.buildRelational(inputExpr)
if err != nil {
return execPlan{}, err
}

input, err = b.ensureColumns(input, colList, nil, inputExpr.ProvidedPhysical().Ordering)
if err != nil {
return execPlan{}, err
}

if p.WithID != 0 {
label := fmt.Sprintf("buffer %d", p.WithID)
input.root, err = b.factory.ConstructBuffer(input.root, label)
if err != nil {
return execPlan{}, err
}

b.addBuiltWithExpr(p.WithID, input.outputCols, input.root)
}
return input, nil
}

func (b *Builder) buildInsert(ins *memo.InsertExpr) (execPlan, error) {
// Construct list of columns that only contains columns that need to be
// inserted (e.g. delete-only mutation columns don't need to be inserted).
colList := make(opt.ColList, 0, len(ins.InsertCols)+len(ins.CheckCols))
colList = appendColsWhenPresent(colList, ins.InsertCols)
colList = appendColsWhenPresent(colList, ins.CheckCols)
input, err = b.ensureColumns(input, colList, nil, ins.Input.ProvidedPhysical().Ordering)
input, err := b.buildMutationInput(ins.Input, colList, &ins.MutationPrivate)
if err != nil {
return execPlan{}, err
}
Expand Down Expand Up @@ -75,13 +94,6 @@ func (b *Builder) buildInsert(ins *memo.InsertExpr) (execPlan, error) {
}

func (b *Builder) buildUpdate(upd *memo.UpdateExpr) (execPlan, error) {
// Build the input query and ensure that the fetch and update columns are
// projected.
input, err := b.buildRelational(upd.Input)
if err != nil {
return execPlan{}, err
}

// Currently, the execution engine requires one input column for each fetch
// and update expression, so use ensureColumns to map and reorder colums so
// that they correspond to target table columns. For example:
Expand All @@ -97,7 +109,8 @@ func (b *Builder) buildUpdate(upd *memo.UpdateExpr) (execPlan, error) {
colList = appendColsWhenPresent(colList, upd.FetchCols)
colList = appendColsWhenPresent(colList, upd.UpdateCols)
colList = appendColsWhenPresent(colList, upd.CheckCols)
input, err = b.ensureColumns(input, colList, nil, upd.Input.ProvidedPhysical().Ordering)

input, err := b.buildMutationInput(upd.Input, colList, &upd.MutationPrivate)
if err != nil {
return execPlan{}, err
}
Expand Down Expand Up @@ -130,13 +143,6 @@ func (b *Builder) buildUpdate(upd *memo.UpdateExpr) (execPlan, error) {
}

func (b *Builder) buildUpsert(ups *memo.UpsertExpr) (execPlan, error) {
// Build the input query and ensure that the insert, fetch, and update columns
// are projected.
input, err := b.buildRelational(ups.Input)
if err != nil {
return execPlan{}, err
}

// Currently, the execution engine requires one input column for each insert,
// fetch, and update expression, so use ensureColumns to map and reorder
// columns so that they correspond to target table columns. For example:
Expand Down Expand Up @@ -164,7 +170,7 @@ func (b *Builder) buildUpsert(ups *memo.UpsertExpr) (execPlan, error) {
colList = append(colList, ups.CanaryCol)
}
colList = appendColsWhenPresent(colList, ups.CheckCols)
input, err = b.ensureColumns(input, colList, nil, ups.Input.ProvidedPhysical().Ordering)
input, err := b.buildMutationInput(ups.Input, colList, &ups.MutationPrivate)
if err != nil {
return execPlan{}, err
}
Expand Down Expand Up @@ -212,19 +218,13 @@ func (b *Builder) buildDelete(del *memo.DeleteExpr) (execPlan, error) {
return b.buildDeleteRange(del)
}

// Build the input query and ensure that the fetch columns are projected.
input, err := b.buildRelational(del.Input)
if err != nil {
return execPlan{}, err
}

// Ensure that order of input columns matches order of target table columns.
//
// TODO(andyk): Using ensureColumns here can result in an extra Render.
// Upgrade execution engine to not require this.
colList := make(opt.ColList, 0, len(del.FetchCols))
colList = appendColsWhenPresent(colList, del.FetchCols)
input, err = b.ensureColumns(input, colList, nil, del.Input.ProvidedPhysical().Ordering)
input, err := b.buildMutationInput(del.Input, colList, &del.MutationPrivate)
if err != nil {
return execPlan{}, err
}
Expand Down
51 changes: 29 additions & 22 deletions pkg/sql/opt/exec/execbuilder/relational.go
Original file line number Diff line number Diff line change
Expand Up @@ -1373,11 +1373,7 @@ func (b *Builder) buildWith(with *memo.WithExpr) (execPlan, error) {
Root: buffer,
})

b.withExprs = append(b.withExprs, builtWithExpr{
id: with.ID,
outputCols: value.outputCols,
bufferNode: buffer,
})
b.addBuiltWithExpr(with.ID, value.outputCols, buffer)

return b.buildRelational(with.Input)
}
Expand Down Expand Up @@ -1405,27 +1401,38 @@ func (b *Builder) buildWithScan(withScan *memo.WithScanExpr) (execPlan, error) {
if err != nil {
return execPlan{}, err
}
res := execPlan{root: node}

// The ColumnIDs from the With expression need to get remapped according to
// the mapping in the withScan to get the actual colMap for this expression.
var outputCols opt.ColMap

referencedExpr := b.mem.WithExpr(withScan.ID)
if !referencedExpr.Relational().OutputCols.Equals(withScan.InCols.ToSet()) {
panic(errors.AssertionFailedf(
"columns being output from WITH do not match expected columns",
))
}
if maxVal, _ := e.outputCols.MaxValue(); len(withScan.InCols) == maxVal+1 {
// We are outputting all columns. Just set up the map.

for i := range withScan.InCols {
idx, _ := e.outputCols.Get(int(withScan.InCols[i]))
outputCols.Set(int(withScan.OutCols[i]), idx)
// The ColumnIDs from the With expression need to get remapped according to
// the mapping in the withScan to get the actual colMap for this expression.
for i := range withScan.InCols {
idx, _ := e.outputCols.Get(int(withScan.InCols[i]))
res.outputCols.Set(int(withScan.OutCols[i]), idx)
}
} else {
// We need a projection.
cols := make([]exec.ColumnOrdinal, len(withScan.InCols))
for i := range withScan.InCols {
col, ok := e.outputCols.Get(int(withScan.InCols[i]))
if !ok {
panic(errors.AssertionFailedf("column %d not in input", log.Safe(withScan.InCols[i])))
}
cols[i] = exec.ColumnOrdinal(col)
res.outputCols.Set(int(withScan.OutCols[i]), i)
}
res.root, err = b.factory.ConstructSimpleProject(
res.root, cols, nil, /* colNames */
exec.OutputOrdering(res.sqlOrdering(withScan.ProvidedPhysical().Ordering)),
)
if err != nil {
return execPlan{}, err
}
}
return res, nil

return execPlan{
root: node,
outputCols: outputCols,
}, nil
}

func (b *Builder) buildProjectSet(projectSet *memo.ProjectSetExpr) (execPlan, error) {
Expand Down
75 changes: 57 additions & 18 deletions pkg/sql/opt/exec/execbuilder/testdata/fk_opt
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,61 @@ CREATE TABLE parent (p INT PRIMARY KEY, other INT)
statement ok
CREATE TABLE child (c INT PRIMARY KEY, p INT NOT NULL REFERENCES parent(p))

query TTT
EXPLAIN INSERT INTO child VALUES (1,1), (2,2)
query TTTTT
EXPLAIN (VERBOSE) INSERT INTO child VALUES (1,1), (2,2)
----
root · ·
├── count · ·
│ └── insert · ·
│ │ into child(c, p)
│ │ strategy inserter
│ └── values · ·
│ size 2 columns, 2 rows
└── postquery · ·
└── errorIfRows · ·
└── lookup-join · ·
│ table parent@primary
│ type anti
│ equality (column2) = (p)
│ equality cols are key ·
└── values · ·
· size 1 column, 2 rows
root · · () ·
├── count · · () ·
│ └── insert · · () ·
│ │ into child(c, p) · ·
│ │ strategy inserter · ·
│ └── buffer node · · (column1, column2) ·
│ │ label buffer 1 · ·
│ └── values · · (column1, column2) ·
│ size 2 columns, 2 rows · ·
│ row 0, expr 0 1 · ·
│ row 0, expr 1 1 · ·
│ row 1, expr 0 2 · ·
│ row 1, expr 1 2 · ·
└── postquery · · () ·
└── error if rows · · () ·
└── lookup-join · · (column2) ·
│ table parent@primary · ·
│ type anti · ·
│ equality (column2) = (p) · ·
│ equality cols are key · · ·
└── render · · (column2) ·
│ render 0 column2 · ·
└── scan buffer node · · (column1, column2) ·
· label buffer 1 · ·

# Use data from a different table as input.
statement ok
CREATE TABLE xy (x INT, y INT)

query TTTTT
EXPLAIN (VERBOSE) INSERT INTO child SELECT x,y FROM xy
----
root · · () ·
├── count · · () ·
│ └── insert · · () ·
│ │ into child(c, p) · ·
│ │ strategy inserter · ·
│ └── buffer node · · (x, y) ·
│ │ label buffer 1 · ·
│ └── scan · · (x, y) ·
│ table xy@primary · ·
│ spans ALL · ·
└── postquery · · () ·
└── error if rows · · () ·
└── hash-join · · (y) ·
│ type anti · ·
│ equality (y) = (p) · ·
│ right cols are key · · ·
├── render · · (y) ·
│ │ render 0 y · ·
│ └── scan buffer node · · (x, y) ·
│ label buffer 1 · ·
└── scan · · (p) ·
· table parent@primary · ·
· spans ALL · ·
Loading

0 comments on commit 7c80217

Please sign in to comment.