diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index b346275e875c..743c1c567bf3 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -2665,3 +2665,13 @@ WHERE AND attname = 'x' ---- 'howdy'::STRING + +# Regression test for limits on virtual index scans. (#53522) + +let $testid +SELECT 't47285'::REGCLASS::OID + +query T +SELECT attname FROM pg_attribute WHERE attrelid = $testid LIMIT 1 +---- +x diff --git a/pkg/sql/opt/exec/execbuilder/testdata/catalog b/pkg/sql/opt/exec/execbuilder/testdata/catalog index e1c1fe411548..2c26cd7a0398 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/catalog +++ b/pkg/sql/opt/exec/execbuilder/testdata/catalog @@ -272,5 +272,5 @@ scan abx query T EXPLAIN (OPT) select * from pg_type where oid = 1000 ---- - scan pg_type@pg_type_oid_idx - └── constraint: /2: [/1000 - /1000] +scan pg_type@pg_type_oid_idx + └── constraint: /2/1: [/1000 - /1000] diff --git a/pkg/sql/opt/exec/execbuilder/testdata/explain b/pkg/sql/opt/exec/execbuilder/testdata/explain index d40ed04e8009..5eddf636dc62 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/explain +++ b/pkg/sql/opt/exec/execbuilder/testdata/explain @@ -179,65 +179,63 @@ sort · · query TTT SELECT * FROM [EXPLAIN SHOW TABLES] WHERE field != 'size' ---- -· distribution local -· vectorized false -sort · · - │ order +nspname,+relname - └── render · · - └── hash join (right outer) · · - │ equality (table_id) = (column44) - ├── virtual table · · - │ table table_row_statistics@primary - └── render · · - └── hash join · · - │ equality (oid) = (relnamespace) - ├── filter · · - │ │ filter nspname NOT IN ('crdb_internal', 'information_schema', 'pg_catalog', 'pg_extension') - │ └── virtual table · · - │ table pg_namespace@primary - └── hash join (left outer) · · - │ equality (oid) = (objoid) - │ left cols are key · - ├── filter · · - │ │ filter relkind IN ('S', 'r', 'v') - │ └── virtual table · · - │ table pg_class@primary - └── filter · · - │ filter objsubid = 0 - └── virtual table · · -· table pg_description@primary +· distribution local +· vectorized false +sort · · + │ order +nspname,+relname + └── render · · + └── hash join (right outer) · · + │ equality (table_id) = (column44) + ├── virtual table · · + │ table table_row_statistics@primary + └── render · · + └── hash join · · + │ equality (oid) = (relnamespace) + ├── filter · · + │ │ filter nspname NOT IN ('crdb_internal', 'information_schema', 'pg_catalog', 'pg_extension') + │ └── virtual table · · + │ table pg_namespace@primary + └── hash join (left outer) · · + │ equality (oid) = (objoid) + ├── filter · · + │ │ filter relkind IN ('S', 'r', 'v') + │ └── virtual table · · + │ table pg_class@primary + └── filter · · + │ filter objsubid = 0 + └── virtual table · · +· table pg_description@primary query TTT SELECT * FROM [EXPLAIN SHOW TABLES WITH COMMENT] WHERE field != 'size' ---- -· distribution local -· vectorized false -sort · · - │ order +nspname,+relname - └── render · · - └── hash join (right outer) · · - │ equality (table_id) = (column44) - ├── virtual table · · - │ table table_row_statistics@primary - └── render · · - └── hash join · · - │ equality (oid) = (relnamespace) - ├── filter · · - │ │ filter nspname NOT IN ('crdb_internal', 'information_schema', 'pg_catalog', 'pg_extension') - │ └── virtual table · · - │ table pg_namespace@primary - └── hash join (left outer) · · - │ equality (oid) = (objoid) - │ left cols are key · - ├── filter · · - │ │ filter relkind IN ('S', 'r', 'v') - │ └── virtual table · · - │ table pg_class@primary - └── filter · · - │ filter objsubid = 0 - └── virtual table · · -· table pg_description@primary +· distribution local +· vectorized false +sort · · + │ order +nspname,+relname + └── render · · + └── hash join (right outer) · · + │ equality (table_id) = (column44) + ├── virtual table · · + │ table table_row_statistics@primary + └── render · · + └── hash join · · + │ equality (oid) = (relnamespace) + ├── filter · · + │ │ filter nspname NOT IN ('crdb_internal', 'information_schema', 'pg_catalog', 'pg_extension') + │ └── virtual table · · + │ table pg_namespace@primary + └── hash join (left outer) · · + │ equality (oid) = (objoid) + ├── filter · · + │ │ filter relkind IN ('S', 'r', 'v') + │ └── virtual table · · + │ table pg_class@primary + └── filter · · + │ filter objsubid = 0 + └── virtual table · · +· table pg_description@primary query TTT SELECT * FROM [EXPLAIN SHOW DATABASE] WHERE field != 'size' @@ -347,25 +345,24 @@ render · · query TTT EXPLAIN SHOW CONSTRAINTS FROM foo ---- -· distribution local -· vectorized false -sort · · - │ order +conname - └── render · · - └── hash join · · - │ equality (oid) = (relnamespace) - ├── filter · · - │ │ filter nspname = 'public' - │ └── virtual table · · - │ table pg_namespace@primary - └── virtual table lookup join · · - │ table pg_constraint@pg_constraint_conrelid_idx - │ equality (oid) = (conrelid) - │ equality cols are key · - └── filter · · - │ filter relname = 'foo' - └── virtual table · · -· table pg_class@primary +· distribution local +· vectorized false +sort · · + │ order +conname + └── render · · + └── hash join · · + │ equality (relnamespace) = (oid) + ├── virtual table lookup join · · + │ │ table pg_constraint@pg_constraint_conrelid_idx + │ │ equality (oid) = (conrelid) + │ └── filter · · + │ │ filter relname = 'foo' + │ └── virtual table · · + │ table pg_class@primary + └── filter · · + │ filter nspname = 'public' + └── virtual table · · +· table pg_namespace@primary query TTT EXPLAIN SHOW USERS diff --git a/pkg/sql/opt/exec/execbuilder/testdata/join b/pkg/sql/opt/exec/execbuilder/testdata/join index 51e61d652efb..d482ea3a2dd0 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/join +++ b/pkg/sql/opt/exec/execbuilder/testdata/join @@ -325,92 +325,92 @@ SELECT level, node_type, field, description FROM [EXPLAIN (VERBOSE) SELECT pos.n ] WHERE node_type <> 'values' AND field <> 'size' ---- -0 · distribution local -0 · vectorized false -0 project · · -1 sort · · -1 · estimated row count 106 (missing stats) -1 · order +nspname,+relname,+conname,+generate_series -2 render · · -2 · estimated row count 106 (missing stats) -2 · render 0 CAST(NULL AS STRING) -2 · render 1 CASE confupdtype WHEN 'c' THEN 0 WHEN 'n' THEN 2 WHEN 'd' THEN 4 WHEN 'r' THEN 1 WHEN 'a' THEN 3 ELSE CAST(NULL AS INT8) END -2 · render 2 CASE confdeltype WHEN 'c' THEN 0 WHEN 'n' THEN 2 WHEN 'd' THEN 4 WHEN 'r' THEN 1 WHEN 'a' THEN 3 ELSE CAST(NULL AS INT8) END -2 · render 3 CASE WHEN condeferrable AND condeferred THEN 5 WHEN condeferrable THEN 6 ELSE 7 END -2 · render 4 nspname -2 · render 5 relname -2 · render 6 attname -2 · render 7 nspname -2 · render 8 relname -2 · render 9 attname -2 · render 10 conname -2 · render 11 generate_series -2 · render 12 relname -3 hash join (inner) · · -3 · estimated row count 106 (missing stats) -3 · equality (oid) = (objid) -4 hash join (inner) · · -4 · estimated row count 109 (missing stats) -4 · equality (oid) = (relnamespace) -5 virtual table · · -5 · estimated row count 1000 (missing stats) -5 · table pg_namespace@primary -5 hash join (inner) · · -5 · estimated row count 11 (missing stats) -5 · equality (relnamespace) = (oid) -6 cross join (inner) · · -6 · estimated row count 11 (missing stats) -6 · pred (attnum = confkey[generate_series]) AND (attnum = conkey[generate_series]) -7 project set · · -7 · estimated row count 10 -7 · render 0 generate_series(1, 32) -8 emptyrow · · -7 virtual table lookup join (inner) · · -7 · estimated row count 10 (missing stats) -7 · table pg_attribute@pg_attribute_attrelid_idx -7 · equality (oid) = (attrelid) -7 · equality cols are key · -8 virtual table lookup join (inner) · · -8 · estimated row count 10 (missing stats) -8 · table pg_class@pg_class_oid_idx -8 · equality (attrelid) = (oid) -8 · equality cols are key · -9 virtual table lookup join (inner) · · -9 · estimated row count 10 (missing stats) -9 · table pg_attribute@pg_attribute_attrelid_idx -9 · equality (confrelid) = (attrelid) -9 · equality cols are key · -10 virtual table lookup join (inner) · · -10 · estimated row count 10 (missing stats) -10 · table pg_constraint@pg_constraint_conrelid_idx -10 · equality (oid) = (conrelid) -10 · equality cols are key · -10 · pred contype = 'f' -11 filter · · -11 · estimated row count 10 (missing stats) -11 · filter relname = 'orders' -12 virtual table · · -12 · estimated row count 1000 (missing stats) -12 · table pg_class@primary -6 filter · · -6 · estimated row count 10 (missing stats) -6 · filter nspname = 'public' -7 virtual table · · -7 · estimated row count 1000 (missing stats) -7 · table pg_namespace@primary -4 hash join (inner) · · -4 · estimated row count 99 (missing stats) -4 · equality (refobjid) = (oid) -4 · right cols are key · -5 virtual table · · -5 · estimated row count 1000 (missing stats) -5 · table pg_depend@primary -5 filter · · -5 · estimated row count 10 (missing stats) -5 · filter relkind = 'i' -6 virtual table · · -6 · estimated row count 1000 (missing stats) -6 · table pg_class@primary +0 · distribution local +0 · vectorized false +0 project · · +1 sort · · +1 · estimated row count 110908 (missing stats) +1 · order +nspname,+relname,+conname,+generate_series +2 render · · +2 · estimated row count 110908 (missing stats) +2 · render 0 CAST(NULL AS STRING) +2 · render 1 CASE confupdtype WHEN 'c' THEN 0 WHEN 'n' THEN 2 WHEN 'd' THEN 4 WHEN 'r' THEN 1 WHEN 'a' THEN 3 ELSE CAST(NULL AS INT8) END +2 · render 2 CASE confdeltype WHEN 'c' THEN 0 WHEN 'n' THEN 2 WHEN 'd' THEN 4 WHEN 'r' THEN 1 WHEN 'a' THEN 3 ELSE CAST(NULL AS INT8) END +2 · render 3 CASE WHEN condeferrable AND condeferred THEN 5 WHEN condeferrable THEN 6 ELSE 7 END +2 · render 4 nspname +2 · render 5 relname +2 · render 6 attname +2 · render 7 nspname +2 · render 8 relname +2 · render 9 attname +2 · render 10 conname +2 · render 11 generate_series +2 · render 12 relname +3 hash join (inner) · · +3 · estimated row count 110908 (missing stats) +3 · equality (oid) = (objid) +4 hash join (inner) · · +4 · estimated row count 114302 (missing stats) +4 · equality (relnamespace) = (oid) +5 hash join (inner) · · +5 · estimated row count 11557 (missing stats) +5 · equality (attrelid) = (oid) +5 · pred attnum = confkey[generate_series] +6 hash join (inner) · · +6 · estimated row count 3502 (missing stats) +6 · equality (attrelid) = (confrelid) +7 virtual table · · +7 · estimated row count 1000 (missing stats) +7 · table pg_attribute@primary +7 cross join (inner) · · +7 · estimated row count 354 (missing stats) +7 · pred attnum = conkey[generate_series] +8 hash join (inner) · · +8 · estimated row count 107 (missing stats) +8 · equality (relnamespace) = (oid) +9 virtual table lookup join (inner) · · +9 · estimated row count 105 (missing stats) +9 · table pg_attribute@pg_attribute_attrelid_idx +9 · equality (oid) = (attrelid) +10 virtual table lookup join (inner) · · +10 · estimated row count 10 (missing stats) +10 · table pg_constraint@pg_constraint_conrelid_idx +10 · equality (oid) = (conrelid) +10 · pred contype = 'f' +11 filter · · +11 · estimated row count 10 (missing stats) +11 · filter relname = 'orders' +12 virtual table · · +12 · estimated row count 1000 (missing stats) +12 · table pg_class@primary +9 filter · · +9 · estimated row count 10 (missing stats) +9 · filter nspname = 'public' +10 virtual table · · +10 · estimated row count 1000 (missing stats) +10 · table pg_namespace@primary +8 project set · · +8 · estimated row count 10 +8 · render 0 generate_series(1, 32) +9 emptyrow · · +6 virtual table · · +6 · estimated row count 1000 (missing stats) +6 · table pg_class@primary +5 virtual table · · +5 · estimated row count 1000 (missing stats) +5 · table pg_namespace@primary +4 hash join (inner) · · +4 · estimated row count 99 (missing stats) +4 · equality (refobjid) = (oid) +5 virtual table · · +5 · estimated row count 1000 (missing stats) +5 · table pg_depend@primary +5 filter · · +5 · estimated row count 10 (missing stats) +5 · filter relkind = 'i' +6 virtual table · · +6 · estimated row count 1000 (missing stats) +6 · table pg_class@primary # Ensure that left joins on non-null foreign keys turn into inner joins statement ok diff --git a/pkg/sql/opt/exec/execbuilder/testdata/scalar b/pkg/sql/opt/exec/execbuilder/testdata/scalar index a5bb0ea99df8..4dacf0af7516 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/scalar +++ b/pkg/sql/opt/exec/execbuilder/testdata/scalar @@ -819,7 +819,7 @@ EXPLAIN (VERBOSE) SELECT * FROM pg_attribute WHERE attrelid='t'::regclass · distribution local · · · vectorized false · · virtual table · · (attrelid, attname, atttypid, attstattarget, attlen, attnum, attndims, attcacheoff, atttypmod, attbyval, attstorage, attalign, attnotnull, atthasdef, attidentity, attgenerated, attisdropped, attislocal, attinhcount, attcollation, attacl, attoptions, attfdwoptions) · -· estimated row count 1 (missing stats) · · +· estimated row count 10 (missing stats) · · · table pg_attribute@pg_attribute_attrelid_idx · · · spans /53-/54 · · diff --git a/pkg/sql/opt/exec/execbuilder/testdata/virtual b/pkg/sql/opt/exec/execbuilder/testdata/virtual index fb6caa4f7661..c6dcdb3274f6 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/virtual +++ b/pkg/sql/opt/exec/execbuilder/testdata/virtual @@ -63,26 +63,24 @@ filter · · query TTT EXPLAIN SELECT * FROM pg_constraint INNER LOOKUP JOIN pg_class on conrelid=pg_class.oid ---- -· distribution local -· vectorized false -virtual table lookup join · · - │ table pg_class@pg_class_oid_idx - │ equality (conrelid) = (oid) - │ equality cols are key · - └── virtual table · · -· table pg_constraint@primary +· distribution local +· vectorized false +virtual table lookup join · · + │ table pg_class@pg_class_oid_idx + │ equality (conrelid) = (oid) + └── virtual table · · +· table pg_constraint@primary query TTT EXPLAIN SELECT * FROM pg_constraint LEFT LOOKUP JOIN pg_class on conrelid=pg_class.oid ---- -· distribution local -· vectorized false -virtual table lookup join (left outer) · · - │ table pg_class@pg_class_oid_idx - │ equality (conrelid) = (oid) - │ equality cols are key · - └── virtual table · · -· table pg_constraint@primary +· distribution local +· vectorized false +virtual table lookup join (left outer) · · + │ table pg_class@pg_class_oid_idx + │ equality (conrelid) = (oid) + └── virtual table · · +· table pg_constraint@primary # Can't lookup into a vtable with no index. query error could not produce @@ -114,39 +112,48 @@ WHERE ORDER BY c.conname ---- -· distribution local -· vectorized false -sort · · - │ order +conname - └── render · · - └── hash join · · - │ equality (oid) = (connamespace) - ├── filter · · - │ │ filter nspname IN ('public',) - │ └── virtual table · · - │ table pg_namespace@primary - └── virtual table lookup join · · - │ table pg_attribute@pg_attribute_attrelid_idx - │ equality (oid) = (attrelid) - │ equality cols are key · - │ pred column135 = attnum - └── render · · - └── virtual table lookup join · · - │ table pg_attribute@pg_attribute_attrelid_idx - │ equality (oid) = (attrelid) - │ equality cols are key · - │ pred (column110 = attnum) AND (attrelid = 'b'::REGCLASS) - └── render · · - └── virtual table lookup join · · - │ table pg_class@pg_class_oid_idx - │ equality (conrelid) = (oid) - │ equality cols are key · - │ pred oid = 'b'::REGCLASS - └── virtual table lookup join · · - │ table pg_class@pg_class_oid_idx - │ equality (confrelid) = (oid) - │ equality cols are key · - └── filter · · - │ filter (conrelid = 'b'::REGCLASS) AND (contype = 'f') - └── virtual table · · -· table pg_constraint@primary +· distribution local +· vectorized false +sort · · + │ order +conname + └── render · · + └── hash join · · + │ equality (oid) = (connamespace) + ├── filter · · + │ │ filter nspname IN ('public',) + │ └── virtual table · · + │ table pg_namespace@primary + └── virtual table lookup join · · + │ table pg_attribute@pg_attribute_attrelid_idx + │ equality (oid) = (attrelid) + │ pred column135 = attnum + └── render · · + └── hash join · · + │ equality (attrelid, attnum) = (oid, column110) + ├── filter · · + │ │ filter attrelid = 'b'::REGCLASS + │ └── virtual table · · + │ table pg_attribute@primary + └── render · · + └── virtual table lookup join · · + │ table pg_class@pg_class_oid_idx + │ equality (confrelid) = (oid) + └── virtual table lookup join · · + │ table pg_class@pg_class_oid_idx + │ equality (conrelid) = (oid) + │ pred oid = 'b'::REGCLASS + └── filter · · + │ filter (conrelid = 'b'::REGCLASS) AND (contype = 'f') + └── virtual table · · +· table pg_constraint@primary + +# Test that limits are respected. +query TTT +EXPLAIN SELECT * FROM pg_catalog.pg_class WHERE oid = 50 LIMIT 1 +---- +· distribution local +· vectorized false +virtual table · · +· table pg_class@pg_class_oid_idx +· spans [/50 - /50] +· limit 1 diff --git a/pkg/sql/opt_catalog.go b/pkg/sql/opt_catalog.go index 8ff79fdebd4b..4c3358e34ca4 100644 --- a/pkg/sql/opt_catalog.go +++ b/pkg/sql/opt_catalog.go @@ -1806,12 +1806,20 @@ func (oi *optVirtualIndex) Predicate() (string, bool) { // KeyColumnCount is part of the cat.Index interface. func (oi *optVirtualIndex) KeyColumnCount() int { - return 1 + // Virtual indexes for the time being always have exactly 2 key columns, + // because they're only constructable on a single column, and we don't support + // the concept of a unique virtual index. So, we always export 2 key columns: + // the first is the column to be indexed, and the second is the fake virtual + // index column that we pretend exists to guarantee uniqueness. See the + // implementation of optVirtualIndex.Column(). + return 2 } // LaxKeyColumnCount is part of the cat.Index interface. func (oi *optVirtualIndex) LaxKeyColumnCount() int { - return 1 + // Virtual indexes are never unique, so their lax key is the same as their + // key. + return 2 } // lookupColumnOrdinal returns the ordinal of the column with the given ID. A @@ -1830,10 +1838,6 @@ func (oi *optVirtualIndex) Column(i int) cat.IndexColumn { if oi.isPrimary { return cat.IndexColumn{Column: oi.tab.Column(i)} } - if i == oi.ColumnCount()-1 { - // The special bogus PK column goes at the end. It has ID 0. - return cat.IndexColumn{Column: oi.tab.Column(0)} - } length := len(oi.desc.ColumnIDs) if i < length { ord, _ := oi.tab.lookupColumnOrdinal(oi.desc.ColumnIDs[i]) @@ -1841,8 +1845,13 @@ func (oi *optVirtualIndex) Column(i int) cat.IndexColumn { Column: oi.tab.Column(ord), } } + if i == length { + // The special bogus PK column goes at the end of the index columns. It + // has ID 0. + return cat.IndexColumn{Column: oi.tab.Column(0)} + } - i -= length + i -= length + 1 ord, _ := oi.tab.lookupColumnOrdinal(oi.desc.StoreColumnIDs[i]) return cat.IndexColumn{Column: oi.tab.Column(ord)} } @@ -1869,7 +1878,7 @@ func (oi *optVirtualIndex) Ordinal() int { // PartitionByListPrefixes is part of the cat.Index interface. func (oi *optVirtualIndex) PartitionByListPrefixes() []tree.Datums { - panic("no partition") + return nil } // InterleaveAncestorCount is part of the cat.Index interface.