diff --git a/pkg/sql/logictest/testdata/logic_test/unique b/pkg/sql/logictest/testdata/logic_test/unique index 67de2a6d8781..acf32539420c 100644 --- a/pkg/sql/logictest/testdata/logic_test/unique +++ b/pkg/sql/logictest/testdata/logic_test/unique @@ -251,6 +251,49 @@ us-west foo 1 1 eu-west bar 2 2 +# Insert some non-null data into a table with a partial unique without index +# constraint. +statement ok +INSERT INTO uniq_partial VALUES (1, 1), (1, -1), (2, 2) + +# Partial unique constraint violation. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +INSERT INTO uniq_partial VALUES (1, 3) + +# No partial unique constraint violation because b <= 0. +statement ok +INSERT INTO uniq_partial VALUES (1, -3) + +# Attempt to insert conflicting keys twice in the same statement. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(3\) already exists\. +INSERT INTO uniq_partial VALUES (3, 3), (3, 4) + +# Attempt to insert one conflicting key and one non-conflicting key in the same +# statement. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +INSERT INTO uniq_partial VALUES (1, 3), (3, 3) + +# Insert some rows with NULL keys. +statement ok +INSERT INTO uniq_partial VALUES (NULL, 5), (5, 5), (NULL, 5) + +# Insert with non-constant input. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +INSERT INTO uniq_partial SELECT w, x FROM other + +query II colnames,rowsort +SELECT * FROM uniq_partial +---- +a b +1 1 +1 -1 +1 -3 +2 2 +5 5 +NULL 5 +NULL 5 + + # -- Tests with UPDATE -- subtest Update diff --git a/pkg/sql/opt/optbuilder/mutation_builder.go b/pkg/sql/opt/optbuilder/mutation_builder.go index bd49503facb8..8aa408c3b77f 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder.go +++ b/pkg/sql/opt/optbuilder/mutation_builder.go @@ -159,6 +159,11 @@ type mutationBuilder struct { // reuse. parsedIndexExprs []tree.Expr + // parsedUniqueConstraintExprs is a cached set of parsed partial unique + // constraint predicate expressions from the table schema. These are parsed + // once and cached for reuse. + parsedUniqueConstraintExprs []tree.Expr + // uniqueChecks contains unique check queries; see buildUnique* methods. uniqueChecks memo.UniqueChecksExpr @@ -1193,6 +1198,35 @@ func (mb *mutationBuilder) parsePartialIndexPredicateExpr(idx cat.IndexOrdinal) return expr } +// parseUniqueConstraintPredicateExpr parses the predicate of the given partial +// unique constraint and caches it for reuse. This function panics if the unique +// constraint at the given ordinal is not partial. +func (mb *mutationBuilder) parseUniqueConstraintPredicateExpr(idx cat.UniqueOrdinal) tree.Expr { + uniqueConstraint := mb.tab.Unique(idx) + + predStr, isPartial := uniqueConstraint.Predicate() + if !isPartial { + panic(errors.AssertionFailedf("unique constraints at ordinal %d is not a partial unique constraint", idx)) + } + + if mb.parsedUniqueConstraintExprs == nil { + mb.parsedUniqueConstraintExprs = make([]tree.Expr, mb.tab.UniqueCount()) + } + + // Return expression from the cache, if it was already parsed previously. + if mb.parsedUniqueConstraintExprs[idx] != nil { + return mb.parsedUniqueConstraintExprs[idx] + } + + expr, err := parser.ParseExpr(predStr) + if err != nil { + panic(err) + } + + mb.parsedUniqueConstraintExprs[idx] = expr + return expr +} + // getIndexLaxKeyOrdinals returns the ordinals of all lax key columns in the // given index. A column's ordinal is the ordered position of that column in the // owning table. diff --git a/pkg/sql/opt/optbuilder/mutation_builder_unique.go b/pkg/sql/opt/optbuilder/mutation_builder_unique.go index cc30ee2bdc42..4971c5c5942c 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder_unique.go +++ b/pkg/sql/opt/optbuilder/mutation_builder_unique.go @@ -17,6 +17,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" + "github.com/cockroachdb/cockroach/pkg/sql/types" "github.com/cockroachdb/cockroach/pkg/util" ) @@ -129,12 +130,11 @@ type uniqueCheckHelper struct { // uniqueOrdinals are the table ordinals of the unique columns in the table // that is being mutated. They correspond 1-to-1 to the columns in the // UniqueConstraint. - uniqueOrdinals []int + uniqueOrdinals util.FastIntSet - // uniqueAndPrimaryKeyOrdinals includes all the ordinals from uniqueOrdinals, - // plus the ordinals from any primary key columns that are not already - // included in uniqueOrdinals. - uniqueAndPrimaryKeyOrdinals []int + // primaryKeyOrdinals includes the ordinals from any primary key columns + // that are not included in uniqueOrdinals. + primaryKeyOrdinals util.FastIntSet } // init initializes the helper with a unique constraint. @@ -150,15 +150,18 @@ func (h *uniqueCheckHelper) init(mb *mutationBuilder, uniqueOrdinal int) bool { uniqueOrdinal: uniqueOrdinal, } - uniqueCount := h.unique.ColumnCount() - var uniqueOrds util.FastIntSet - for i := 0; i < uniqueCount; i++ { + for i, n := 0, h.unique.ColumnCount(); i < n; i++ { uniqueOrds.Add(h.unique.ColumnOrdinal(mb.tab, i)) } // Find the primary key columns that are not part of the unique constraint. // If there aren't any, we don't need a check. + // TODO(mgartner): We also don't need a check if there exists a unique index + // with columns that are a subset of the unique constraint columns. + // Similarly, we don't need a check for a partial unique constraint if there + // exists a non-partial unique constraint with columns that are a subset of + // the partial unique constrain columns. primaryOrds := getIndexLaxKeyOrdinals(mb.tab.Index(cat.PrimaryIndex)) primaryOrds.DifferenceWith(uniqueOrds) if primaryOrds.Empty() { @@ -167,13 +170,13 @@ func (h *uniqueCheckHelper) init(mb *mutationBuilder, uniqueOrdinal int) bool { return false } - h.uniqueAndPrimaryKeyOrdinals = append(uniqueOrds.Ordered(), primaryOrds.Ordered()...) - h.uniqueOrdinals = h.uniqueAndPrimaryKeyOrdinals[:uniqueCount] + h.uniqueOrdinals = uniqueOrds + h.primaryKeyOrdinals = primaryOrds // Check if we are setting NULL values for the unique columns, like when this // mutation is the result of a SET NULL cascade action. numNullCols := 0 - for _, tabOrd := range h.uniqueOrdinals { + for tabOrd, ok := h.uniqueOrdinals.Next(0); ok; tabOrd, ok = h.uniqueOrdinals.Next(tabOrd + 1) { colID := mb.mapToReturnColID(tabOrd) if memo.OutputColumnIsAlwaysNull(mb.outScope.expr, colID) { numNullCols++ @@ -189,26 +192,31 @@ func (h *uniqueCheckHelper) init(mb *mutationBuilder, uniqueOrdinal int) bool { // table. The input to the insertion check will be produced from the input to // the mutation operator. func (h *uniqueCheckHelper) buildInsertionCheck() memo.UniqueChecksItem { - withScanScope, _ := h.mb.buildCheckInputScan( - checkInputScanNewVals, h.uniqueAndPrimaryKeyOrdinals, - ) - - numCols := len(withScanScope.cols) f := h.mb.b.factory // Build a self semi-join, with the new values on the left and the // existing values on the right. + scanScope, ordinals := h.buildTableScan() - scanScope, _ := h.buildTableScan() + withScanScope, _ := h.mb.buildCheckInputScan( + checkInputScanNewVals, ordinals, + ) // Build the join filters: // (new_a = existing_a) AND (new_b = existing_b) AND ... // - // Set the capacity to len(h.uniqueOrdinals)+1 since we'll have an equality + // Set the capacity to h.uniqueOrdinals.Len()+1 since we'll have an equality // condition for each column in the unique constraint, plus one additional - // condition to prevent rows from matching themselves (see below). - semiJoinFilters := make(memo.FiltersExpr, 0, len(h.uniqueOrdinals)+1) - for i := 0; i < len(h.uniqueOrdinals); i++ { + // condition to prevent rows from matching themselves (see below). If the + // constraint is partial, add 2 to account for filtering both the WithScan + // and the Scan by the partial unique constraint predicate. + numFilters := h.uniqueOrdinals.Len() + 1 + _, isPartial := h.unique.Predicate() + if isPartial { + numFilters += 2 + } + semiJoinFilters := make(memo.FiltersExpr, 0, numFilters) + for i, ok := h.uniqueOrdinals.Next(0); ok; i, ok = h.uniqueOrdinals.Next(i + 1) { semiJoinFilters = append(semiJoinFilters, f.ConstructFiltersItem( f.ConstructEq( f.ConstructVariable(withScanScope.cols[i].id), @@ -217,12 +225,29 @@ func (h *uniqueCheckHelper) buildInsertionCheck() memo.UniqueChecksItem { )) } + // If the unique constraint is partial, we need to filter out inserted rows + // that don't satisfy the predicate. We also need to make sure that rows do + // not match existing rows in the the table that do not satisfy the + // predicate. So we add the predicate as a filter on both the WithScan + // columns and the Scan columns. + if isPartial { + pred := h.mb.parseUniqueConstraintPredicateExpr(h.uniqueOrdinal) + + typedPred := withScanScope.resolveAndRequireType(pred, types.Bool) + withScanPred := h.mb.b.buildScalar(typedPred, withScanScope, nil, nil, nil) + semiJoinFilters = append(semiJoinFilters, f.ConstructFiltersItem(withScanPred)) + + typedPred = scanScope.resolveAndRequireType(pred, types.Bool) + scanPred := h.mb.b.buildScalar(typedPred, scanScope, nil, nil, nil) + semiJoinFilters = append(semiJoinFilters, f.ConstructFiltersItem(scanPred)) + } + // We need to prevent rows from matching themselves in the semi join. We can // do this by adding another filter that uses the primary keys to check if // two rows are identical: // (new_pk1 != existing_pk1) OR (new_pk2 != existing_pk2) OR ... var pkFilter opt.ScalarExpr - for i := len(h.uniqueOrdinals); i < numCols; i++ { + for i, ok := h.primaryKeyOrdinals.Next(0); ok; i, ok = h.primaryKeyOrdinals.Next(i + 1) { pkFilterLocal := f.ConstructNe( f.ConstructVariable(withScanScope.cols[i].id), f.ConstructVariable(scanScope.cols[i].id), @@ -237,26 +262,38 @@ func (h *uniqueCheckHelper) buildInsertionCheck() memo.UniqueChecksItem { semiJoin := f.ConstructSemiJoin(withScanScope.expr, scanScope.expr, semiJoinFilters, memo.EmptyJoinPrivate) + // Collect the key columns that will be shown in the error message if there + // is a duplicate key violation resulting from this uniqueness check. + keyCols := make(opt.ColList, 0, h.uniqueOrdinals.Len()) + for i, ok := h.uniqueOrdinals.Next(0); ok; i, ok = h.uniqueOrdinals.Next(i + 1) { + keyCols = append(keyCols, withScanScope.cols[i].id) + } + return f.ConstructUniqueChecksItem(semiJoin, &memo.UniqueChecksItemPrivate{ Table: h.mb.tabID, CheckOrdinal: h.uniqueOrdinal, - // uniqueOrdinals is always a prefix of uniqueAndPrimaryKeyOrdinals, - // which maps 1-to-1 to the columns in withScanScope.cols. The remaining - // columns are primary key columns and should not be included in the - // KeyCols. - KeyCols: withScanScope.colList()[:len(h.uniqueOrdinals)], - OpName: h.mb.opName, + KeyCols: keyCols, + OpName: h.mb.opName, }) } -// buildTableScan builds a Scan of the table. -func (h *uniqueCheckHelper) buildTableScan() (outScope *scope, tabMeta *opt.TableMeta) { - tabMeta = h.mb.b.addTable(h.mb.tab, tree.NewUnqualifiedTableName(h.mb.tab.Name())) +// buildTableScan builds a Scan of the table. The ordinals of the columns +// scanned are also returned. +func (h *uniqueCheckHelper) buildTableScan() (outScope *scope, ordinals []int) { + tabMeta := h.mb.b.addTable(h.mb.tab, tree.NewUnqualifiedTableName(h.mb.tab.Name())) + // TODO(mgartner): Should mutation columns be included here? Does uniqueness + // need to hold true for write-only columns? + ordinals = tableOrdinals(tabMeta.Table, columnKinds{ + includeMutations: false, + includeSystem: false, + includeVirtualInverted: false, + includeVirtualComputed: true, + }) return h.mb.b.buildScan( tabMeta, - h.uniqueAndPrimaryKeyOrdinals, + ordinals, nil, /* indexFlags */ noRowLocking, h.mb.b.allocScope(), - ), tabMeta + ), ordinals } diff --git a/pkg/sql/opt/optbuilder/testdata/unique-checks-insert b/pkg/sql/opt/optbuilder/testdata/unique-checks-insert index 876623dcd9ba..d2ca06bc26b7 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-insert +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-insert @@ -29,32 +29,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:12!null column1:13!null + │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column5:22!null │ ├── with-scan &1 - │ │ ├── columns: column3:12!null column1:13!null + │ │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column5:22!null │ │ └── mapping: - │ │ ├── column3:9 => column3:12 - │ │ └── column1:7 => column1:13 + │ │ ├── column1:7 => column1:18 + │ │ ├── column2:8 => column2:19 + │ │ ├── column3:9 => column3:20 + │ │ ├── column4:10 => column4:21 + │ │ └── column5:11 => column5:22 │ ├── scan uniq - │ │ └── columns: k:14!null w:16 + │ │ └── columns: k:12!null v:13 w:14 x:15 y:16 │ └── filters - │ ├── column3:12 = w:16 - │ └── column1:13 != k:14 + │ ├── column3:20 = w:14 + │ └── column1:18 != k:12 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:20!null column5:21!null column1:22!null + ├── columns: column1:29!null column2:30!null column3:31!null column4:32!null column5:33!null ├── with-scan &1 - │ ├── columns: column4:20!null column5:21!null column1:22!null + │ ├── columns: column1:29!null column2:30!null column3:31!null column4:32!null column5:33!null │ └── mapping: - │ ├── column4:10 => column4:20 - │ ├── column5:11 => column5:21 - │ └── column1:7 => column1:22 + │ ├── column1:7 => column1:29 + │ ├── column2:8 => column2:30 + │ ├── column3:9 => column3:31 + │ ├── column4:10 => column4:32 + │ └── column5:11 => column5:33 ├── scan uniq - │ └── columns: k:23!null x:26 y:27 + │ └── columns: k:23!null v:24 w:25 x:26 y:27 └── filters - ├── column4:20 = x:26 - ├── column5:21 = y:27 - └── column1:22 != k:23 + ├── column4:32 = x:26 + ├── column5:33 = y:27 + └── column1:29 != k:23 # Some of the inserted values have nulls. build @@ -77,34 +82,39 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:12 column1:13!null + │ ├── columns: column1:18!null column2:19 column3:20 column4:21 column5:22!null │ ├── with-scan &1 - │ │ ├── columns: column3:12 column1:13!null + │ │ ├── columns: column1:18!null column2:19 column3:20 column4:21 column5:22!null │ │ └── mapping: - │ │ ├── column3:9 => column3:12 - │ │ └── column1:7 => column1:13 + │ │ ├── column1:7 => column1:18 + │ │ ├── column2:8 => column2:19 + │ │ ├── column3:9 => column3:20 + │ │ ├── column4:10 => column4:21 + │ │ └── column5:11 => column5:22 │ ├── scan uniq - │ │ └── columns: k:14!null w:16 + │ │ └── columns: k:12!null v:13 w:14 x:15 y:16 │ └── filters - │ ├── column3:12 = w:16 - │ └── column1:13 != k:14 + │ ├── column3:20 = w:14 + │ └── column1:18 != k:12 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:20 column5:21!null column1:22!null + ├── columns: column1:29!null column2:30 column3:31 column4:32 column5:33!null ├── with-scan &1 - │ ├── columns: column4:20 column5:21!null column1:22!null + │ ├── columns: column1:29!null column2:30 column3:31 column4:32 column5:33!null │ └── mapping: - │ ├── column4:10 => column4:20 - │ ├── column5:11 => column5:21 - │ └── column1:7 => column1:22 + │ ├── column1:7 => column1:29 + │ ├── column2:8 => column2:30 + │ ├── column3:9 => column3:31 + │ ├── column4:10 => column4:32 + │ └── column5:11 => column5:33 ├── scan uniq - │ └── columns: k:23!null x:26 y:27 + │ └── columns: k:23!null v:24 w:25 x:26 y:27 └── filters - ├── column4:20 = x:26 - ├── column5:21 = y:27 - └── column1:22 != k:23 + ├── column4:32 = x:26 + ├── column5:33 = y:27 + └── column1:29 != k:23 -# No need to plan checks for w since it's aways null. +# No need to plan checks for w since it's always null. build INSERT INTO uniq VALUES (1, 1, NULL, 1, 1), (2, 2, NULL, 2, 2) ---- @@ -124,21 +134,23 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:12!null column5:13!null column1:14!null + ├── columns: column1:18!null column2:19!null column3:20 column4:21!null column5:22!null ├── with-scan &1 - │ ├── columns: column4:12!null column5:13!null column1:14!null + │ ├── columns: column1:18!null column2:19!null column3:20 column4:21!null column5:22!null │ └── mapping: - │ ├── column4:10 => column4:12 - │ ├── column5:11 => column5:13 - │ └── column1:7 => column1:14 + │ ├── column1:7 => column1:18 + │ ├── column2:8 => column2:19 + │ ├── column3:9 => column3:20 + │ ├── column4:10 => column4:21 + │ └── column5:11 => column5:22 ├── scan uniq - │ └── columns: k:15!null x:18 y:19 + │ └── columns: k:12!null v:13 w:14 x:15 y:16 └── filters - ├── column4:12 = x:18 - ├── column5:13 = y:19 - └── column1:14 != k:15 + ├── column4:21 = x:15 + ├── column5:22 = y:16 + └── column1:18 != k:12 -# No need to plan checks for x,y since x is aways null. +# No need to plan checks for x,y since x is always null. build INSERT INTO uniq VALUES (1, 1, 1, NULL, 1), (2, 2, NULL, NULL, 2) ---- @@ -158,19 +170,22 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: column3:12 column1:13!null + ├── columns: column1:18!null column2:19!null column3:20 column4:21 column5:22!null ├── with-scan &1 - │ ├── columns: column3:12 column1:13!null + │ ├── columns: column1:18!null column2:19!null column3:20 column4:21 column5:22!null │ └── mapping: - │ ├── column3:9 => column3:12 - │ └── column1:7 => column1:13 + │ ├── column1:7 => column1:18 + │ ├── column2:8 => column2:19 + │ ├── column3:9 => column3:20 + │ ├── column4:10 => column4:21 + │ └── column5:11 => column5:22 ├── scan uniq - │ └── columns: k:14!null w:16 + │ └── columns: k:12!null v:13 w:14 x:15 y:16 └── filters - ├── column3:12 = w:16 - └── column1:13 != k:14 + ├── column3:20 = w:14 + └── column1:18 != k:12 -# No need to plan checks for x,y since y is aways null. +# No need to plan checks for x,y since y is always null. build INSERT INTO uniq VALUES (1, 1, 1, 1, NULL), (2, 2, 2, 2, NULL) ---- @@ -190,19 +205,22 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: column3:12!null column1:13!null + ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column5:22 ├── with-scan &1 - │ ├── columns: column3:12!null column1:13!null + │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column5:22 │ └── mapping: - │ ├── column3:9 => column3:12 - │ └── column1:7 => column1:13 + │ ├── column1:7 => column1:18 + │ ├── column2:8 => column2:19 + │ ├── column3:9 => column3:20 + │ ├── column4:10 => column4:21 + │ └── column5:11 => column5:22 ├── scan uniq - │ └── columns: k:14!null w:16 + │ └── columns: k:12!null v:13 w:14 x:15 y:16 └── filters - ├── column3:12 = w:16 - └── column1:13 != k:14 + ├── column3:20 = w:14 + └── column1:18 != k:12 -# No need to plan any checks, since w, x and y are aways null. +# No need to plan any checks, since w, x and y are always null. build INSERT INTO uniq VALUES (1, 1, NULL, NULL, NULL), (2, 2, NULL, NULL, NULL) ---- @@ -314,32 +332,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:36!null column1:37!null + │ ├── columns: column1:42!null column2:43!null column3:44!null column4:45!null column5:46!null │ ├── with-scan &1 - │ │ ├── columns: column3:36!null column1:37!null + │ │ ├── columns: column1:42!null column2:43!null column3:44!null column4:45!null column5:46!null │ │ └── mapping: - │ │ ├── column3:9 => column3:36 - │ │ └── column1:7 => column1:37 + │ │ ├── column1:7 => column1:42 + │ │ ├── column2:8 => column2:43 + │ │ ├── column3:9 => column3:44 + │ │ ├── column4:10 => column4:45 + │ │ └── column5:11 => column5:46 │ ├── scan uniq - │ │ └── columns: k:38!null w:40 + │ │ └── columns: k:36!null v:37 w:38 x:39 y:40 │ └── filters - │ ├── column3:36 = w:40 - │ └── column1:37 != k:38 + │ ├── column3:44 = w:38 + │ └── column1:42 != k:36 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:44!null column5:45!null column1:46!null + ├── columns: column1:53!null column2:54!null column3:55!null column4:56!null column5:57!null ├── with-scan &1 - │ ├── columns: column4:44!null column5:45!null column1:46!null + │ ├── columns: column1:53!null column2:54!null column3:55!null column4:56!null column5:57!null │ └── mapping: - │ ├── column4:10 => column4:44 - │ ├── column5:11 => column5:45 - │ └── column1:7 => column1:46 + │ ├── column1:7 => column1:53 + │ ├── column2:8 => column2:54 + │ ├── column3:9 => column3:55 + │ ├── column4:10 => column4:56 + │ └── column5:11 => column5:57 ├── scan uniq - │ └── columns: k:47!null x:50 y:51 + │ └── columns: k:47!null v:48 w:49 x:50 y:51 └── filters - ├── column4:44 = x:50 - ├── column5:45 = y:51 - └── column1:46 != k:47 + ├── column4:56 = x:50 + ├── column5:57 = y:51 + └── column1:53 != k:47 # On conflict clause references unique without index constraint. # TODO(rytaft): we should be able to remove the unique check for w in this case @@ -381,32 +404,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:18!null column1:19!null + │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null column5:28!null │ ├── with-scan &1 - │ │ ├── columns: column3:18!null column1:19!null + │ │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null column5:28!null │ │ └── mapping: - │ │ ├── column3:9 => column3:18 - │ │ └── column1:7 => column1:19 + │ │ ├── column1:7 => column1:24 + │ │ ├── column2:8 => column2:25 + │ │ ├── column3:9 => column3:26 + │ │ ├── column4:10 => column4:27 + │ │ └── column5:11 => column5:28 │ ├── scan uniq - │ │ └── columns: k:20!null w:22 + │ │ └── columns: k:18!null v:19 w:20 x:21 y:22 │ └── filters - │ ├── column3:18 = w:22 - │ └── column1:19 != k:20 + │ ├── column3:26 = w:20 + │ └── column1:24 != k:18 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:26!null column5:27!null column1:28!null + ├── columns: column1:35!null column2:36!null column3:37!null column4:38!null column5:39!null ├── with-scan &1 - │ ├── columns: column4:26!null column5:27!null column1:28!null + │ ├── columns: column1:35!null column2:36!null column3:37!null column4:38!null column5:39!null │ └── mapping: - │ ├── column4:10 => column4:26 - │ ├── column5:11 => column5:27 - │ └── column1:7 => column1:28 + │ ├── column1:7 => column1:35 + │ ├── column2:8 => column2:36 + │ ├── column3:9 => column3:37 + │ ├── column4:10 => column4:38 + │ └── column5:11 => column5:39 ├── scan uniq - │ └── columns: k:29!null x:32 y:33 + │ └── columns: k:29!null v:30 w:31 x:32 y:33 └── filters - ├── column4:26 = x:32 - ├── column5:27 = y:33 - └── column1:28 != k:29 + ├── column4:38 = x:32 + ├── column5:39 = y:33 + └── column1:35 != k:29 exec-ddl CREATE TABLE other (k INT, v INT, w INT NOT NULL, x INT, y INT) @@ -432,32 +460,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:14!null k:15 + │ ├── columns: k:20 v:21 w:22!null x:23 y:24 │ ├── with-scan &1 - │ │ ├── columns: w:14!null k:15 + │ │ ├── columns: k:20 v:21 w:22!null x:23 y:24 │ │ └── mapping: - │ │ ├── other.w:9 => w:14 - │ │ └── other.k:7 => k:15 + │ │ ├── other.k:7 => k:20 + │ │ ├── other.v:8 => v:21 + │ │ ├── other.w:9 => w:22 + │ │ ├── other.x:10 => x:23 + │ │ └── other.y:11 => y:24 │ ├── scan uniq - │ │ └── columns: uniq.k:16!null uniq.w:18 + │ │ └── columns: uniq.k:14!null uniq.v:15 uniq.w:16 uniq.x:17 uniq.y:18 │ └── filters - │ ├── w:14 = uniq.w:18 - │ └── k:15 != uniq.k:16 + │ ├── w:22 = uniq.w:16 + │ └── k:20 != uniq.k:14 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:22 y:23 k:24 + ├── columns: k:31 v:32 w:33!null x:34 y:35 ├── with-scan &1 - │ ├── columns: x:22 y:23 k:24 + │ ├── columns: k:31 v:32 w:33!null x:34 y:35 │ └── mapping: - │ ├── other.x:10 => x:22 - │ ├── other.y:11 => y:23 - │ └── other.k:7 => k:24 + │ ├── other.k:7 => k:31 + │ ├── other.v:8 => v:32 + │ ├── other.w:9 => w:33 + │ ├── other.x:10 => x:34 + │ └── other.y:11 => y:35 ├── scan uniq - │ └── columns: uniq.k:25!null uniq.x:28 uniq.y:29 + │ └── columns: uniq.k:25!null uniq.v:26 uniq.w:27 uniq.x:28 uniq.y:29 └── filters - ├── x:22 = uniq.x:28 - ├── y:23 = uniq.y:29 - └── k:24 != uniq.k:25 + ├── x:34 = uniq.x:28 + ├── y:35 = uniq.y:29 + └── k:31 != uniq.k:25 exec-ddl CREATE TABLE uniq_overlaps_pk ( @@ -494,48 +527,51 @@ insert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: column2:10!null column3:11!null column1:12!null + │ ├── columns: column1:15!null column2:16!null column3:17!null column4:18!null │ ├── with-scan &1 - │ │ ├── columns: column2:10!null column3:11!null column1:12!null + │ │ ├── columns: column1:15!null column2:16!null column3:17!null column4:18!null │ │ └── mapping: - │ │ ├── column2:7 => column2:10 - │ │ ├── column3:8 => column3:11 - │ │ └── column1:6 => column1:12 + │ │ ├── column1:6 => column1:15 + │ │ ├── column2:7 => column2:16 + │ │ ├── column3:8 => column3:17 + │ │ └── column4:9 => column4:18 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:13!null b:14!null c:15 + │ │ └── columns: a:10!null b:11!null c:12 d:13 │ └── filters - │ ├── column2:10 = b:14 - │ ├── column3:11 = c:15 - │ └── column1:12 != a:13 + │ ├── column2:16 = b:11 + │ ├── column3:17 = c:12 + │ └── column1:15 != a:10 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: column1:18!null column2:19!null + │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null │ ├── with-scan &1 - │ │ ├── columns: column1:18!null column2:19!null + │ │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null │ │ └── mapping: - │ │ ├── column1:6 => column1:18 - │ │ └── column2:7 => column2:19 + │ │ ├── column1:6 => column1:24 + │ │ ├── column2:7 => column2:25 + │ │ ├── column3:8 => column3:26 + │ │ └── column4:9 => column4:27 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:20!null b:21!null + │ │ └── columns: a:19!null b:20!null c:21 d:22 │ └── filters - │ ├── column1:18 = a:20 - │ └── column2:19 != b:21 + │ ├── column1:24 = a:19 + │ └── column2:25 != b:20 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: column3:25!null column4:26!null column1:27!null column2:28!null + ├── columns: column1:33!null column2:34!null column3:35!null column4:36!null ├── with-scan &1 - │ ├── columns: column3:25!null column4:26!null column1:27!null column2:28!null + │ ├── columns: column1:33!null column2:34!null column3:35!null column4:36!null │ └── mapping: - │ ├── column3:8 => column3:25 - │ ├── column4:9 => column4:26 - │ ├── column1:6 => column1:27 - │ └── column2:7 => column2:28 + │ ├── column1:6 => column1:33 + │ ├── column2:7 => column2:34 + │ ├── column3:8 => column3:35 + │ └── column4:9 => column4:36 ├── scan uniq_overlaps_pk - │ └── columns: a:29!null b:30!null c:31 d:32 + │ └── columns: a:28!null b:29!null c:30 d:31 └── filters - ├── column3:25 = c:31 - ├── column4:26 = d:32 - └── (column1:27 != a:29) OR (column2:28 != b:30) + ├── column3:35 = c:30 + ├── column4:36 = d:31 + └── (column1:33 != a:28) OR (column2:34 != b:29) # Insert with non-constant input. # Add inequality filters for the primary key columns that are not part of each @@ -558,48 +594,51 @@ insert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: v:13 x:14 k:15 + │ ├── columns: k:18 v:19 x:20 y:21 │ ├── with-scan &1 - │ │ ├── columns: v:13 x:14 k:15 + │ │ ├── columns: k:18 v:19 x:20 y:21 │ │ └── mapping: - │ │ ├── other.v:7 => v:13 - │ │ ├── other.x:9 => x:14 - │ │ └── other.k:6 => k:15 + │ │ ├── other.k:6 => k:18 + │ │ ├── other.v:7 => v:19 + │ │ ├── other.x:9 => x:20 + │ │ └── other.y:10 => y:21 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:16!null b:17!null c:18 + │ │ └── columns: a:13!null b:14!null c:15 d:16 │ └── filters - │ ├── v:13 = b:17 - │ ├── x:14 = c:18 - │ └── k:15 != a:16 + │ ├── v:19 = b:14 + │ ├── x:20 = c:15 + │ └── k:18 != a:13 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: k:21 v:22 + │ ├── columns: k:27 v:28 x:29 y:30 │ ├── with-scan &1 - │ │ ├── columns: k:21 v:22 + │ │ ├── columns: k:27 v:28 x:29 y:30 │ │ └── mapping: - │ │ ├── other.k:6 => k:21 - │ │ └── other.v:7 => v:22 + │ │ ├── other.k:6 => k:27 + │ │ ├── other.v:7 => v:28 + │ │ ├── other.x:9 => x:29 + │ │ └── other.y:10 => y:30 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:23!null b:24!null + │ │ └── columns: a:22!null b:23!null c:24 d:25 │ └── filters - │ ├── k:21 = a:23 - │ └── v:22 != b:24 + │ ├── k:27 = a:22 + │ └── v:28 != b:23 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: x:28 y:29 k:30 v:31 + ├── columns: k:36 v:37 x:38 y:39 ├── with-scan &1 - │ ├── columns: x:28 y:29 k:30 v:31 + │ ├── columns: k:36 v:37 x:38 y:39 │ └── mapping: - │ ├── other.x:9 => x:28 - │ ├── other.y:10 => y:29 - │ ├── other.k:6 => k:30 - │ └── other.v:7 => v:31 + │ ├── other.k:6 => k:36 + │ ├── other.v:7 => v:37 + │ ├── other.x:9 => x:38 + │ └── other.y:10 => y:39 ├── scan uniq_overlaps_pk - │ └── columns: a:32!null b:33!null c:34 d:35 + │ └── columns: a:31!null b:32!null c:33 d:34 └── filters - ├── x:28 = c:34 - ├── y:29 = d:35 - └── (k:30 != a:32) OR (v:31 != b:33) + ├── x:38 = c:33 + ├── y:39 = d:34 + └── (k:36 != a:31) OR (v:37 != b:32) exec-ddl CREATE TABLE uniq_hidden_pk ( @@ -638,49 +677,55 @@ insert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: column2:12!null column3:13!null column11:14 + │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column11:22 │ ├── with-scan &1 - │ │ ├── columns: column2:12!null column3:13!null column11:14 + │ │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column11:22 │ │ └── mapping: - │ │ ├── column2:8 => column2:12 - │ │ ├── column3:9 => column3:13 - │ │ └── column11:11 => column11:14 + │ │ ├── column1:7 => column1:18 + │ │ ├── column2:8 => column2:19 + │ │ ├── column3:9 => column3:20 + │ │ ├── column4:10 => column4:21 + │ │ └── column11:11 => column11:22 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:16 c:17 rowid:19!null + │ │ └── columns: a:12 b:13 c:14 d:15 rowid:16!null │ └── filters - │ ├── column2:12 = b:16 - │ ├── column3:13 = c:17 - │ └── column11:14 != rowid:19 + │ ├── column2:19 = b:13 + │ ├── column3:20 = c:14 + │ └── column11:22 != rowid:16 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: column1:21!null column2:22!null column4:23!null column11:24 + │ ├── columns: column1:29!null column2:30!null column3:31!null column4:32!null column11:33 │ ├── with-scan &1 - │ │ ├── columns: column1:21!null column2:22!null column4:23!null column11:24 + │ │ ├── columns: column1:29!null column2:30!null column3:31!null column4:32!null column11:33 │ │ └── mapping: - │ │ ├── column1:7 => column1:21 - │ │ ├── column2:8 => column2:22 - │ │ ├── column4:10 => column4:23 - │ │ └── column11:11 => column11:24 + │ │ ├── column1:7 => column1:29 + │ │ ├── column2:8 => column2:30 + │ │ ├── column3:9 => column3:31 + │ │ ├── column4:10 => column4:32 + │ │ └── column11:11 => column11:33 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:25 b:26 d:28 rowid:29!null + │ │ └── columns: a:23 b:24 c:25 d:26 rowid:27!null │ └── filters - │ ├── column1:21 = a:25 - │ ├── column2:22 = b:26 - │ ├── column4:23 = d:28 - │ └── column11:24 != rowid:29 + │ ├── column1:29 = a:23 + │ ├── column2:30 = b:24 + │ ├── column4:32 = d:26 + │ └── column11:33 != rowid:27 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: column1:31!null column11:32 + ├── columns: column1:40!null column2:41!null column3:42!null column4:43!null column11:44 ├── with-scan &1 - │ ├── columns: column1:31!null column11:32 + │ ├── columns: column1:40!null column2:41!null column3:42!null column4:43!null column11:44 │ └── mapping: - │ ├── column1:7 => column1:31 - │ └── column11:11 => column11:32 + │ ├── column1:7 => column1:40 + │ ├── column2:8 => column2:41 + │ ├── column3:9 => column3:42 + │ ├── column4:10 => column4:43 + │ └── column11:11 => column11:44 ├── scan uniq_hidden_pk - │ └── columns: a:33 rowid:37!null + │ └── columns: a:34 b:35 c:36 d:37 rowid:38!null └── filters - ├── column1:31 = a:33 - └── column11:32 != rowid:37 + ├── column1:40 = a:34 + └── column11:44 != rowid:38 # Insert with non-constant input. # Add inequality filters for the hidden primary key column. @@ -707,46 +752,310 @@ insert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: v:15 x:16 column14:17 + │ ├── columns: k:21 v:22 x:23 y:24 column14:25 │ ├── with-scan &1 - │ │ ├── columns: v:15 x:16 column14:17 + │ │ ├── columns: k:21 v:22 x:23 y:24 column14:25 │ │ └── mapping: - │ │ ├── other.v:8 => v:15 - │ │ ├── other.x:10 => x:16 - │ │ └── column14:14 => column14:17 + │ │ ├── other.k:7 => k:21 + │ │ ├── other.v:8 => v:22 + │ │ ├── other.x:10 => x:23 + │ │ ├── other.y:11 => y:24 + │ │ └── column14:14 => column14:25 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:19 c:20 uniq_hidden_pk.rowid:22!null + │ │ └── columns: a:15 b:16 c:17 d:18 uniq_hidden_pk.rowid:19!null │ └── filters - │ ├── v:15 = b:19 - │ ├── x:16 = c:20 - │ └── column14:17 != uniq_hidden_pk.rowid:22 + │ ├── v:22 = b:16 + │ ├── x:23 = c:17 + │ └── column14:25 != uniq_hidden_pk.rowid:19 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: k:24 v:25 y:26 column14:27 + │ ├── columns: k:32 v:33 x:34 y:35 column14:36 │ ├── with-scan &1 - │ │ ├── columns: k:24 v:25 y:26 column14:27 + │ │ ├── columns: k:32 v:33 x:34 y:35 column14:36 │ │ └── mapping: - │ │ ├── other.k:7 => k:24 - │ │ ├── other.v:8 => v:25 - │ │ ├── other.y:11 => y:26 - │ │ └── column14:14 => column14:27 + │ │ ├── other.k:7 => k:32 + │ │ ├── other.v:8 => v:33 + │ │ ├── other.x:10 => x:34 + │ │ ├── other.y:11 => y:35 + │ │ └── column14:14 => column14:36 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:28 b:29 d:31 uniq_hidden_pk.rowid:32!null + │ │ └── columns: a:26 b:27 c:28 d:29 uniq_hidden_pk.rowid:30!null │ └── filters - │ ├── k:24 = a:28 - │ ├── v:25 = b:29 - │ ├── y:26 = d:31 - │ └── column14:27 != uniq_hidden_pk.rowid:32 + │ ├── k:32 = a:26 + │ ├── v:33 = b:27 + │ ├── y:35 = d:29 + │ └── column14:36 != uniq_hidden_pk.rowid:30 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: k:34 column14:35 + ├── columns: k:43 v:44 x:45 y:46 column14:47 ├── with-scan &1 - │ ├── columns: k:34 column14:35 + │ ├── columns: k:43 v:44 x:45 y:46 column14:47 │ └── mapping: - │ ├── other.k:7 => k:34 - │ └── column14:14 => column14:35 + │ ├── other.k:7 => k:43 + │ ├── other.v:8 => v:44 + │ ├── other.x:10 => x:45 + │ ├── other.y:11 => y:46 + │ └── column14:14 => column14:47 ├── scan uniq_hidden_pk - │ └── columns: a:36 uniq_hidden_pk.rowid:40!null + │ └── columns: a:37 b:38 c:39 d:40 uniq_hidden_pk.rowid:41!null └── filters - ├── k:34 = a:36 - └── column14:35 != uniq_hidden_pk.rowid:40 + ├── k:43 = a:37 + └── column14:47 != uniq_hidden_pk.rowid:41 + +exec-ddl +CREATE TABLE uniq_partial ( + k INT PRIMARY KEY, + a INT, + b INT, + UNIQUE WITHOUT INDEX (a) WHERE b > 0 +) +---- + +# None of the inserted values have nulls. +build +INSERT INTO uniq_partial VALUES (1, 1, 1), (2, 2, 2) +---- +insert uniq_partial + ├── columns: + ├── insert-mapping: + │ ├── column1:5 => k:1 + │ ├── column2:6 => a:2 + │ └── column3:7 => b:3 + ├── input binding: &1 + ├── values + │ ├── columns: column1:5!null column2:6!null column3:7!null + │ ├── (1, 1, 1) + │ └── (2, 2, 2) + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: column1:12!null column2:13!null column3:14!null + ├── with-scan &1 + │ ├── columns: column1:12!null column2:13!null column3:14!null + │ └── mapping: + │ ├── column1:5 => column1:12 + │ ├── column2:6 => column2:13 + │ └── column3:7 => column3:14 + ├── scan uniq_partial + │ └── columns: k:8!null a:9 b:10 + └── filters + ├── column2:13 = a:9 + ├── column3:14 > 0 + ├── b:10 > 0 + └── column1:12 != k:8 + +# Some of the inserted values have nulls. +build +INSERT INTO uniq_partial VALUES (1, 1, 1), (2, 2, 2), (3, NULL, 3) +---- +insert uniq_partial + ├── columns: + ├── insert-mapping: + │ ├── column1:5 => k:1 + │ ├── column2:6 => a:2 + │ └── column3:7 => b:3 + ├── input binding: &1 + ├── values + │ ├── columns: column1:5!null column2:6 column3:7!null + │ ├── (1, 1, 1) + │ ├── (2, 2, 2) + │ └── (3, NULL::INT8, 3) + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: column1:12!null column2:13 column3:14!null + ├── with-scan &1 + │ ├── columns: column1:12!null column2:13 column3:14!null + │ └── mapping: + │ ├── column1:5 => column1:12 + │ ├── column2:6 => column2:13 + │ └── column3:7 => column3:14 + ├── scan uniq_partial + │ └── columns: k:8!null a:9 b:10 + └── filters + ├── column2:13 = a:9 + ├── column3:14 > 0 + ├── b:10 > 0 + └── column1:12 != k:8 + +# No need to plan checks for a since it's always null. +build +INSERT INTO uniq_partial VALUES (1, NULL, 1), (2, NULL, 2) +---- +insert uniq_partial + ├── columns: + ├── insert-mapping: + │ ├── column1:5 => k:1 + │ ├── column2:6 => a:2 + │ └── column3:7 => b:3 + └── values + ├── columns: column1:5!null column2:6 column3:7!null + ├── (1, NULL::INT8, 1) + └── (2, NULL::INT8, 2) + +# Insert with non-constant input. +build +INSERT INTO uniq_partial SELECT k, v, w FROM other +---- +insert uniq_partial + ├── columns: + ├── insert-mapping: + │ ├── other.k:5 => uniq_partial.k:1 + │ ├── other.v:6 => a:2 + │ └── other.w:7 => b:3 + ├── input binding: &1 + ├── project + │ ├── columns: other.k:5 other.v:6 other.w:7!null + │ └── scan other + │ └── columns: other.k:5 other.v:6 other.w:7!null x:8 y:9 rowid:10!null other.crdb_internal_mvcc_timestamp:11 + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: k:16 v:17 w:18!null + ├── with-scan &1 + │ ├── columns: k:16 v:17 w:18!null + │ └── mapping: + │ ├── other.k:5 => k:16 + │ ├── other.v:6 => v:17 + │ └── other.w:7 => w:18 + ├── scan uniq_partial + │ └── columns: uniq_partial.k:12!null a:13 b:14 + └── filters + ├── v:17 = a:13 + ├── w:18 > 0 + ├── b:14 > 0 + └── k:16 != uniq_partial.k:12 + +exec-ddl +CREATE TABLE uniq_partial_overlaps_pk ( + a INT PRIMARY KEY, + b INT, + c INT, + UNIQUE WITHOUT INDEX (a) WHERE c > 0, + UNIQUE WITHOUT INDEX (a, b) WHERE c > 0 +) +---- + +# Insert with constant input. +# Do not build uniqueness checks when the primary key columns are a subset of +# the partial unique constraint columns. +build +INSERT INTO uniq_partial_overlaps_pk VALUES (1, 1, 1), (2, 2, 2) +---- +insert uniq_partial_overlaps_pk + ├── columns: + ├── insert-mapping: + │ ├── column1:5 => a:1 + │ ├── column2:6 => b:2 + │ └── column3:7 => c:3 + └── values + ├── columns: column1:5!null column2:6!null column3:7!null + ├── (1, 1, 1) + └── (2, 2, 2) + +# Insert with non-constant input. +# Do not build uniqueness checks when the primary key columns are a subset of +# the partial unique constraint columns. +build +INSERT INTO uniq_partial_overlaps_pk SELECT k, v, x FROM other +---- +insert uniq_partial_overlaps_pk + ├── columns: + ├── insert-mapping: + │ ├── k:5 => a:1 + │ ├── v:6 => b:2 + │ └── x:8 => c:3 + └── project + ├── columns: k:5 v:6 x:8 + └── scan other + └── columns: k:5 v:6 w:7!null x:8 y:9 rowid:10!null other.crdb_internal_mvcc_timestamp:11 + +exec-ddl +CREATE TABLE uniq_partial_hidden_pk ( + a INT, + b INT, + c INT, + UNIQUE WITHOUT INDEX (b) WHERE c > 0 +) +---- + +# Insert with constant input. +# Add inequality filters for the hidden primary key column. +build +INSERT INTO uniq_partial_hidden_pk VALUES (1, 1), (2, 2) +---- +insert uniq_partial_hidden_pk + ├── columns: + ├── insert-mapping: + │ ├── column1:6 => a:1 + │ ├── column2:7 => b:2 + │ ├── column8:8 => c:3 + │ └── column9:9 => rowid:4 + ├── input binding: &1 + ├── project + │ ├── columns: column8:8 column9:9 column1:6!null column2:7!null + │ ├── values + │ │ ├── columns: column1:6!null column2:7!null + │ │ ├── (1, 1) + │ │ └── (2, 2) + │ └── projections + │ ├── NULL::INT8 [as=column8:8] + │ └── unique_rowid() [as=column9:9] + └── unique-checks + └── unique-checks-item: uniq_partial_hidden_pk(b) + └── semi-join (hash) + ├── columns: column1:15!null column2:16!null column8:17 column9:18 + ├── with-scan &1 + │ ├── columns: column1:15!null column2:16!null column8:17 column9:18 + │ └── mapping: + │ ├── column1:6 => column1:15 + │ ├── column2:7 => column2:16 + │ ├── column8:8 => column8:17 + │ └── column9:9 => column9:18 + ├── scan uniq_partial_hidden_pk + │ └── columns: a:10 b:11 c:12 rowid:13!null + └── filters + ├── column2:16 = b:11 + ├── column8:17 > 0 + ├── c:12 > 0 + └── column9:18 != rowid:13 + +# Add inequality filters for the hidden primary key column. +build +INSERT INTO uniq_partial_hidden_pk SELECT k, v FROM other +---- +insert uniq_partial_hidden_pk + ├── columns: + ├── insert-mapping: + │ ├── other.k:6 => a:1 + │ ├── other.v:7 => b:2 + │ ├── column13:13 => c:3 + │ └── column14:14 => uniq_partial_hidden_pk.rowid:4 + ├── input binding: &1 + ├── project + │ ├── columns: column13:13 column14:14 other.k:6 other.v:7 + │ ├── project + │ │ ├── columns: other.k:6 other.v:7 + │ │ └── scan other + │ │ └── columns: other.k:6 other.v:7 w:8!null x:9 y:10 other.rowid:11!null other.crdb_internal_mvcc_timestamp:12 + │ └── projections + │ ├── NULL::INT8 [as=column13:13] + │ └── unique_rowid() [as=column14:14] + └── unique-checks + └── unique-checks-item: uniq_partial_hidden_pk(b) + └── semi-join (hash) + ├── columns: k:20 v:21 column13:22 column14:23 + ├── with-scan &1 + │ ├── columns: k:20 v:21 column13:22 column14:23 + │ └── mapping: + │ ├── other.k:6 => k:20 + │ ├── other.v:7 => v:21 + │ ├── column13:13 => column13:22 + │ └── column14:14 => column14:23 + ├── scan uniq_partial_hidden_pk + │ └── columns: a:15 b:16 c:17 uniq_partial_hidden_pk.rowid:18!null + └── filters + ├── v:21 = b:16 + ├── column13:22 > 0 + ├── c:17 > 0 + └── column14:23 != uniq_partial_hidden_pk.rowid:18 diff --git a/pkg/sql/opt/optbuilder/testdata/unique-checks-update b/pkg/sql/opt/optbuilder/testdata/unique-checks-update index 007675fc8f92..569b50dde45b 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-update +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-update @@ -15,47 +15,52 @@ UPDATE uniq SET w = 1, x = 2 ---- update uniq ├── columns: - ├── fetch columns: uniq.k:7 v:8 w:9 x:10 uniq.y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 w:9 x:10 uniq.y:11 ├── update-mapping: │ ├── w_new:13 => w:3 │ └── x_new:14 => x:4 ├── input binding: &1 ├── project - │ ├── columns: w_new:13!null x_new:14!null uniq.k:7!null v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: w_new:13!null x_new:14!null uniq.k:7!null uniq.v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: uniq.k:7!null v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq.k:7!null uniq.v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ └── projections │ ├── 1 [as=w_new:13] │ └── 2 [as=x_new:14] └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w_new:15!null k:16!null + │ ├── columns: k:21!null v:22 w_new:23!null x_new:24!null y:25 │ ├── with-scan &1 - │ │ ├── columns: w_new:15!null k:16!null + │ │ ├── columns: k:21!null v:22 w_new:23!null x_new:24!null y:25 │ │ └── mapping: - │ │ ├── w_new:13 => w_new:15 - │ │ └── uniq.k:7 => k:16 + │ │ ├── uniq.k:7 => k:21 + │ │ ├── uniq.v:8 => v:22 + │ │ ├── w_new:13 => w_new:23 + │ │ ├── x_new:14 => x_new:24 + │ │ └── uniq.y:11 => y:25 │ ├── scan uniq - │ │ └── columns: uniq.k:17!null w:19 + │ │ └── columns: uniq.k:15!null uniq.v:16 w:17 x:18 uniq.y:19 │ └── filters - │ ├── w_new:15 = w:19 - │ └── k:16 != uniq.k:17 + │ ├── w_new:23 = w:17 + │ └── k:21 != uniq.k:15 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x_new:23!null y:24 k:25!null + ├── columns: k:32!null v:33 w_new:34!null x_new:35!null y:36 ├── with-scan &1 - │ ├── columns: x_new:23!null y:24 k:25!null + │ ├── columns: k:32!null v:33 w_new:34!null x_new:35!null y:36 │ └── mapping: - │ ├── x_new:14 => x_new:23 - │ ├── uniq.y:11 => y:24 - │ └── uniq.k:7 => k:25 + │ ├── uniq.k:7 => k:32 + │ ├── uniq.v:8 => v:33 + │ ├── w_new:13 => w_new:34 + │ ├── x_new:14 => x_new:35 + │ └── uniq.y:11 => y:36 ├── scan uniq - │ └── columns: uniq.k:26!null x:29 uniq.y:30 + │ └── columns: uniq.k:26!null uniq.v:27 w:28 x:29 uniq.y:30 └── filters - ├── x_new:23 = x:29 - ├── y:24 = uniq.y:30 - └── k:25 != uniq.k:26 + ├── x_new:35 = x:29 + ├── y:36 = uniq.y:30 + └── k:32 != uniq.k:26 # No need to plan checks for w since it's aways null. build @@ -63,34 +68,36 @@ UPDATE uniq SET w = NULL, x = 1 ---- update uniq ├── columns: - ├── fetch columns: uniq.k:7 v:8 w:9 x:10 uniq.y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 w:9 x:10 uniq.y:11 ├── update-mapping: │ ├── w_new:13 => w:3 │ └── x_new:14 => x:4 ├── input binding: &1 ├── project - │ ├── columns: w_new:13 x_new:14!null uniq.k:7!null v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: w_new:13 x_new:14!null uniq.k:7!null uniq.v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: uniq.k:7!null v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq.k:7!null uniq.v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ └── projections │ ├── NULL::INT8 [as=w_new:13] │ └── 1 [as=x_new:14] └── unique-checks └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x_new:15!null y:16 k:17!null + ├── columns: k:21!null v:22 w_new:23 x_new:24!null y:25 ├── with-scan &1 - │ ├── columns: x_new:15!null y:16 k:17!null + │ ├── columns: k:21!null v:22 w_new:23 x_new:24!null y:25 │ └── mapping: - │ ├── x_new:14 => x_new:15 - │ ├── uniq.y:11 => y:16 - │ └── uniq.k:7 => k:17 + │ ├── uniq.k:7 => k:21 + │ ├── uniq.v:8 => v:22 + │ ├── w_new:13 => w_new:23 + │ ├── x_new:14 => x_new:24 + │ └── uniq.y:11 => y:25 ├── scan uniq - │ └── columns: uniq.k:18!null x:21 uniq.y:22 + │ └── columns: uniq.k:15!null uniq.v:16 w:17 x:18 uniq.y:19 └── filters - ├── x_new:15 = x:21 - ├── y:16 = uniq.y:22 - └── k:17 != uniq.k:18 + ├── x_new:24 = x:18 + ├── y:25 = uniq.y:19 + └── k:21 != uniq.k:15 # No need to plan checks for x,y since x is aways null. # Also update the primary key. @@ -99,16 +106,16 @@ UPDATE uniq SET k = 1, w = 2, x = NULL ---- update uniq ├── columns: - ├── fetch columns: k:7 v:8 w:9 x:10 y:11 + ├── fetch columns: k:7 uniq.v:8 w:9 x:10 uniq.y:11 ├── update-mapping: │ ├── k_new:13 => k:1 │ ├── w_new:14 => w:3 │ └── x_new:15 => x:4 ├── input binding: &1 ├── project - │ ├── columns: k_new:13!null w_new:14!null x_new:15 k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: k_new:13!null w_new:14!null x_new:15 k:7!null uniq.v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: k:7!null uniq.v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ └── projections │ ├── 1 [as=k_new:13] │ ├── 2 [as=w_new:14] @@ -116,17 +123,20 @@ update uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: w_new:16!null k_new:17!null + ├── columns: k_new:22!null v:23 w_new:24!null x_new:25 y:26 ├── with-scan &1 - │ ├── columns: w_new:16!null k_new:17!null + │ ├── columns: k_new:22!null v:23 w_new:24!null x_new:25 y:26 │ └── mapping: - │ ├── w_new:14 => w_new:16 - │ └── k_new:13 => k_new:17 + │ ├── k_new:13 => k_new:22 + │ ├── uniq.v:8 => v:23 + │ ├── w_new:14 => w_new:24 + │ ├── x_new:15 => x_new:25 + │ └── uniq.y:11 => y:26 ├── scan uniq - │ └── columns: k:18!null w:20 + │ └── columns: k:16!null uniq.v:17 w:18 x:19 uniq.y:20 └── filters - ├── w_new:16 = w:20 - └── k_new:17 != k:18 + ├── w_new:24 = w:18 + └── k_new:22 != k:16 # No need to plan checks for x,y since y is aways null. build @@ -134,17 +144,17 @@ UPDATE uniq SET w = 1, y = NULL WHERE k = 1 ---- update uniq ├── columns: - ├── fetch columns: uniq.k:7 v:8 w:9 x:10 y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 w:9 uniq.x:10 y:11 ├── update-mapping: │ ├── w_new:13 => w:3 │ └── y_new:14 => y:5 ├── input binding: &1 ├── project - │ ├── columns: w_new:13!null y_new:14 uniq.k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: w_new:13!null y_new:14 uniq.k:7!null uniq.v:8 w:9 uniq.x:10 y:11 crdb_internal_mvcc_timestamp:12 │ ├── select - │ │ ├── columns: uniq.k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ ├── columns: uniq.k:7!null uniq.v:8 w:9 uniq.x:10 y:11 crdb_internal_mvcc_timestamp:12 │ │ ├── scan uniq - │ │ │ └── columns: uniq.k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ │ └── columns: uniq.k:7!null uniq.v:8 w:9 uniq.x:10 y:11 crdb_internal_mvcc_timestamp:12 │ │ └── filters │ │ └── uniq.k:7 = 1 │ └── projections @@ -153,17 +163,20 @@ update uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: w_new:15!null k:16!null + ├── columns: k:21!null v:22 w_new:23!null x:24 y_new:25 ├── with-scan &1 - │ ├── columns: w_new:15!null k:16!null + │ ├── columns: k:21!null v:22 w_new:23!null x:24 y_new:25 │ └── mapping: - │ ├── w_new:13 => w_new:15 - │ └── uniq.k:7 => k:16 + │ ├── uniq.k:7 => k:21 + │ ├── uniq.v:8 => v:22 + │ ├── w_new:13 => w_new:23 + │ ├── uniq.x:10 => x:24 + │ └── y_new:14 => y_new:25 ├── scan uniq - │ └── columns: uniq.k:17!null w:19 + │ └── columns: uniq.k:15!null uniq.v:16 w:17 uniq.x:18 y:19 └── filters - ├── w_new:15 = w:19 - └── k:16 != uniq.k:17 + ├── w_new:23 = w:17 + └── k:21 != uniq.k:15 # No need to plan checks since none of the columns requiring checks are updated. build @@ -236,32 +249,37 @@ update uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:20!null k:21!null + │ ├── columns: k:26!null v:27 w:28!null x:29 y:30 │ ├── with-scan &1 - │ │ ├── columns: w:20!null k:21!null + │ │ ├── columns: k:26!null v:27 w:28!null x:29 y:30 │ │ └── mapping: - │ │ ├── other.w:15 => w:20 - │ │ └── uniq.k:7 => k:21 + │ │ ├── uniq.k:7 => k:26 + │ │ ├── uniq.v:8 => v:27 + │ │ ├── other.w:15 => w:28 + │ │ ├── other.x:16 => x:29 + │ │ └── uniq.y:11 => y:30 │ ├── scan uniq - │ │ └── columns: uniq.k:22!null uniq.w:24 + │ │ └── columns: uniq.k:20!null uniq.v:21 uniq.w:22 uniq.x:23 uniq.y:24 │ └── filters - │ ├── w:20 = uniq.w:24 - │ └── k:21 != uniq.k:22 + │ ├── w:28 = uniq.w:22 + │ └── k:26 != uniq.k:20 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:28 y:29 k:30!null + ├── columns: k:37!null v:38 w:39!null x:40 y:41 ├── with-scan &1 - │ ├── columns: x:28 y:29 k:30!null + │ ├── columns: k:37!null v:38 w:39!null x:40 y:41 │ └── mapping: - │ ├── other.x:16 => x:28 - │ ├── uniq.y:11 => y:29 - │ └── uniq.k:7 => k:30 + │ ├── uniq.k:7 => k:37 + │ ├── uniq.v:8 => v:38 + │ ├── other.w:15 => w:39 + │ ├── other.x:16 => x:40 + │ └── uniq.y:11 => y:41 ├── scan uniq - │ └── columns: uniq.k:31!null uniq.x:34 uniq.y:35 + │ └── columns: uniq.k:31!null uniq.v:32 uniq.w:33 uniq.x:34 uniq.y:35 └── filters - ├── x:28 = uniq.x:34 - ├── y:29 = uniq.y:35 - └── k:30 != uniq.k:31 + ├── x:40 = uniq.x:34 + ├── y:41 = uniq.y:35 + └── k:37 != uniq.k:31 exec-ddl CREATE TABLE uniq_overlaps_pk ( @@ -308,48 +326,51 @@ update uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b_new:15!null c_new:16!null a_new:17!null + │ ├── columns: a_new:20!null b_new:21!null c_new:22!null d_new:23!null │ ├── with-scan &1 - │ │ ├── columns: b_new:15!null c_new:16!null a_new:17!null + │ │ ├── columns: a_new:20!null b_new:21!null c_new:22!null d_new:23!null │ │ └── mapping: - │ │ ├── b_new:12 => b_new:15 - │ │ ├── c_new:13 => c_new:16 - │ │ └── a_new:11 => a_new:17 + │ │ ├── a_new:11 => a_new:20 + │ │ ├── b_new:12 => b_new:21 + │ │ ├── c_new:13 => c_new:22 + │ │ └── d_new:14 => d_new:23 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:18!null b:19!null c:20 + │ │ └── columns: a:15!null b:16!null c:17 d:18 │ └── filters - │ ├── b_new:15 = b:19 - │ ├── c_new:16 = c:20 - │ └── a_new:17 != a:18 + │ ├── b_new:21 = b:16 + │ ├── c_new:22 = c:17 + │ └── a_new:20 != a:15 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a_new:23!null b_new:24!null + │ ├── columns: a_new:29!null b_new:30!null c_new:31!null d_new:32!null │ ├── with-scan &1 - │ │ ├── columns: a_new:23!null b_new:24!null + │ │ ├── columns: a_new:29!null b_new:30!null c_new:31!null d_new:32!null │ │ └── mapping: - │ │ ├── a_new:11 => a_new:23 - │ │ └── b_new:12 => b_new:24 + │ │ ├── a_new:11 => a_new:29 + │ │ ├── b_new:12 => b_new:30 + │ │ ├── c_new:13 => c_new:31 + │ │ └── d_new:14 => d_new:32 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:25!null b:26!null + │ │ └── columns: a:24!null b:25!null c:26 d:27 │ └── filters - │ ├── a_new:23 = a:25 - │ └── b_new:24 != b:26 + │ ├── a_new:29 = a:24 + │ └── b_new:30 != b:25 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c_new:30!null d_new:31!null a_new:32!null b_new:33!null + ├── columns: a_new:38!null b_new:39!null c_new:40!null d_new:41!null ├── with-scan &1 - │ ├── columns: c_new:30!null d_new:31!null a_new:32!null b_new:33!null + │ ├── columns: a_new:38!null b_new:39!null c_new:40!null d_new:41!null │ └── mapping: - │ ├── c_new:13 => c_new:30 - │ ├── d_new:14 => d_new:31 - │ ├── a_new:11 => a_new:32 - │ └── b_new:12 => b_new:33 + │ ├── a_new:11 => a_new:38 + │ ├── b_new:12 => b_new:39 + │ ├── c_new:13 => c_new:40 + │ └── d_new:14 => d_new:41 ├── scan uniq_overlaps_pk - │ └── columns: a:34!null b:35!null c:36 d:37 + │ └── columns: a:33!null b:34!null c:35 d:36 └── filters - ├── c_new:30 = c:36 - ├── d_new:31 = d:37 - └── (a_new:32 != a:34) OR (b_new:33 != b:35) + ├── c_new:40 = c:35 + ├── d_new:41 = d:36 + └── (a_new:38 != a:33) OR (b_new:39 != b:34) # Update with non-constant input. # No need to add a check for b,c since those columns weren't updated. @@ -399,33 +420,35 @@ update uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: k:18 b:19!null + │ ├── columns: k:23 b:24!null c:25 v:26 │ ├── with-scan &1 - │ │ ├── columns: k:18 b:19!null + │ │ ├── columns: k:23 b:24!null c:25 v:26 │ │ └── mapping: - │ │ ├── other.k:11 => k:18 - │ │ └── uniq_overlaps_pk.b:7 => b:19 + │ │ ├── other.k:11 => k:23 + │ │ ├── uniq_overlaps_pk.b:7 => b:24 + │ │ ├── uniq_overlaps_pk.c:8 => c:25 + │ │ └── other.v:12 => v:26 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:20!null uniq_overlaps_pk.b:21!null + │ │ └── columns: a:18!null uniq_overlaps_pk.b:19!null uniq_overlaps_pk.c:20 d:21 │ └── filters - │ ├── k:18 = a:20 - │ └── b:19 != uniq_overlaps_pk.b:21 + │ ├── k:23 = a:18 + │ └── b:24 != uniq_overlaps_pk.b:19 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:25 v:26 k:27 b:28!null + ├── columns: k:32 b:33!null c:34 v:35 ├── with-scan &1 - │ ├── columns: c:25 v:26 k:27 b:28!null + │ ├── columns: k:32 b:33!null c:34 v:35 │ └── mapping: - │ ├── uniq_overlaps_pk.c:8 => c:25 - │ ├── other.v:12 => v:26 - │ ├── other.k:11 => k:27 - │ └── uniq_overlaps_pk.b:7 => b:28 + │ ├── other.k:11 => k:32 + │ ├── uniq_overlaps_pk.b:7 => b:33 + │ ├── uniq_overlaps_pk.c:8 => c:34 + │ └── other.v:12 => v:35 ├── scan uniq_overlaps_pk - │ └── columns: a:29!null uniq_overlaps_pk.b:30!null uniq_overlaps_pk.c:31 d:32 + │ └── columns: a:27!null uniq_overlaps_pk.b:28!null uniq_overlaps_pk.c:29 d:30 └── filters - ├── c:25 = uniq_overlaps_pk.c:31 - ├── v:26 = d:32 - └── (k:27 != a:29) OR (b:28 != uniq_overlaps_pk.b:30) + ├── c:34 = uniq_overlaps_pk.c:29 + ├── v:35 = d:30 + └── (k:32 != a:27) OR (b:33 != uniq_overlaps_pk.b:28) exec-ddl CREATE TABLE uniq_hidden_pk ( @@ -447,47 +470,51 @@ UPDATE uniq_hidden_pk SET a = 1 ---- update uniq_hidden_pk ├── columns: - ├── fetch columns: a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 + ├── fetch columns: a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 ├── update-mapping: │ └── a_new:13 => a:1 ├── input binding: &1 ├── project - │ ├── columns: a_new:13!null a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null crdb_internal_mvcc_timestamp:12 + │ ├── columns: a_new:13!null a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null crdb_internal_mvcc_timestamp:12 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null crdb_internal_mvcc_timestamp:12 + │ │ └── columns: a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null crdb_internal_mvcc_timestamp:12 │ └── projections │ └── 1 [as=a_new:13] └── unique-checks ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: a_new:14!null b:15 d:16 rowid:17!null + │ ├── columns: a_new:20!null b:21 c:22 d:23 rowid:24!null │ ├── with-scan &1 - │ │ ├── columns: a_new:14!null b:15 d:16 rowid:17!null + │ │ ├── columns: a_new:20!null b:21 c:22 d:23 rowid:24!null │ │ └── mapping: - │ │ ├── a_new:13 => a_new:14 - │ │ ├── uniq_hidden_pk.b:8 => b:15 - │ │ ├── uniq_hidden_pk.d:10 => d:16 - │ │ └── uniq_hidden_pk.rowid:11 => rowid:17 + │ │ ├── a_new:13 => a_new:20 + │ │ ├── uniq_hidden_pk.b:8 => b:21 + │ │ ├── uniq_hidden_pk.c:9 => c:22 + │ │ ├── uniq_hidden_pk.d:10 => d:23 + │ │ └── uniq_hidden_pk.rowid:11 => rowid:24 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:18 uniq_hidden_pk.b:19 uniq_hidden_pk.d:21 uniq_hidden_pk.rowid:22!null + │ │ └── columns: a:14 uniq_hidden_pk.b:15 uniq_hidden_pk.c:16 uniq_hidden_pk.d:17 uniq_hidden_pk.rowid:18!null │ └── filters - │ ├── a_new:14 = a:18 - │ ├── b:15 = uniq_hidden_pk.b:19 - │ ├── d:16 = uniq_hidden_pk.d:21 - │ └── rowid:17 != uniq_hidden_pk.rowid:22 + │ ├── a_new:20 = a:14 + │ ├── b:21 = uniq_hidden_pk.b:15 + │ ├── d:23 = uniq_hidden_pk.d:17 + │ └── rowid:24 != uniq_hidden_pk.rowid:18 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: a_new:24!null rowid:25!null + ├── columns: a_new:31!null b:32 c:33 d:34 rowid:35!null ├── with-scan &1 - │ ├── columns: a_new:24!null rowid:25!null + │ ├── columns: a_new:31!null b:32 c:33 d:34 rowid:35!null │ └── mapping: - │ ├── a_new:13 => a_new:24 - │ └── uniq_hidden_pk.rowid:11 => rowid:25 + │ ├── a_new:13 => a_new:31 + │ ├── uniq_hidden_pk.b:8 => b:32 + │ ├── uniq_hidden_pk.c:9 => c:33 + │ ├── uniq_hidden_pk.d:10 => d:34 + │ └── uniq_hidden_pk.rowid:11 => rowid:35 ├── scan uniq_hidden_pk - │ └── columns: a:26 uniq_hidden_pk.rowid:30!null + │ └── columns: a:25 uniq_hidden_pk.b:26 uniq_hidden_pk.c:27 uniq_hidden_pk.d:28 uniq_hidden_pk.rowid:29!null └── filters - ├── a_new:24 = a:26 - └── rowid:25 != uniq_hidden_pk.rowid:30 + ├── a_new:31 = a:25 + └── rowid:35 != uniq_hidden_pk.rowid:29 # Update with non-constant input. # No need to add a check for b,c since those columns weren't updated. @@ -497,45 +524,49 @@ UPDATE uniq_hidden_pk SET a = k FROM other ---- update uniq_hidden_pk ├── columns: - ├── fetch columns: a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 + ├── fetch columns: a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 ├── update-mapping: │ └── other.k:13 => a:1 ├── input binding: &1 ├── inner-join (cross) - │ ├── columns: a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null uniq_hidden_pk.crdb_internal_mvcc_timestamp:12 other.k:13 v:14 w:15!null x:16 y:17 other.rowid:18!null other.crdb_internal_mvcc_timestamp:19 + │ ├── columns: a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null uniq_hidden_pk.crdb_internal_mvcc_timestamp:12 other.k:13 v:14 w:15!null x:16 y:17 other.rowid:18!null other.crdb_internal_mvcc_timestamp:19 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null uniq_hidden_pk.crdb_internal_mvcc_timestamp:12 + │ │ └── columns: a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null uniq_hidden_pk.crdb_internal_mvcc_timestamp:12 │ ├── scan other │ │ └── columns: other.k:13 v:14 w:15!null x:16 y:17 other.rowid:18!null other.crdb_internal_mvcc_timestamp:19 │ └── filters (true) └── unique-checks ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: k:20 b:21 d:22 rowid:23!null + │ ├── columns: k:26 b:27 c:28 d:29 rowid:30!null │ ├── with-scan &1 - │ │ ├── columns: k:20 b:21 d:22 rowid:23!null + │ │ ├── columns: k:26 b:27 c:28 d:29 rowid:30!null │ │ └── mapping: - │ │ ├── other.k:13 => k:20 - │ │ ├── uniq_hidden_pk.b:8 => b:21 - │ │ ├── uniq_hidden_pk.d:10 => d:22 - │ │ └── uniq_hidden_pk.rowid:11 => rowid:23 + │ │ ├── other.k:13 => k:26 + │ │ ├── uniq_hidden_pk.b:8 => b:27 + │ │ ├── uniq_hidden_pk.c:9 => c:28 + │ │ ├── uniq_hidden_pk.d:10 => d:29 + │ │ └── uniq_hidden_pk.rowid:11 => rowid:30 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:24 uniq_hidden_pk.b:25 uniq_hidden_pk.d:27 uniq_hidden_pk.rowid:28!null + │ │ └── columns: a:20 uniq_hidden_pk.b:21 uniq_hidden_pk.c:22 uniq_hidden_pk.d:23 uniq_hidden_pk.rowid:24!null │ └── filters - │ ├── k:20 = a:24 - │ ├── b:21 = uniq_hidden_pk.b:25 - │ ├── d:22 = uniq_hidden_pk.d:27 - │ └── rowid:23 != uniq_hidden_pk.rowid:28 + │ ├── k:26 = a:20 + │ ├── b:27 = uniq_hidden_pk.b:21 + │ ├── d:29 = uniq_hidden_pk.d:23 + │ └── rowid:30 != uniq_hidden_pk.rowid:24 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: k:30 rowid:31!null + ├── columns: k:37 b:38 c:39 d:40 rowid:41!null ├── with-scan &1 - │ ├── columns: k:30 rowid:31!null + │ ├── columns: k:37 b:38 c:39 d:40 rowid:41!null │ └── mapping: - │ ├── other.k:13 => k:30 - │ └── uniq_hidden_pk.rowid:11 => rowid:31 + │ ├── other.k:13 => k:37 + │ ├── uniq_hidden_pk.b:8 => b:38 + │ ├── uniq_hidden_pk.c:9 => c:39 + │ ├── uniq_hidden_pk.d:10 => d:40 + │ └── uniq_hidden_pk.rowid:11 => rowid:41 ├── scan uniq_hidden_pk - │ └── columns: a:32 uniq_hidden_pk.rowid:36!null + │ └── columns: a:31 uniq_hidden_pk.b:32 uniq_hidden_pk.c:33 uniq_hidden_pk.d:34 uniq_hidden_pk.rowid:35!null └── filters - ├── k:30 = a:32 - └── rowid:31 != uniq_hidden_pk.rowid:36 + ├── k:37 = a:31 + └── rowid:41 != uniq_hidden_pk.rowid:35 diff --git a/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert b/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert index d463dd203dcb..e528887c03e9 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert @@ -63,32 +63,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:19!null upsert_k:20 + │ ├── columns: upsert_k:25 column2:26!null column3:27!null column4:28!null column5:29!null │ ├── with-scan &1 - │ │ ├── columns: column3:19!null upsert_k:20 + │ │ ├── columns: upsert_k:25 column2:26!null column3:27!null column4:28!null column5:29!null │ │ └── mapping: - │ │ ├── column3:9 => column3:19 - │ │ └── upsert_k:18 => upsert_k:20 + │ │ ├── upsert_k:18 => upsert_k:25 + │ │ ├── column2:8 => column2:26 + │ │ ├── column3:9 => column3:27 + │ │ ├── column4:10 => column4:28 + │ │ └── column5:11 => column5:29 │ ├── scan uniq - │ │ └── columns: k:21!null w:23 + │ │ └── columns: k:19!null v:20 w:21 x:22 y:23 │ └── filters - │ ├── column3:19 = w:23 - │ └── upsert_k:20 != k:21 + │ ├── column3:27 = w:21 + │ └── upsert_k:25 != k:19 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:27!null column5:28!null upsert_k:29 + ├── columns: upsert_k:36 column2:37!null column3:38!null column4:39!null column5:40!null ├── with-scan &1 - │ ├── columns: column4:27!null column5:28!null upsert_k:29 + │ ├── columns: upsert_k:36 column2:37!null column3:38!null column4:39!null column5:40!null │ └── mapping: - │ ├── column4:10 => column4:27 - │ ├── column5:11 => column5:28 - │ └── upsert_k:18 => upsert_k:29 + │ ├── upsert_k:18 => upsert_k:36 + │ ├── column2:8 => column2:37 + │ ├── column3:9 => column3:38 + │ ├── column4:10 => column4:39 + │ └── column5:11 => column5:40 ├── scan uniq - │ └── columns: k:30!null x:33 y:34 + │ └── columns: k:30!null v:31 w:32 x:33 y:34 └── filters - ├── column4:27 = x:33 - ├── column5:28 = y:34 - └── upsert_k:29 != k:30 + ├── column4:39 = x:33 + ├── column5:40 = y:34 + └── upsert_k:36 != k:30 # TODO(rytaft): The default value for x is NULL, and we're not updating either # x or y. Therefore, we could avoid planning checks for (x,y) (see #58300). @@ -146,32 +151,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:21!null upsert_k:22 + │ ├── columns: upsert_k:27 column2:28!null column3:29!null upsert_x:30 upsert_y:31 │ ├── with-scan &1 - │ │ ├── columns: column3:21!null upsert_k:22 + │ │ ├── columns: upsert_k:27 column2:28!null column3:29!null upsert_x:30 upsert_y:31 │ │ └── mapping: - │ │ ├── column3:9 => column3:21 - │ │ └── upsert_k:18 => upsert_k:22 + │ │ ├── upsert_k:18 => upsert_k:27 + │ │ ├── column2:8 => column2:28 + │ │ ├── column3:9 => column3:29 + │ │ ├── upsert_x:19 => upsert_x:30 + │ │ └── upsert_y:20 => upsert_y:31 │ ├── scan uniq - │ │ └── columns: k:23!null w:25 + │ │ └── columns: k:21!null v:22 w:23 x:24 y:25 │ └── filters - │ ├── column3:21 = w:25 - │ └── upsert_k:22 != k:23 + │ ├── column3:29 = w:23 + │ └── upsert_k:27 != k:21 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:29 upsert_y:30 upsert_k:31 + ├── columns: upsert_k:38 column2:39!null column3:40!null upsert_x:41 upsert_y:42 ├── with-scan &1 - │ ├── columns: upsert_x:29 upsert_y:30 upsert_k:31 + │ ├── columns: upsert_k:38 column2:39!null column3:40!null upsert_x:41 upsert_y:42 │ └── mapping: - │ ├── upsert_x:19 => upsert_x:29 - │ ├── upsert_y:20 => upsert_y:30 - │ └── upsert_k:18 => upsert_k:31 + │ ├── upsert_k:18 => upsert_k:38 + │ ├── column2:8 => column2:39 + │ ├── column3:9 => column3:40 + │ ├── upsert_x:19 => upsert_x:41 + │ └── upsert_y:20 => upsert_y:42 ├── scan uniq - │ └── columns: k:32!null x:35 y:36 + │ └── columns: k:32!null v:33 w:34 x:35 y:36 └── filters - ├── upsert_x:29 = x:35 - ├── upsert_y:30 = y:36 - └── upsert_k:31 != k:32 + ├── upsert_x:41 = x:35 + ├── upsert_y:42 = y:36 + └── upsert_k:38 != k:32 # TODO(rytaft): No need to plan checks for w since it's aways NULL. # We currently can't determine that w is always NULL since the function @@ -230,32 +240,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column2:21 upsert_k:22 + │ ├── columns: upsert_k:27 upsert_v:28 column2:29 column3:30 upsert_y:31 │ ├── with-scan &1 - │ │ ├── columns: column2:21 upsert_k:22 + │ │ ├── columns: upsert_k:27 upsert_v:28 column2:29 column3:30 upsert_y:31 │ │ └── mapping: - │ │ ├── column2:8 => column2:21 - │ │ └── upsert_k:18 => upsert_k:22 + │ │ ├── upsert_k:18 => upsert_k:27 + │ │ ├── upsert_v:19 => upsert_v:28 + │ │ ├── column2:8 => column2:29 + │ │ ├── column3:9 => column3:30 + │ │ └── upsert_y:20 => upsert_y:31 │ ├── scan uniq - │ │ └── columns: k:23!null w:25 + │ │ └── columns: k:21!null v:22 w:23 x:24 y:25 │ └── filters - │ ├── column2:21 = w:25 - │ └── upsert_k:22 != k:23 + │ ├── column2:29 = w:23 + │ └── upsert_k:27 != k:21 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column3:29 upsert_y:30 upsert_k:31 + ├── columns: upsert_k:38 upsert_v:39 column2:40 column3:41 upsert_y:42 ├── with-scan &1 - │ ├── columns: column3:29 upsert_y:30 upsert_k:31 + │ ├── columns: upsert_k:38 upsert_v:39 column2:40 column3:41 upsert_y:42 │ └── mapping: - │ ├── column3:9 => column3:29 - │ ├── upsert_y:20 => upsert_y:30 - │ └── upsert_k:18 => upsert_k:31 + │ ├── upsert_k:18 => upsert_k:38 + │ ├── upsert_v:19 => upsert_v:39 + │ ├── column2:8 => column2:40 + │ ├── column3:9 => column3:41 + │ └── upsert_y:20 => upsert_y:42 ├── scan uniq - │ └── columns: k:32!null x:35 y:36 + │ └── columns: k:32!null v:33 w:34 x:35 y:36 └── filters - ├── column3:29 = x:35 - ├── upsert_y:30 = y:36 - └── upsert_k:31 != k:32 + ├── column3:41 = x:35 + ├── upsert_y:42 = y:36 + └── upsert_k:38 != k:32 # Upsert with non-constant input. # TODO(rytaft): The default value for x is NULL, and we're not updating either @@ -314,32 +329,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:23!null upsert_k:24 + │ ├── columns: upsert_k:29 v:30 w:31!null column14:32 column15:33!null │ ├── with-scan &1 - │ │ ├── columns: w:23!null upsert_k:24 + │ │ ├── columns: upsert_k:29 v:30 w:31!null column14:32 column15:33!null │ │ └── mapping: - │ │ ├── other.w:9 => w:23 - │ │ └── upsert_k:22 => upsert_k:24 + │ │ ├── upsert_k:22 => upsert_k:29 + │ │ ├── other.v:8 => v:30 + │ │ ├── other.w:9 => w:31 + │ │ ├── column14:14 => column14:32 + │ │ └── column15:15 => column15:33 │ ├── scan uniq - │ │ └── columns: uniq.k:25!null uniq.w:27 + │ │ └── columns: uniq.k:23!null uniq.v:24 uniq.w:25 uniq.x:26 uniq.y:27 │ └── filters - │ ├── w:23 = uniq.w:27 - │ └── upsert_k:24 != uniq.k:25 + │ ├── w:31 = uniq.w:25 + │ └── upsert_k:29 != uniq.k:23 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column14:31 column15:32!null upsert_k:33 + ├── columns: upsert_k:40 v:41 w:42!null column14:43 column15:44!null ├── with-scan &1 - │ ├── columns: column14:31 column15:32!null upsert_k:33 + │ ├── columns: upsert_k:40 v:41 w:42!null column14:43 column15:44!null │ └── mapping: - │ ├── column14:14 => column14:31 - │ ├── column15:15 => column15:32 - │ └── upsert_k:22 => upsert_k:33 + │ ├── upsert_k:22 => upsert_k:40 + │ ├── other.v:8 => v:41 + │ ├── other.w:9 => w:42 + │ ├── column14:14 => column14:43 + │ └── column15:15 => column15:44 ├── scan uniq - │ └── columns: uniq.k:34!null uniq.x:37 uniq.y:38 + │ └── columns: uniq.k:34!null uniq.v:35 uniq.w:36 uniq.x:37 uniq.y:38 └── filters - ├── column14:31 = uniq.x:37 - ├── column15:32 = uniq.y:38 - └── upsert_k:33 != uniq.k:34 + ├── column14:43 = uniq.x:37 + ├── column15:44 = uniq.y:38 + └── upsert_k:40 != uniq.k:34 # On conflict do update with constant input. # TODO(rytaft): The default value for x is NULL, and we're not updating either @@ -402,32 +422,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: upsert_w:23 upsert_k:24 + │ ├── columns: upsert_k:29 upsert_v:30 upsert_w:31 upsert_x:32 upsert_y:33 │ ├── with-scan &1 - │ │ ├── columns: upsert_w:23 upsert_k:24 + │ │ ├── columns: upsert_k:29 upsert_v:30 upsert_w:31 upsert_x:32 upsert_y:33 │ │ └── mapping: - │ │ ├── upsert_w:20 => upsert_w:23 - │ │ └── upsert_k:18 => upsert_k:24 + │ │ ├── upsert_k:18 => upsert_k:29 + │ │ ├── upsert_v:19 => upsert_v:30 + │ │ ├── upsert_w:20 => upsert_w:31 + │ │ ├── upsert_x:21 => upsert_x:32 + │ │ └── upsert_y:22 => upsert_y:33 │ ├── scan uniq - │ │ └── columns: k:25!null w:27 + │ │ └── columns: k:23!null v:24 w:25 x:26 y:27 │ └── filters - │ ├── upsert_w:23 = w:27 - │ └── upsert_k:24 != k:25 + │ ├── upsert_w:31 = w:25 + │ └── upsert_k:29 != k:23 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:31 upsert_y:32 upsert_k:33 + ├── columns: upsert_k:40 upsert_v:41 upsert_w:42 upsert_x:43 upsert_y:44 ├── with-scan &1 - │ ├── columns: upsert_x:31 upsert_y:32 upsert_k:33 + │ ├── columns: upsert_k:40 upsert_v:41 upsert_w:42 upsert_x:43 upsert_y:44 │ └── mapping: - │ ├── upsert_x:21 => upsert_x:31 - │ ├── upsert_y:22 => upsert_y:32 - │ └── upsert_k:18 => upsert_k:33 + │ ├── upsert_k:18 => upsert_k:40 + │ ├── upsert_v:19 => upsert_v:41 + │ ├── upsert_w:20 => upsert_w:42 + │ ├── upsert_x:21 => upsert_x:43 + │ └── upsert_y:22 => upsert_y:44 ├── scan uniq - │ └── columns: k:34!null x:37 y:38 + │ └── columns: k:34!null v:35 w:36 x:37 y:38 └── filters - ├── upsert_x:31 = x:37 - ├── upsert_y:32 = y:38 - └── upsert_k:33 != k:34 + ├── upsert_x:43 = x:37 + ├── upsert_y:44 = y:38 + └── upsert_k:40 != k:34 # On conflict do update with non-constant input. # TODO(rytaft): The default value for x is NULL, and we're not updating either @@ -489,32 +514,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: upsert_w:28 upsert_k:29 + │ ├── columns: upsert_k:34 upsert_v:35 upsert_w:36 upsert_x:37 upsert_y:38 │ ├── with-scan &1 - │ │ ├── columns: upsert_w:28 upsert_k:29 + │ │ ├── columns: upsert_k:34 upsert_v:35 upsert_w:36 upsert_x:37 upsert_y:38 │ │ └── mapping: - │ │ ├── upsert_w:25 => upsert_w:28 - │ │ └── upsert_k:23 => upsert_k:29 + │ │ ├── upsert_k:23 => upsert_k:34 + │ │ ├── upsert_v:24 => upsert_v:35 + │ │ ├── upsert_w:25 => upsert_w:36 + │ │ ├── upsert_x:26 => upsert_x:37 + │ │ └── upsert_y:27 => upsert_y:38 │ ├── scan uniq - │ │ └── columns: uniq.k:30!null uniq.w:32 + │ │ └── columns: uniq.k:28!null uniq.v:29 uniq.w:30 uniq.x:31 uniq.y:32 │ └── filters - │ ├── upsert_w:28 = uniq.w:32 - │ └── upsert_k:29 != uniq.k:30 + │ ├── upsert_w:36 = uniq.w:30 + │ └── upsert_k:34 != uniq.k:28 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:36 upsert_y:37 upsert_k:38 + ├── columns: upsert_k:45 upsert_v:46 upsert_w:47 upsert_x:48 upsert_y:49 ├── with-scan &1 - │ ├── columns: upsert_x:36 upsert_y:37 upsert_k:38 + │ ├── columns: upsert_k:45 upsert_v:46 upsert_w:47 upsert_x:48 upsert_y:49 │ └── mapping: - │ ├── upsert_x:26 => upsert_x:36 - │ ├── upsert_y:27 => upsert_y:37 - │ └── upsert_k:23 => upsert_k:38 + │ ├── upsert_k:23 => upsert_k:45 + │ ├── upsert_v:24 => upsert_v:46 + │ ├── upsert_w:25 => upsert_w:47 + │ ├── upsert_x:26 => upsert_x:48 + │ └── upsert_y:27 => upsert_y:49 ├── scan uniq - │ └── columns: uniq.k:39!null uniq.x:42 uniq.y:43 + │ └── columns: uniq.k:39!null uniq.v:40 uniq.w:41 uniq.x:42 uniq.y:43 └── filters - ├── upsert_x:36 = uniq.x:42 - ├── upsert_y:37 = uniq.y:43 - └── upsert_k:38 != uniq.k:39 + ├── upsert_x:48 = uniq.x:42 + ├── upsert_y:49 = uniq.y:43 + └── upsert_k:45 != uniq.k:39 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # column. @@ -577,32 +607,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: upsert_w:24!null upsert_k:25 + │ ├── columns: upsert_k:30 upsert_v:31 upsert_w:32!null upsert_x:33 upsert_y:34 │ ├── with-scan &1 - │ │ ├── columns: upsert_w:24!null upsert_k:25 + │ │ ├── columns: upsert_k:30 upsert_v:31 upsert_w:32!null upsert_x:33 upsert_y:34 │ │ └── mapping: - │ │ ├── upsert_w:21 => upsert_w:24 - │ │ └── upsert_k:19 => upsert_k:25 + │ │ ├── upsert_k:19 => upsert_k:30 + │ │ ├── upsert_v:20 => upsert_v:31 + │ │ ├── upsert_w:21 => upsert_w:32 + │ │ ├── upsert_x:22 => upsert_x:33 + │ │ └── upsert_y:23 => upsert_y:34 │ ├── scan uniq - │ │ └── columns: k:26!null w:28 + │ │ └── columns: k:24!null v:25 w:26 x:27 y:28 │ └── filters - │ ├── upsert_w:24 = w:28 - │ └── upsert_k:25 != k:26 + │ ├── upsert_w:32 = w:26 + │ └── upsert_k:30 != k:24 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:32 upsert_y:33 upsert_k:34 + ├── columns: upsert_k:41 upsert_v:42 upsert_w:43!null upsert_x:44 upsert_y:45 ├── with-scan &1 - │ ├── columns: upsert_x:32 upsert_y:33 upsert_k:34 + │ ├── columns: upsert_k:41 upsert_v:42 upsert_w:43!null upsert_x:44 upsert_y:45 │ └── mapping: - │ ├── upsert_x:22 => upsert_x:32 - │ ├── upsert_y:23 => upsert_y:33 - │ └── upsert_k:19 => upsert_k:34 + │ ├── upsert_k:19 => upsert_k:41 + │ ├── upsert_v:20 => upsert_v:42 + │ ├── upsert_w:21 => upsert_w:43 + │ ├── upsert_x:22 => upsert_x:44 + │ └── upsert_y:23 => upsert_y:45 ├── scan uniq - │ └── columns: k:35!null x:38 y:39 + │ └── columns: k:35!null v:36 w:37 x:38 y:39 └── filters - ├── upsert_x:32 = x:38 - ├── upsert_y:33 = y:39 - └── upsert_k:34 != k:35 + ├── upsert_x:44 = x:38 + ├── upsert_y:45 = y:39 + └── upsert_k:41 != k:35 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # columns. @@ -658,32 +693,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: upsert_w:24 upsert_k:25 + │ ├── columns: upsert_k:30 upsert_v:31!null upsert_w:32 upsert_x:33 upsert_y:34 │ ├── with-scan &1 - │ │ ├── columns: upsert_w:24 upsert_k:25 + │ │ ├── columns: upsert_k:30 upsert_v:31!null upsert_w:32 upsert_x:33 upsert_y:34 │ │ └── mapping: - │ │ ├── upsert_w:21 => upsert_w:24 - │ │ └── upsert_k:19 => upsert_k:25 + │ │ ├── upsert_k:19 => upsert_k:30 + │ │ ├── upsert_v:20 => upsert_v:31 + │ │ ├── upsert_w:21 => upsert_w:32 + │ │ ├── upsert_x:22 => upsert_x:33 + │ │ └── upsert_y:23 => upsert_y:34 │ ├── scan uniq - │ │ └── columns: k:26!null w:28 + │ │ └── columns: k:24!null v:25 w:26 x:27 y:28 │ └── filters - │ ├── upsert_w:24 = w:28 - │ └── upsert_k:25 != k:26 + │ ├── upsert_w:32 = w:26 + │ └── upsert_k:30 != k:24 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:32 upsert_y:33 upsert_k:34 + ├── columns: upsert_k:41 upsert_v:42!null upsert_w:43 upsert_x:44 upsert_y:45 ├── with-scan &1 - │ ├── columns: upsert_x:32 upsert_y:33 upsert_k:34 + │ ├── columns: upsert_k:41 upsert_v:42!null upsert_w:43 upsert_x:44 upsert_y:45 │ └── mapping: - │ ├── upsert_x:22 => upsert_x:32 - │ ├── upsert_y:23 => upsert_y:33 - │ └── upsert_k:19 => upsert_k:34 + │ ├── upsert_k:19 => upsert_k:41 + │ ├── upsert_v:20 => upsert_v:42 + │ ├── upsert_w:21 => upsert_w:43 + │ ├── upsert_x:22 => upsert_x:44 + │ └── upsert_y:23 => upsert_y:45 ├── scan uniq - │ └── columns: k:35!null x:38 y:39 + │ └── columns: k:35!null v:36 w:37 x:38 y:39 └── filters - ├── upsert_x:32 = x:38 - ├── upsert_y:33 = y:39 - └── upsert_k:34 != k:35 + ├── upsert_x:44 = x:38 + ├── upsert_y:45 = y:39 + └── upsert_k:41 != k:35 # Cannot conflict on a subset of columns in a unique constraint. build @@ -732,48 +772,51 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: column2:10!null column3:11!null column1:12!null + │ ├── columns: column1:15!null column2:16!null column3:17!null column4:18!null │ ├── with-scan &1 - │ │ ├── columns: column2:10!null column3:11!null column1:12!null + │ │ ├── columns: column1:15!null column2:16!null column3:17!null column4:18!null │ │ └── mapping: - │ │ ├── column2:7 => column2:10 - │ │ ├── column3:8 => column3:11 - │ │ └── column1:6 => column1:12 + │ │ ├── column1:6 => column1:15 + │ │ ├── column2:7 => column2:16 + │ │ ├── column3:8 => column3:17 + │ │ └── column4:9 => column4:18 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:13!null b:14!null c:15 + │ │ └── columns: a:10!null b:11!null c:12 d:13 │ └── filters - │ ├── column2:10 = b:14 - │ ├── column3:11 = c:15 - │ └── column1:12 != a:13 + │ ├── column2:16 = b:11 + │ ├── column3:17 = c:12 + │ └── column1:15 != a:10 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: column1:18!null column2:19!null + │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null │ ├── with-scan &1 - │ │ ├── columns: column1:18!null column2:19!null + │ │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null │ │ └── mapping: - │ │ ├── column1:6 => column1:18 - │ │ └── column2:7 => column2:19 + │ │ ├── column1:6 => column1:24 + │ │ ├── column2:7 => column2:25 + │ │ ├── column3:8 => column3:26 + │ │ └── column4:9 => column4:27 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:20!null b:21!null + │ │ └── columns: a:19!null b:20!null c:21 d:22 │ └── filters - │ ├── column1:18 = a:20 - │ └── column2:19 != b:21 + │ ├── column1:24 = a:19 + │ └── column2:25 != b:20 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: column3:25!null column4:26!null column1:27!null column2:28!null + ├── columns: column1:33!null column2:34!null column3:35!null column4:36!null ├── with-scan &1 - │ ├── columns: column3:25!null column4:26!null column1:27!null column2:28!null + │ ├── columns: column1:33!null column2:34!null column3:35!null column4:36!null │ └── mapping: - │ ├── column3:8 => column3:25 - │ ├── column4:9 => column4:26 - │ ├── column1:6 => column1:27 - │ └── column2:7 => column2:28 + │ ├── column1:6 => column1:33 + │ ├── column2:7 => column2:34 + │ ├── column3:8 => column3:35 + │ └── column4:9 => column4:36 ├── scan uniq_overlaps_pk - │ └── columns: a:29!null b:30!null c:31 d:32 + │ └── columns: a:28!null b:29!null c:30 d:31 └── filters - ├── column3:25 = c:31 - ├── column4:26 = d:32 - └── (column1:27 != a:29) OR (column2:28 != b:30) + ├── column3:35 = c:30 + ├── column4:36 = d:31 + └── (column1:33 != a:28) OR (column2:34 != b:29) # Upsert with non-constant input. # Add inequality filters for the primary key columns that are not part of each @@ -801,32 +844,35 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: v:14 x:15 k:16 + │ ├── columns: k:19 v:20 x:21 column13:22 │ ├── with-scan &1 - │ │ ├── columns: v:14 x:15 k:16 + │ │ ├── columns: k:19 v:20 x:21 column13:22 │ │ └── mapping: - │ │ ├── other.v:7 => v:14 - │ │ ├── other.x:9 => x:15 - │ │ └── other.k:6 => k:16 + │ │ ├── other.k:6 => k:19 + │ │ ├── other.v:7 => v:20 + │ │ ├── other.x:9 => x:21 + │ │ └── column13:13 => column13:22 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:17!null b:18!null c:19 + │ │ └── columns: a:14!null b:15!null c:16 d:17 │ └── filters - │ ├── v:14 = b:18 - │ ├── x:15 = c:19 - │ └── k:16 != a:17 + │ ├── v:20 = b:15 + │ ├── x:21 = c:16 + │ └── k:19 != a:14 └── unique-checks-item: uniq_overlaps_pk(a) └── semi-join (hash) - ├── columns: k:22 v:23 + ├── columns: k:28 v:29 x:30 column13:31 ├── with-scan &1 - │ ├── columns: k:22 v:23 + │ ├── columns: k:28 v:29 x:30 column13:31 │ └── mapping: - │ ├── other.k:6 => k:22 - │ └── other.v:7 => v:23 + │ ├── other.k:6 => k:28 + │ ├── other.v:7 => v:29 + │ ├── other.x:9 => x:30 + │ └── column13:13 => column13:31 ├── scan uniq_overlaps_pk - │ └── columns: a:24!null b:25!null + │ └── columns: a:23!null b:24!null c:25 d:26 └── filters - ├── k:22 = a:24 - └── v:23 != b:25 + ├── k:28 = a:23 + └── v:29 != b:24 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # column. @@ -880,48 +926,51 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: upsert_b:20 upsert_c:21 upsert_a:22!null + │ ├── columns: upsert_a:25!null upsert_b:26 upsert_c:27 upsert_d:28 │ ├── with-scan &1 - │ │ ├── columns: upsert_b:20 upsert_c:21 upsert_a:22!null + │ │ ├── columns: upsert_a:25!null upsert_b:26 upsert_c:27 upsert_d:28 │ │ └── mapping: - │ │ ├── upsert_b:17 => upsert_b:20 - │ │ ├── upsert_c:18 => upsert_c:21 - │ │ └── upsert_a:16 => upsert_a:22 + │ │ ├── upsert_a:16 => upsert_a:25 + │ │ ├── upsert_b:17 => upsert_b:26 + │ │ ├── upsert_c:18 => upsert_c:27 + │ │ └── upsert_d:19 => upsert_d:28 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:23!null b:24!null c:25 + │ │ └── columns: a:20!null b:21!null c:22 d:23 │ └── filters - │ ├── upsert_b:20 = b:24 - │ ├── upsert_c:21 = c:25 - │ └── upsert_a:22 != a:23 + │ ├── upsert_b:26 = b:21 + │ ├── upsert_c:27 = c:22 + │ └── upsert_a:25 != a:20 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: upsert_a:28!null upsert_b:29 + │ ├── columns: upsert_a:34!null upsert_b:35 upsert_c:36 upsert_d:37 │ ├── with-scan &1 - │ │ ├── columns: upsert_a:28!null upsert_b:29 + │ │ ├── columns: upsert_a:34!null upsert_b:35 upsert_c:36 upsert_d:37 │ │ └── mapping: - │ │ ├── upsert_a:16 => upsert_a:28 - │ │ └── upsert_b:17 => upsert_b:29 + │ │ ├── upsert_a:16 => upsert_a:34 + │ │ ├── upsert_b:17 => upsert_b:35 + │ │ ├── upsert_c:18 => upsert_c:36 + │ │ └── upsert_d:19 => upsert_d:37 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:30!null b:31!null + │ │ └── columns: a:29!null b:30!null c:31 d:32 │ └── filters - │ ├── upsert_a:28 = a:30 - │ └── upsert_b:29 != b:31 + │ ├── upsert_a:34 = a:29 + │ └── upsert_b:35 != b:30 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: upsert_c:35 upsert_d:36 upsert_a:37!null upsert_b:38 + ├── columns: upsert_a:43!null upsert_b:44 upsert_c:45 upsert_d:46 ├── with-scan &1 - │ ├── columns: upsert_c:35 upsert_d:36 upsert_a:37!null upsert_b:38 + │ ├── columns: upsert_a:43!null upsert_b:44 upsert_c:45 upsert_d:46 │ └── mapping: - │ ├── upsert_c:18 => upsert_c:35 - │ ├── upsert_d:19 => upsert_d:36 - │ ├── upsert_a:16 => upsert_a:37 - │ └── upsert_b:17 => upsert_b:38 + │ ├── upsert_a:16 => upsert_a:43 + │ ├── upsert_b:17 => upsert_b:44 + │ ├── upsert_c:18 => upsert_c:45 + │ └── upsert_d:19 => upsert_d:46 ├── scan uniq_overlaps_pk - │ └── columns: a:39!null b:40!null c:41 d:42 + │ └── columns: a:38!null b:39!null c:40 d:41 └── filters - ├── upsert_c:35 = c:41 - ├── upsert_d:36 = d:42 - └── (upsert_a:37 != a:39) OR (upsert_b:38 != b:40) + ├── upsert_c:45 = c:40 + ├── upsert_d:46 = d:41 + └── (upsert_a:43 != a:38) OR (upsert_b:44 != b:39) # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # columns. @@ -973,48 +1022,51 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: upsert_b:20!null upsert_c:21 upsert_a:22 + │ ├── columns: upsert_a:25 upsert_b:26!null upsert_c:27 upsert_d:28 │ ├── with-scan &1 - │ │ ├── columns: upsert_b:20!null upsert_c:21 upsert_a:22 + │ │ ├── columns: upsert_a:25 upsert_b:26!null upsert_c:27 upsert_d:28 │ │ └── mapping: - │ │ ├── upsert_b:17 => upsert_b:20 - │ │ ├── upsert_c:18 => upsert_c:21 - │ │ └── upsert_a:16 => upsert_a:22 + │ │ ├── upsert_a:16 => upsert_a:25 + │ │ ├── upsert_b:17 => upsert_b:26 + │ │ ├── upsert_c:18 => upsert_c:27 + │ │ └── upsert_d:19 => upsert_d:28 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:23!null b:24!null c:25 + │ │ └── columns: a:20!null b:21!null c:22 d:23 │ └── filters - │ ├── upsert_b:20 = b:24 - │ ├── upsert_c:21 = c:25 - │ └── upsert_a:22 != a:23 + │ ├── upsert_b:26 = b:21 + │ ├── upsert_c:27 = c:22 + │ └── upsert_a:25 != a:20 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: upsert_a:28 upsert_b:29!null + │ ├── columns: upsert_a:34 upsert_b:35!null upsert_c:36 upsert_d:37 │ ├── with-scan &1 - │ │ ├── columns: upsert_a:28 upsert_b:29!null + │ │ ├── columns: upsert_a:34 upsert_b:35!null upsert_c:36 upsert_d:37 │ │ └── mapping: - │ │ ├── upsert_a:16 => upsert_a:28 - │ │ └── upsert_b:17 => upsert_b:29 + │ │ ├── upsert_a:16 => upsert_a:34 + │ │ ├── upsert_b:17 => upsert_b:35 + │ │ ├── upsert_c:18 => upsert_c:36 + │ │ └── upsert_d:19 => upsert_d:37 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:30!null b:31!null + │ │ └── columns: a:29!null b:30!null c:31 d:32 │ └── filters - │ ├── upsert_a:28 = a:30 - │ └── upsert_b:29 != b:31 + │ ├── upsert_a:34 = a:29 + │ └── upsert_b:35 != b:30 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: upsert_c:35 upsert_d:36 upsert_a:37 upsert_b:38!null + ├── columns: upsert_a:43 upsert_b:44!null upsert_c:45 upsert_d:46 ├── with-scan &1 - │ ├── columns: upsert_c:35 upsert_d:36 upsert_a:37 upsert_b:38!null + │ ├── columns: upsert_a:43 upsert_b:44!null upsert_c:45 upsert_d:46 │ └── mapping: - │ ├── upsert_c:18 => upsert_c:35 - │ ├── upsert_d:19 => upsert_d:36 - │ ├── upsert_a:16 => upsert_a:37 - │ └── upsert_b:17 => upsert_b:38 + │ ├── upsert_a:16 => upsert_a:43 + │ ├── upsert_b:17 => upsert_b:44 + │ ├── upsert_c:18 => upsert_c:45 + │ └── upsert_d:19 => upsert_d:46 ├── scan uniq_overlaps_pk - │ └── columns: a:39!null b:40!null c:41 d:42 + │ └── columns: a:38!null b:39!null c:40 d:41 └── filters - ├── upsert_c:35 = c:41 - ├── upsert_d:36 = d:42 - └── (upsert_a:37 != a:39) OR (upsert_b:38 != b:40) + ├── upsert_c:45 = c:40 + ├── upsert_d:46 = d:41 + └── (upsert_a:43 != a:38) OR (upsert_b:44 != b:39) exec-ddl CREATE TABLE uniq_hidden_pk ( @@ -1084,49 +1136,55 @@ upsert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: column2:20!null upsert_c:21 upsert_rowid:22 + │ ├── columns: column1:26!null column2:27!null upsert_c:28 column3:29!null upsert_rowid:30 │ ├── with-scan &1 - │ │ ├── columns: column2:20!null upsert_c:21 upsert_rowid:22 + │ │ ├── columns: column1:26!null column2:27!null upsert_c:28 column3:29!null upsert_rowid:30 │ │ └── mapping: - │ │ ├── column2:8 => column2:20 - │ │ ├── upsert_c:18 => upsert_c:21 - │ │ └── upsert_rowid:19 => upsert_rowid:22 + │ │ ├── column1:7 => column1:26 + │ │ ├── column2:8 => column2:27 + │ │ ├── upsert_c:18 => upsert_c:28 + │ │ ├── column3:9 => column3:29 + │ │ └── upsert_rowid:19 => upsert_rowid:30 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:24 c:25 rowid:27!null + │ │ └── columns: a:20 b:21 c:22 d:23 rowid:24!null │ └── filters - │ ├── column2:20 = b:24 - │ ├── upsert_c:21 = c:25 - │ └── upsert_rowid:22 != rowid:27 + │ ├── column2:27 = b:21 + │ ├── upsert_c:28 = c:22 + │ └── upsert_rowid:30 != rowid:24 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: column1:29!null column2:30!null column3:31!null upsert_rowid:32 + │ ├── columns: column1:37!null column2:38!null upsert_c:39 column3:40!null upsert_rowid:41 │ ├── with-scan &1 - │ │ ├── columns: column1:29!null column2:30!null column3:31!null upsert_rowid:32 + │ │ ├── columns: column1:37!null column2:38!null upsert_c:39 column3:40!null upsert_rowid:41 │ │ └── mapping: - │ │ ├── column1:7 => column1:29 - │ │ ├── column2:8 => column2:30 - │ │ ├── column3:9 => column3:31 - │ │ └── upsert_rowid:19 => upsert_rowid:32 + │ │ ├── column1:7 => column1:37 + │ │ ├── column2:8 => column2:38 + │ │ ├── upsert_c:18 => upsert_c:39 + │ │ ├── column3:9 => column3:40 + │ │ └── upsert_rowid:19 => upsert_rowid:41 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:33 b:34 d:36 rowid:37!null + │ │ └── columns: a:31 b:32 c:33 d:34 rowid:35!null │ └── filters - │ ├── column1:29 = a:33 - │ ├── column2:30 = b:34 - │ ├── column3:31 = d:36 - │ └── upsert_rowid:32 != rowid:37 + │ ├── column1:37 = a:31 + │ ├── column2:38 = b:32 + │ ├── column3:40 = d:34 + │ └── upsert_rowid:41 != rowid:35 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: column1:39!null upsert_rowid:40 + ├── columns: column1:48!null column2:49!null upsert_c:50 column3:51!null upsert_rowid:52 ├── with-scan &1 - │ ├── columns: column1:39!null upsert_rowid:40 + │ ├── columns: column1:48!null column2:49!null upsert_c:50 column3:51!null upsert_rowid:52 │ └── mapping: - │ ├── column1:7 => column1:39 - │ └── upsert_rowid:19 => upsert_rowid:40 + │ ├── column1:7 => column1:48 + │ ├── column2:8 => column2:49 + │ ├── upsert_c:18 => upsert_c:50 + │ ├── column3:9 => column3:51 + │ └── upsert_rowid:19 => upsert_rowid:52 ├── scan uniq_hidden_pk - │ └── columns: a:41 rowid:45!null + │ └── columns: a:42 b:43 c:44 d:45 rowid:46!null └── filters - ├── column1:39 = a:41 - └── upsert_rowid:40 != rowid:45 + ├── column1:48 = a:42 + └── upsert_rowid:52 != rowid:46 # Upsert with non-constant input. # Add inequality filters for the hidden primary key column. @@ -1153,49 +1211,55 @@ upsert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: v:15 x:16 column14:17 + │ ├── columns: k:21 v:22 x:23 y:24 column14:25 │ ├── with-scan &1 - │ │ ├── columns: v:15 x:16 column14:17 + │ │ ├── columns: k:21 v:22 x:23 y:24 column14:25 │ │ └── mapping: - │ │ ├── other.v:8 => v:15 - │ │ ├── other.x:10 => x:16 - │ │ └── column14:14 => column14:17 + │ │ ├── other.k:7 => k:21 + │ │ ├── other.v:8 => v:22 + │ │ ├── other.x:10 => x:23 + │ │ ├── other.y:11 => y:24 + │ │ └── column14:14 => column14:25 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:19 c:20 uniq_hidden_pk.rowid:22!null + │ │ └── columns: a:15 b:16 c:17 d:18 uniq_hidden_pk.rowid:19!null │ └── filters - │ ├── v:15 = b:19 - │ ├── x:16 = c:20 - │ └── column14:17 != uniq_hidden_pk.rowid:22 + │ ├── v:22 = b:16 + │ ├── x:23 = c:17 + │ └── column14:25 != uniq_hidden_pk.rowid:19 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: k:24 v:25 y:26 column14:27 + │ ├── columns: k:32 v:33 x:34 y:35 column14:36 │ ├── with-scan &1 - │ │ ├── columns: k:24 v:25 y:26 column14:27 + │ │ ├── columns: k:32 v:33 x:34 y:35 column14:36 │ │ └── mapping: - │ │ ├── other.k:7 => k:24 - │ │ ├── other.v:8 => v:25 - │ │ ├── other.y:11 => y:26 - │ │ └── column14:14 => column14:27 + │ │ ├── other.k:7 => k:32 + │ │ ├── other.v:8 => v:33 + │ │ ├── other.x:10 => x:34 + │ │ ├── other.y:11 => y:35 + │ │ └── column14:14 => column14:36 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:28 b:29 d:31 uniq_hidden_pk.rowid:32!null + │ │ └── columns: a:26 b:27 c:28 d:29 uniq_hidden_pk.rowid:30!null │ └── filters - │ ├── k:24 = a:28 - │ ├── v:25 = b:29 - │ ├── y:26 = d:31 - │ └── column14:27 != uniq_hidden_pk.rowid:32 + │ ├── k:32 = a:26 + │ ├── v:33 = b:27 + │ ├── y:35 = d:29 + │ └── column14:36 != uniq_hidden_pk.rowid:30 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: k:34 column14:35 + ├── columns: k:43 v:44 x:45 y:46 column14:47 ├── with-scan &1 - │ ├── columns: k:34 column14:35 + │ ├── columns: k:43 v:44 x:45 y:46 column14:47 │ └── mapping: - │ ├── other.k:7 => k:34 - │ └── column14:14 => column14:35 + │ ├── other.k:7 => k:43 + │ ├── other.v:8 => v:44 + │ ├── other.x:10 => x:45 + │ ├── other.y:11 => y:46 + │ └── column14:14 => column14:47 ├── scan uniq_hidden_pk - │ └── columns: a:36 uniq_hidden_pk.rowid:40!null + │ └── columns: a:37 b:38 c:39 d:40 uniq_hidden_pk.rowid:41!null └── filters - ├── k:34 = a:36 - └── column14:35 != uniq_hidden_pk.rowid:40 + ├── k:43 = a:37 + └── column14:47 != uniq_hidden_pk.rowid:41 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # columns. @@ -1254,49 +1318,55 @@ upsert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: upsert_b:24 upsert_c:25 upsert_rowid:26 + │ ├── columns: upsert_a:30!null upsert_b:31 upsert_c:32 upsert_d:33 upsert_rowid:34 │ ├── with-scan &1 - │ │ ├── columns: upsert_b:24 upsert_c:25 upsert_rowid:26 + │ │ ├── columns: upsert_a:30!null upsert_b:31 upsert_c:32 upsert_d:33 upsert_rowid:34 │ │ └── mapping: - │ │ ├── upsert_b:20 => upsert_b:24 - │ │ ├── upsert_c:21 => upsert_c:25 - │ │ └── upsert_rowid:23 => upsert_rowid:26 + │ │ ├── upsert_a:19 => upsert_a:30 + │ │ ├── upsert_b:20 => upsert_b:31 + │ │ ├── upsert_c:21 => upsert_c:32 + │ │ ├── upsert_d:22 => upsert_d:33 + │ │ └── upsert_rowid:23 => upsert_rowid:34 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:28 c:29 rowid:31!null + │ │ └── columns: a:24 b:25 c:26 d:27 rowid:28!null │ └── filters - │ ├── upsert_b:24 = b:28 - │ ├── upsert_c:25 = c:29 - │ └── upsert_rowid:26 != rowid:31 + │ ├── upsert_b:31 = b:25 + │ ├── upsert_c:32 = c:26 + │ └── upsert_rowid:34 != rowid:28 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: upsert_a:33!null upsert_b:34 upsert_d:35 upsert_rowid:36 + │ ├── columns: upsert_a:41!null upsert_b:42 upsert_c:43 upsert_d:44 upsert_rowid:45 │ ├── with-scan &1 - │ │ ├── columns: upsert_a:33!null upsert_b:34 upsert_d:35 upsert_rowid:36 + │ │ ├── columns: upsert_a:41!null upsert_b:42 upsert_c:43 upsert_d:44 upsert_rowid:45 │ │ └── mapping: - │ │ ├── upsert_a:19 => upsert_a:33 - │ │ ├── upsert_b:20 => upsert_b:34 - │ │ ├── upsert_d:22 => upsert_d:35 - │ │ └── upsert_rowid:23 => upsert_rowid:36 + │ │ ├── upsert_a:19 => upsert_a:41 + │ │ ├── upsert_b:20 => upsert_b:42 + │ │ ├── upsert_c:21 => upsert_c:43 + │ │ ├── upsert_d:22 => upsert_d:44 + │ │ └── upsert_rowid:23 => upsert_rowid:45 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:37 b:38 d:40 rowid:41!null + │ │ └── columns: a:35 b:36 c:37 d:38 rowid:39!null │ └── filters - │ ├── upsert_a:33 = a:37 - │ ├── upsert_b:34 = b:38 - │ ├── upsert_d:35 = d:40 - │ └── upsert_rowid:36 != rowid:41 + │ ├── upsert_a:41 = a:35 + │ ├── upsert_b:42 = b:36 + │ ├── upsert_d:44 = d:38 + │ └── upsert_rowid:45 != rowid:39 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: upsert_a:43!null upsert_rowid:44 + ├── columns: upsert_a:52!null upsert_b:53 upsert_c:54 upsert_d:55 upsert_rowid:56 ├── with-scan &1 - │ ├── columns: upsert_a:43!null upsert_rowid:44 + │ ├── columns: upsert_a:52!null upsert_b:53 upsert_c:54 upsert_d:55 upsert_rowid:56 │ └── mapping: - │ ├── upsert_a:19 => upsert_a:43 - │ └── upsert_rowid:23 => upsert_rowid:44 + │ ├── upsert_a:19 => upsert_a:52 + │ ├── upsert_b:20 => upsert_b:53 + │ ├── upsert_c:21 => upsert_c:54 + │ ├── upsert_d:22 => upsert_d:55 + │ └── upsert_rowid:23 => upsert_rowid:56 ├── scan uniq_hidden_pk - │ └── columns: a:45 rowid:49!null + │ └── columns: a:46 b:47 c:48 d:49 rowid:50!null └── filters - ├── upsert_a:43 = a:45 - └── upsert_rowid:44 != rowid:49 + ├── upsert_a:52 = a:46 + └── upsert_rowid:56 != rowid:50 exec-ddl CREATE TABLE uniq_fk_parent ( @@ -1358,17 +1428,17 @@ upsert uniq_fk_parent ├── unique-checks │ └── unique-checks-item: uniq_fk_parent(a) │ └── semi-join (hash) - │ ├── columns: column1:10!null upsert_rowid:11 + │ ├── columns: column1:13!null upsert_rowid:14 │ ├── with-scan &1 - │ │ ├── columns: column1:10!null upsert_rowid:11 + │ │ ├── columns: column1:13!null upsert_rowid:14 │ │ └── mapping: - │ │ ├── column1:4 => column1:10 - │ │ └── upsert_rowid:9 => upsert_rowid:11 + │ │ ├── column1:4 => column1:13 + │ │ └── upsert_rowid:9 => upsert_rowid:14 │ ├── scan uniq_fk_parent - │ │ └── columns: uniq_fk_parent.a:12 rowid:13!null + │ │ └── columns: uniq_fk_parent.a:10 rowid:11!null │ └── filters - │ ├── column1:10 = uniq_fk_parent.a:12 - │ └── upsert_rowid:11 != rowid:13 + │ ├── column1:13 = uniq_fk_parent.a:10 + │ └── upsert_rowid:14 != rowid:11 └── f-k-checks └── f-k-checks-item: uniq_fk_child(a) -> uniq_fk_parent(a) └── semi-join (hash) @@ -1481,17 +1551,17 @@ upsert t └── unique-checks └── unique-checks-item: t(i) └── semi-join (hash) - ├── columns: upsert_i:14!null upsert_rowid:15 + ├── columns: upsert_i:17!null upsert_rowid:18 ├── with-scan &1 - │ ├── columns: upsert_i:14!null upsert_rowid:15 + │ ├── columns: upsert_i:17!null upsert_rowid:18 │ └── mapping: - │ ├── upsert_i:10 => upsert_i:14 - │ └── upsert_rowid:11 => upsert_rowid:15 + │ ├── upsert_i:10 => upsert_i:17 + │ └── upsert_rowid:11 => upsert_rowid:18 ├── scan t - │ ├── columns: i:16 rowid:17!null + │ ├── columns: i:14 rowid:15!null │ └── partial index predicates │ └── i1: filters - │ └── i:16 > 0 + │ └── i:14 > 0 └── filters - ├── upsert_i:14 = i:16 - └── upsert_rowid:15 != rowid:17 + ├── upsert_i:17 = i:14 + └── upsert_rowid:18 != rowid:15