exec-ddl
CREATE TABLE a (x INT PRIMARY KEY, y INT, s STRING, d DECIMAL NOT NULL, UNIQUE (s DESC, d))
----

exec-ddl
CREATE TABLE b (x INT, z INT NOT NULL)
----

build
SELECT * FROM a
----
project
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 └── scan a
      ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) crdb_internal_mvcc_timestamp:5(decimal) tableoid:6(oid)
      ├── key: (1)
      ├── fd: (1)-->(2-6), (3,4)~~>(1,2,5,6)
      ├── prune: (1-6)
      └── interesting orderings: (+1) (-3,+4,+1)

build
SELECT * FROM b
----
project
 ├── columns: x:1(int) z:2(int!null)
 ├── prune: (1,2)
 └── scan b
      ├── columns: x:1(int) z:2(int!null) rowid:3(int!null) crdb_internal_mvcc_timestamp:4(decimal) tableoid:5(oid)
      ├── key: (3)
      ├── fd: (3)-->(1,2,4,5)
      ├── prune: (1-5)
      └── interesting orderings: (+3)

# Select subset of columns.
opt
SELECT s, x FROM a
----
scan a@a_s_d_key
 ├── columns: s:3(string) x:1(int!null)
 ├── key: (1)
 ├── fd: (1)-->(3)
 ├── prune: (1,3)
 └── interesting orderings: (+1) (-3)

# Test constrained scan.
opt
SELECT s, x FROM a WHERE x=1
----
scan a
 ├── columns: s:3(string) x:1(int!null)
 ├── constraint: /1: [/1 - /1]
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1,3)
 └── prune: (3)

# Test partial index scan not null columns.
exec-ddl
CREATE INDEX i ON a (y) WHERE y > 0 AND y < 5
----

opt
SELECT s, y FROM a WHERE y > 0 AND y < 5
----
index-join a
 ├── columns: s:3(string) y:2(int!null)
 ├── prune: (3)
 ├── interesting orderings: (-3) (+2)
 └── scan a@i,partial
      ├── columns: x:1(int!null) y:2(int!null)
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── prune: (1,2)
      └── interesting orderings: (+2,+1)

exec-ddl
DROP INDEX i
----

# Test partial index scan cardinality.
exec-ddl
CREATE INDEX i ON a (y) WHERE x > 0 AND x < 5
----

# Disable GenerateConstrainedScans to verify that the partial index scan in its
# own group has a cardinality of [0 - 4].
opt disable=GenerateConstrainedScans
SELECT s FROM a WHERE x > 0 AND x < 5 AND y = 1
----
project
 ├── columns: s:3(string)
 ├── cardinality: [0 - 4]
 ├── prune: (3)
 ├── interesting orderings: (-3)
 └── index-join a
      ├── columns: x:1(int!null) y:2(int!null) s:3(string)
      ├── cardinality: [0 - 4]
      ├── key: (1)
      ├── fd: ()-->(2), (1)-->(3)
      ├── prune: (3)
      ├── interesting orderings: (+1 opt(2)) (-3 opt(2))
      └── select
           ├── columns: x:1(int!null) y:2(int!null)
           ├── cardinality: [0 - 4]
           ├── key: (1)
           ├── fd: ()-->(2)
           ├── prune: (1)
           ├── interesting orderings: (+1 opt(2))
           ├── scan a@i,partial
           │    ├── columns: x:1(int!null) y:2(int)
           │    ├── cardinality: [0 - 4]
           │    ├── key: (1)
           │    ├── fd: (1)-->(2)
           │    ├── prune: (1,2)
           │    └── interesting orderings: (+2,+1)
           └── filters
                └── eq [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
                     ├── variable: y:2 [type=int]
                     └── const: 1 [type=int]

exec-ddl
DROP INDEX i
----

# Test limited scan.
opt
SELECT s, x FROM a WHERE x > 1 LIMIT 2
----
scan a
 ├── columns: s:3(string) x:1(int!null)
 ├── constraint: /1: [/2 - ]
 ├── limit: 2
 ├── key: (1)
 ├── fd: (1)-->(3)
 ├── prune: (3)
 └── interesting orderings: (+1) (-3)

# Test limited scan with 1 row.
opt
SELECT s, x FROM a WHERE x > 1 LIMIT 1
----
scan a
 ├── columns: s:3(string) x:1(int!null)
 ├── constraint: /1: [/2 - ]
 ├── limit: 1
 ├── key: ()
 ├── fd: ()-->(1,3)
 ├── prune: (3)
 └── interesting orderings: (+1) (-3)

# Test case where there are no weak keys available.
opt
SELECT d FROM a
----
scan a@a_s_d_key
 ├── columns: d:4(decimal!null)
 └── prune: (4)

exec-ddl
CREATE TABLE t (
  a INT,
  b CHAR,
  c INT,
  d CHAR,
  PRIMARY KEY (a, b),
  INDEX bc (b, c),
  INDEX dc (d, c),
  INDEX a_desc (a DESC),
  FAMILY (a, b),
  FAMILY (c),
  FAMILY (d)
)
----

opt
SELECT 1 FROM t WHERE a > 1 AND a < 2
----
values
 ├── columns: "?column?":7(int!null)
 ├── cardinality: [0 - 0]
 ├── key: ()
 ├── fd: ()-->(7)
 └── prune: (7)

opt
SELECT * FROM t@bc WHERE b IN ('a', 'b') AND c IN (1, 2) AND a IN (2, 3)
----
index-join t
 ├── columns: a:1(int!null) b:2(char!null) c:3(int!null) d:4(char)
 ├── key: (1,2)
 ├── fd: (1,2)-->(3,4)
 ├── prune: (4)
 ├── interesting orderings: (+1,+2) (+2,+3,+1) (+4,+3,+1,+2) (-1,+2)
 └── scan t@bc
      ├── columns: a:1(int!null) b:2(char!null) c:3(int!null)
      ├── constraint: /2/3/1
      │    ├── [/'a'/1/2 - /'a'/1/3]
      │    ├── [/'a'/2/2 - /'a'/2/3]
      │    ├── [/'b'/1/2 - /'b'/1/3]
      │    └── [/'b'/2/2 - /'b'/2/3]
      ├── flags: force-index=bc
      ├── cardinality: [0 - 8]
      ├── key: (1,2)
      ├── fd: (1,2)-->(3)
      ├── prune: (1-3)
      └── interesting orderings: (+2,+3,+1)

opt
SELECT * FROM a WHERE x IN (1, 2, 4, 6, 7, 9)
----
scan a
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── constraint: /1
 │    ├── [/1 - /2]
 │    ├── [/4 - /4]
 │    ├── [/6 - /7]
 │    └── [/9 - /9]
 ├── cardinality: [0 - 6]
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (2-4)
 └── interesting orderings: (+1) (-3,+4,+1)

exec-ddl
CREATE TABLE date_pk (d DATE PRIMARY KEY, i INT)
----

opt
SELECT * FROM date_pk WHERE d IN ('2019-08-08', '2019-08-07') OR (d >= '2017-01-01' AND d < '2017-01-05')
----
scan date_pk
 ├── columns: d:1(date!null) i:2(int)
 ├── constraint: /1
 │    ├── [/'2017-01-01' - /'2017-01-04']
 │    └── [/'2019-08-07' - /'2019-08-08']
 ├── cardinality: [0 - 6]
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── prune: (2)
 └── interesting orderings: (+1)


# Regression test for #42731: we were incorrectly setting cardinality [0 - 1].
exec-ddl
CREATE TABLE t42731 (id INT PRIMARY KEY, unique_value INT UNIQUE, notnull_value INT NOT NULL)
----

norm
SELECT * FROM t42731 WHERE unique_value IS NULL AND notnull_value = 2000
----
select
 ├── columns: id:1(int!null) unique_value:2(int) notnull_value:3(int!null)
 ├── key: (1)
 ├── fd: ()-->(2,3), (2)~~>(1)
 ├── prune: (1)
 ├── interesting orderings: (+1 opt(2,3))
 ├── scan t42731
 │    ├── columns: id:1(int!null) unique_value:2(int) notnull_value:3(int!null)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3), (2)~~>(1,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1) (+2,+1)
 └── filters
      ├── is [type=bool, outer=(2), constraints=(/2: [/NULL - /NULL]; tight), fd=()-->(2)]
      │    ├── variable: unique_value:2 [type=int]
      │    └── null [type=unknown]
      └── eq [type=bool, outer=(3), constraints=(/3: [/2000 - /2000]; tight), fd=()-->(3)]
           ├── variable: notnull_value:3 [type=int]
           └── const: 2000 [type=int]

# The scan should be marked as side-effecting if FOR UPDATE is used.
build
SELECT * FROM a FOR UPDATE
----
project
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── volatile
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── prune: (1-4)
 ├── interesting orderings: (+1) (-3,+4,+1)
 └── scan a
      ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) crdb_internal_mvcc_timestamp:5(decimal) tableoid:6(oid)
      ├── locking: for-update
      ├── volatile
      ├── key: (1)
      ├── fd: (1)-->(2-6), (3,4)~~>(1,2,5,6)
      ├── prune: (1-6)
      └── interesting orderings: (+1) (-3,+4,+1)

exec-ddl
CREATE TABLE kab (
  k INT8 PRIMARY KEY,
  a INT8 NOT NULL,
  b INT8 NOT NULL CHECK (b = 0),
  INDEX ba (b, a)
)
----

# Verify that check constraints are factored into Scan FDs (namely
# that b:3 shows up as constant).
build
SELECT * FROM kab
----
project
 ├── columns: k:1(int!null) a:2(int!null) b:3(int!null)
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1-3)
 ├── interesting orderings: (+1 opt(3)) (+2,+1 opt(3))
 └── scan kab
      ├── columns: k:1(int!null) a:2(int!null) b:3(int!null) crdb_internal_mvcc_timestamp:4(decimal) tableoid:5(oid)
      ├── check constraint expressions
      │    └── eq [type=bool, outer=(3), constraints=(/3: [/0 - /0]; tight), fd=()-->(3)]
      │         ├── variable: b:3 [type=int]
      │         └── const: 0 [type=int]
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(2,4,5)
      ├── prune: (1-5)
      └── interesting orderings: (+1 opt(3)) (+2,+1 opt(3))

# Test partial index scan functional dependencies.

exec-ddl
CREATE TABLE c (
    k INT PRIMARY KEY,
    x INT NOT NULL,
    y INT,
    s STRING,
    UNIQUE (x) WHERE s = 'foo',
    UNIQUE (y) WHERE s = 'bar'
)
----

# Do not build table function dependency keys from partial index columns.
build
SELECT k FROM c
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 ├── prune: (1)
 ├── interesting orderings: (+1)
 └── scan c
      ├── columns: k:1(int!null) x:2(int!null) y:3(int) s:4(string) crdb_internal_mvcc_timestamp:5(decimal) tableoid:6(oid)
      ├── partial index predicates
      │    ├── c_x_key: filters
      │    │    └── eq [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]
      │    │         ├── variable: s:4 [type=string]
      │    │         └── const: 'foo' [type=string]
      │    └── c_y_key: filters
      │         └── eq [type=bool, outer=(4), constraints=(/4: [/'bar' - /'bar']; tight), fd=()-->(4)]
      │              ├── variable: s:4 [type=string]
      │              └── const: 'bar' [type=string]
      ├── key: (1)
      ├── fd: (1)-->(2-6)
      ├── prune: (1-6)
      └── interesting orderings: (+1) (+2) (+3,+1)

# Add a strict key for partial index scan functional dependencies. (x)-->(k)
# should be in the fds.
opt
SELECT * FROM c WHERE s = 'foo'
----
index-join c
 ├── columns: k:1(int!null) x:2(int!null) y:3(int) s:4(string!null)
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3)
 ├── prune: (1-3)
 ├── interesting orderings: (+1 opt(4)) (+2 opt(4)) (+3,+1 opt(4))
 └── scan c@c_x_key,partial
      ├── columns: k:1(int!null) x:2(int!null)
      ├── key: (1)
      ├── fd: (1)-->(2), (2)-->(1)
      ├── prune: (1,2)
      └── interesting orderings: (+2)

# Add a lax key for partial index scan functional dependencies when the key can
# be null. (y)~~>(k) should be in the fds.
opt
SELECT * FROM c WHERE s = 'bar'
----
index-join c
 ├── columns: k:1(int!null) x:2(int!null) y:3(int) s:4(string!null)
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3)
 ├── prune: (1-3)
 ├── interesting orderings: (+1 opt(4)) (+2 opt(4)) (+3,+1 opt(4))
 └── scan c@c_y_key,partial
      ├── columns: k:1(int!null) y:3(int)
      ├── key: (1)
      ├── fd: (1)-->(3), (3)~~>(1)
      ├── prune: (1,3)
      └── interesting orderings: (+3,+1)

# Test FDs for computed columns.
# We add equivalencies s=c_s and d=c_d, a strict dependency i->c_i_expr, and
# no dependency d->c_d_expr since the expression d::string is composite-
# sensitive.
exec-ddl
CREATE TABLE computed (
  i INT,
  s STRING,
  d DECIMAL,
  c_i_expr STRING AS (CASE WHEN i < 0 THEN 'foo' ELSE 'bar' END) STORED,
  c_s STRING AS (s) VIRTUAL,
  c_d DECIMAL AS (d) STORED,
  c_d_expr STRING AS (d::string) STORED,
  PRIMARY KEY (c_i_expr, i),
  UNIQUE (c_s, s),
  UNIQUE (c_d_expr, d)
)
----

build
SELECT * FROM computed
----
project
 ├── columns: i:1(int!null) s:2(string) d:3(decimal) c_i_expr:4(string!null) c_s:5(string) c_d:6(decimal) c_d_expr:7(string)
 ├── key: (1)
 ├── fd: (1,4)-->(2,3,5-7), (3,7)~~>(1,2,4-6), (1)-->(4), (2)~~>(1,3-7), (3)==(6), (6)==(3), (2)==(5), (5)==(2)
 ├── prune: (1-7)
 ├── interesting orderings: (+4,+1) (+7,+(3|6),+4,+1)
 └── project
      ├── columns: c_s:5(string) i:1(int!null) s:2(string) d:3(decimal) c_i_expr:4(string!null) c_d:6(decimal) c_d_expr:7(string) crdb_internal_mvcc_timestamp:8(decimal) tableoid:9(oid)
      ├── key: (1)
      ├── fd: (1,4)-->(2,3,6-9), (3,7)~~>(1,2,4,6,8,9), (1)-->(4), (2)~~>(1,3,4,6-9), (3)==(6), (6)==(3), (2)==(5), (5)==(2)
      ├── prune: (1-9)
      ├── interesting orderings: (+4,+1) (+7,+(3|6),+4,+1)
      ├── scan computed
      │    ├── columns: i:1(int!null) s:2(string) d:3(decimal) c_i_expr:4(string!null) c_d:6(decimal) c_d_expr:7(string) crdb_internal_mvcc_timestamp:8(decimal) tableoid:9(oid)
      │    ├── computed column expressions
      │    │    ├── c_i_expr:4
      │    │    │    └── case [type=string]
      │    │    │         ├── true [type=bool]
      │    │    │         ├── when [type=string]
      │    │    │         │    ├── lt [type=bool]
      │    │    │         │    │    ├── variable: i:1 [type=int]
      │    │    │         │    │    └── const: 0 [type=int]
      │    │    │         │    └── const: 'foo' [type=string]
      │    │    │         └── const: 'bar' [type=string]
      │    │    ├── c_s:5
      │    │    │    └── variable: s:2 [type=string]
      │    │    ├── c_d:6
      │    │    │    └── variable: d:3 [type=decimal]
      │    │    └── c_d_expr:7
      │    │         └── cast: STRING [type=string]
      │    │              └── variable: d:3 [type=decimal]
      │    ├── key: (1)
      │    ├── fd: (1,4)-->(2,3,6-9), (3,7)~~>(1,2,4,6,8,9), (1)-->(4), (2)~~>(1,3,4,6-9), (3)==(6), (6)==(3)
      │    ├── prune: (1-4,6-9)
      │    └── interesting orderings: (+4,+1) (+7,+(3|6),+4,+1)
      └── projections
           └── variable: s:2 [as=c_s:5, type=string, outer=(2)]

# Test FDs for inverted index scans.
exec-ddl
CREATE TABLE inv (
  k INT PRIMARY KEY,
  a INT[],
  b BOOL,
  INVERTED INDEX a_idx (a),
  INVERTED INDEX a_b_idx (a) WHERE b
)
----

exec-ddl
CREATE TABLE inv_multi_pks (
  k1 INT,
  k2 INT,
  a INT[],
  b BOOL,
  PRIMARY KEY (k1, k2),
  INVERTED INDEX a_idx (a),
  INVERTED INDEX a_b_idx (a) WHERE b
)
----

# If the inverted scan scans a single key, then it upholds the FD keys of the
# underlying table.
opt
SELECT * FROM inv WHERE a @> ARRAY[1]
----
index-join inv
 ├── columns: k:1(int!null) a:2(int[]!null) b:3(bool)
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,3)
 ├── interesting orderings: (+1)
 └── scan inv@a_idx,inverted
      ├── columns: k:1(int!null)
      ├── inverted constraint: /6/1
      │    └── spans: [1, 1]
      ├── key: (1)
      └── prune: (1)

# Same as above, but with multiple PK columns.
opt
SELECT * FROM inv_multi_pks WHERE a @> ARRAY[1]
----
index-join inv_multi_pks
 ├── columns: k1:1(int!null) k2:2(int!null) a:3(int[]!null) b:4(bool)
 ├── immutable
 ├── key: (1,2)
 ├── fd: (1,2)-->(3,4)
 ├── prune: (1,2,4)
 ├── interesting orderings: (+1,+2)
 └── scan inv_multi_pks@a_idx,inverted
      ├── columns: k1:1(int!null) k2:2(int!null)
      ├── inverted constraint: /7/1/2
      │    └── spans: [1, 1]
      ├── key: (1,2)
      └── prune: (1,2)

# If the inverted scan scans multiple keys, then it does NOT uphold the FD keys
# of the underlying table. The table FD keys are only valid after the inverted
# filter expression.
opt
SELECT * FROM inv@{NO_ZIGZAG_JOIN} WHERE a @> ARRAY[1, 2]
----
index-join inv
 ├── columns: k:1(int!null) a:2(int[]!null) b:3(bool)
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,3)
 ├── interesting orderings: (+1)
 └── inverted-filter
      ├── columns: k:1(int!null)
      ├── inverted expression: /6
      │    ├── tight: true, unique: true
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: true
      │         │    └── union spans: [1, 1]
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: [2, 2]
      ├── key: (1)
      └── scan inv@a_idx,inverted
           ├── columns: k:1(int!null) a_inverted_key:6(encodedkey!null)
           ├── inverted constraint: /6/1
           │    └── spans: [1, 3)
           ├── flags: no-zigzag-join
           └── prune: (1,6)

# Same as above, but with multiple inverted spans instead of a single, multi-key
# span.
opt
SELECT * FROM inv@{NO_ZIGZAG_JOIN} WHERE a @> ARRAY[1, 3]
----
index-join inv
 ├── columns: k:1(int!null) a:2(int[]!null) b:3(bool)
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,3)
 ├── interesting orderings: (+1)
 └── inverted-filter
      ├── columns: k:1(int!null)
      ├── inverted expression: /6
      │    ├── tight: true, unique: true
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: true
      │         │    └── union spans: [1, 1]
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: [3, 3]
      ├── key: (1)
      └── scan inv@a_idx,inverted
           ├── columns: k:1(int!null) a_inverted_key:6(encodedkey!null)
           ├── inverted constraint: /6/1
           │    └── spans
           │         ├── [1, 1]
           │         └── [3, 3]
           ├── flags: no-zigzag-join
           └── prune: (1,6)

# Same as above, but with multiple PK columns.
opt
SELECT * FROM inv_multi_pks@{NO_ZIGZAG_JOIN} WHERE a @> ARRAY[1, 3]
----
index-join inv_multi_pks
 ├── columns: k1:1(int!null) k2:2(int!null) a:3(int[]!null) b:4(bool)
 ├── immutable
 ├── key: (1,2)
 ├── fd: (1,2)-->(3,4)
 ├── prune: (1,2,4)
 ├── interesting orderings: (+1,+2)
 └── inverted-filter
      ├── columns: k1:1(int!null) k2:2(int!null)
      ├── inverted expression: /7
      │    ├── tight: true, unique: true
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: true
      │         │    └── union spans: [1, 1]
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: [3, 3]
      ├── key: (1,2)
      └── scan inv_multi_pks@a_idx,inverted
           ├── columns: k1:1(int!null) k2:2(int!null) a_inverted_key:7(encodedkey!null)
           ├── inverted constraint: /7/1/2
           │    └── spans
           │         ├── [1, 1]
           │         └── [3, 3]
           ├── flags: no-zigzag-join
           └── prune: (1,2,7)

# A single key scan over a partial inverted index upholds the FD keys of the
# underlying table.
opt
SELECT * FROM inv WHERE a @> ARRAY[1] AND b
----
index-join inv
 ├── columns: k:1(int!null) a:2(int[]!null) b:3(bool!null)
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1)
 ├── interesting orderings: (+1 opt(3))
 └── scan inv@a_b_idx,inverted,partial
      ├── columns: k:1(int!null)
      ├── inverted constraint: /7/1
      │    └── spans: [1, 1]
      ├── key: (1)
      └── prune: (1)

# Same as above, but with multiple PK columns.
opt
SELECT * FROM inv_multi_pks WHERE a @> ARRAY[1] AND b
----
index-join inv_multi_pks
 ├── columns: k1:1(int!null) k2:2(int!null) a:3(int[]!null) b:4(bool!null)
 ├── immutable
 ├── key: (1,2)
 ├── fd: ()-->(4), (1,2)-->(3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1,+2 opt(4))
 └── scan inv_multi_pks@a_b_idx,inverted,partial
      ├── columns: k1:1(int!null) k2:2(int!null)
      ├── inverted constraint: /8/1/2
      │    └── spans: [1, 1]
      ├── key: (1,2)
      └── prune: (1,2)

# A multi-key scan over a partial inverted index does not uphold the FD keys of
# the underlying table.
opt
SELECT * FROM inv@{NO_ZIGZAG_JOIN} WHERE a @> ARRAY[1, 2] AND b
----
index-join inv
 ├── columns: k:1(int!null) a:2(int[]!null) b:3(bool!null)
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1)
 ├── interesting orderings: (+1 opt(3))
 └── inverted-filter
      ├── columns: k:1(int!null)
      ├── inverted expression: /7
      │    ├── tight: true, unique: true
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: true
      │         │    └── union spans: [1, 1]
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: [2, 2]
      ├── key: (1)
      └── scan inv@a_b_idx,inverted,partial
           ├── columns: k:1(int!null) a_inverted_key:7(encodedkey!null)
           ├── inverted constraint: /7/1
           │    └── spans: [1, 3)
           ├── flags: no-zigzag-join
           ├── key: (1,7)
           └── prune: (1,7)

# Same as above, but with multiple inverted spans instead of a single, multi-key
# span.
opt
SELECT * FROM inv@{NO_ZIGZAG_JOIN} WHERE a @> ARRAY[1, 3] AND b
----
index-join inv
 ├── columns: k:1(int!null) a:2(int[]!null) b:3(bool!null)
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1)
 ├── interesting orderings: (+1 opt(3))
 └── inverted-filter
      ├── columns: k:1(int!null)
      ├── inverted expression: /7
      │    ├── tight: true, unique: true
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: true
      │         │    └── union spans: [1, 1]
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: [3, 3]
      ├── key: (1)
      └── scan inv@a_b_idx,inverted,partial
           ├── columns: k:1(int!null) a_inverted_key:7(encodedkey!null)
           ├── inverted constraint: /7/1
           │    └── spans
           │         ├── [1, 1]
           │         └── [3, 3]
           ├── flags: no-zigzag-join
           ├── key: (1,7)
           └── prune: (1,7)

# Same as above, but with multiple PK columns.
opt
SELECT * FROM inv_multi_pks@{NO_ZIGZAG_JOIN} WHERE a @> ARRAY[1, 3] AND b
----
index-join inv_multi_pks
 ├── columns: k1:1(int!null) k2:2(int!null) a:3(int[]!null) b:4(bool!null)
 ├── immutable
 ├── key: (1,2)
 ├── fd: ()-->(4), (1,2)-->(3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1,+2 opt(4))
 └── inverted-filter
      ├── columns: k1:1(int!null) k2:2(int!null)
      ├── inverted expression: /8
      │    ├── tight: true, unique: true
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: true
      │         │    └── union spans: [1, 1]
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: [3, 3]
      ├── key: (1,2)
      └── scan inv_multi_pks@a_b_idx,inverted,partial
           ├── columns: k1:1(int!null) k2:2(int!null) a_inverted_key:8(encodedkey!null)
           ├── inverted constraint: /8/1/2
           │    └── spans
           │         ├── [1, 1]
           │         └── [3, 3]
           ├── flags: no-zigzag-join
           ├── key: (1,2,8)
           └── prune: (1,2,8)

opt
SELECT * FROM inv@a_idx WHERE a @> '{}'
----
index-join inv
 ├── columns: k:1(int!null) a:2(int[]!null) b:3(bool)
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,3)
 ├── interesting orderings: (+1)
 └── inverted-filter
      ├── columns: k:1(int!null)
      ├── inverted expression: /6
      │    ├── tight: true, unique: false
      │    └── union spans: [, NULL/NULL)
      ├── key: (1)
      └── scan inv@a_idx,inverted
           ├── columns: k:1(int!null) a_inverted_key:6(encodedkey!null)
           ├── inverted constraint: /6/1
           │    └── spans: [, NULL/NULL)
           ├── flags: force-index=a_idx
           └── prune: (1,6)
