exec-ddl
CREATE TABLE a
(
    k INT PRIMARY KEY,
    u INT,
    v INT,
    INDEX u(u) STORING (v),
    UNIQUE INDEX v(v) STORING (u)
)
----

exec-ddl
CREATE TABLE b
(
    k INT PRIMARY KEY,
    u INT,
    v INT,
    j JSONB,
    INDEX u(u),
    UNIQUE INDEX v(v),
    INVERTED INDEX j_inv_idx(j)
)
----

exec-ddl
CREATE TABLE c
(
    k INT PRIMARY KEY,
    a INT[],
    u INT,
    INVERTED INDEX a_inv_idx(a),
    INDEX u(u)
)
----

exec-ddl
CREATE TABLE d
(
    k INT PRIMARY KEY,
    u INT,
    v INT,
    w INT,
    INDEX u(u),
    INDEX v(v)
)
----

exec-ddl
CREATE TABLE e
(
    k INT PRIMARY KEY,
    u INT,
    v INT,
    w INT,
    INDEX uw(u, w),
    INDEX vw(v, w)
)
----

exec-ddl
CREATE TABLE f
(
    k INT,
    j INT,
    u INT,
    v INT,
    CONSTRAINT pk PRIMARY KEY (k, j),
    INDEX u(u),
    INDEX v(v)
)
----

exec-ddl
CREATE TABLE g
(
    k INT PRIMARY KEY,
    v INT,
    geom GEOMETRY,
    geog GEOGRAPHY,
    INVERTED INDEX geom_idx (geom),
    INVERTED INDEX geog_idx (geog)
)
----

exec-ddl
CREATE TABLE trgm (
    k INT PRIMARY KEY,
    s STRING,
    INVERTED INDEX s_idx (s gin_trgm_ops)
)
----

exec-ddl
CREATE TABLE p (
    k INT PRIMARY KEY,
    i INT,
    f FLOAT,
    s STRING,
    b BOOL
)
----

exec-ddl
CREATE TABLE no_explicit_primary_key
(
    k INT,
    u INT,
    v INT,
    INDEX u(u),
    INDEX v(v)
)
----

exec-ddl
CREATE TABLE computed (
    k INT PRIMARY KEY,
    s STRING,
    l STRING AS (lower(s)) STORED,
    j JSON,
    field JSON AS (j->'foo') STORED,
    fieldstr STRING AS ((j->'foo')::string) STORED,
    INDEX l_idx (l),
    INDEX fieldstr_idx (fieldstr),
    INVERTED INDEX field_inv_idx (field)
)
----

exec-ddl
CREATE TABLE virtual (
    k INT PRIMARY KEY,
    a INT,
    b INT,
    c INT AS (b + 10) VIRTUAL,
    s STRING,
    lower_s STRING AS (lower(s)) VIRTUAL,
    upper_s STRING AS (upper(s)) VIRTUAL,
    INDEX (a) WHERE c IN (10, 20, 30),
    INDEX (lower_s),
    INDEX (a) WHERE upper_s = 'FOO',
    INDEX (c) WHERE c > 100,
    INDEX (b) WHERE upper_s IS NOT NULL
)
----

exec-ddl
CREATE TABLE singleton_check(
  singleton BOOL DEFAULT TRUE,
  count INT NOT NULL,
  PRIMARY KEY (singleton),
  CHECK (singleton)
)
----

# --------------------------------------------------
# GeneratePartialIndexScans
# --------------------------------------------------

exec-ddl
CREATE INDEX idx ON p (i) STORING (f, s) WHERE s = 'foo'
----

# Generate a lone partial index scan when the index is covering and there are no
# remaining filters.
opt expect=GeneratePartialIndexScans
SELECT i FROM p WHERE s = 'foo'
----
project
 ├── columns: i:2
 └── scan p@idx,partial
      ├── columns: i:2 s:4!null
      └── fd: ()-->(4)

# Generate a partial index scan inside a select when the index is covering and
# there are remaining filters.
opt expect=GeneratePartialIndexScans
SELECT i FROM p WHERE s = 'foo' AND (i = 1 OR f = 2.0)
----
project
 ├── columns: i:2
 └── select
      ├── columns: i:2 f:3 s:4!null
      ├── fd: ()-->(4)
      ├── scan p@idx,partial
      │    ├── columns: i:2 f:3 s:4!null
      │    └── fd: ()-->(4)
      └── filters
           └── (i:2 = 1) OR (f:3 = 2.0) [outer=(2,3)]

# Generate a partial index scan inside an index-join when the index is not
# covering and there no remaining filters.
opt expect=GeneratePartialIndexScans
SELECT b FROM p WHERE s = 'foo'
----
project
 ├── columns: b:5
 └── index-join p
      ├── columns: s:4!null b:5
      ├── fd: ()-->(4)
      └── scan p@idx,partial
           ├── columns: k:1!null s:4!null
           ├── key: (1)
           └── fd: ()-->(4)

# Generate a partial index scan inside a select inside an index-join when the
# index is not covering and there are remaining filters that are covered by the
# index.
opt expect=GeneratePartialIndexScans
SELECT b FROM p WHERE s = 'foo' AND (i = 1 OR f = 2.0)
----
project
 ├── columns: b:5
 └── index-join p
      ├── columns: i:2 f:3 s:4!null b:5
      ├── fd: ()-->(4)
      └── select
           ├── columns: k:1!null i:2 f:3 s:4!null
           ├── key: (1)
           ├── fd: ()-->(4), (1)-->(2,3)
           ├── scan p@idx,partial
           │    ├── columns: k:1!null i:2 f:3 s:4!null
           │    ├── key: (1)
           │    └── fd: ()-->(4), (1)-->(2,3)
           └── filters
                └── (i:2 = 1) OR (f:3 = 2.0) [outer=(2,3)]

# Generate a partial index scan inside an index-join inside a select when the
# index is not covering and the remaining filters are not covered by the index.
opt expect=GeneratePartialIndexScans
SELECT b FROM p WHERE s = 'foo' AND b
----
project
 ├── columns: b:5!null
 ├── fd: ()-->(5)
 └── select
      ├── columns: s:4!null b:5!null
      ├── fd: ()-->(4,5)
      ├── index-join p
      │    ├── columns: s:4 b:5
      │    ├── fd: ()-->(4)
      │    └── scan p@idx,partial
      │         ├── columns: k:1!null s:4!null
      │         ├── key: (1)
      │         └── fd: ()-->(4)
      └── filters
           └── b:5 [outer=(5), constraints=(/5: [/true - /true]; tight), fd=()-->(5)]

# Generate a partial index scan inside a Select/IndexJoin/Select when the index
# is not covering and the remaining filters are partially covered by the index.
opt expect=GeneratePartialIndexScans
SELECT b FROM p WHERE s = 'foo' AND f = 1 AND b
----
project
 ├── columns: b:5!null
 ├── fd: ()-->(5)
 └── select
      ├── columns: f:3!null s:4!null b:5!null
      ├── fd: ()-->(3-5)
      ├── index-join p
      │    ├── columns: f:3 s:4 b:5
      │    ├── fd: ()-->(3,4)
      │    └── select
      │         ├── columns: k:1!null f:3!null s:4!null
      │         ├── key: (1)
      │         ├── fd: ()-->(3,4)
      │         ├── scan p@idx,partial
      │         │    ├── columns: k:1!null f:3 s:4!null
      │         │    ├── key: (1)
      │         │    └── fd: ()-->(4), (1)-->(3)
      │         └── filters
      │              └── f:3 = 1.0 [outer=(3), constraints=(/3: [/1.0 - /1.0]; tight), fd=()-->(3)]
      └── filters
           └── b:5 [outer=(5), constraints=(/5: [/true - /true]; tight), fd=()-->(5)]

# Generate multiple partial index scans when there are multiple partial indexes
# that have predicates implied by the filters.

exec-ddl
CREATE INDEX idx2 ON p (s) WHERE i > 0
----

memo expect=GeneratePartialIndexScans
SELECT * FROM p WHERE i > 0 AND s = 'foo'
----
memo (optimized, ~19KB, required=[presentation: k:1,i:2,f:3,s:4,b:5])
 ├── G1: (select G2 G3) (index-join G4 p,cols=(1-5)) (index-join G5 p,cols=(1-5)) (index-join G6 p,cols=(1-5)) (index-join G7 p,cols=(1-5))
 │    └── [presentation: k:1,i:2,f:3,s:4,b:5]
 │         ├── best: (index-join G4 p,cols=(1-5))
 │         └── cost: 63.03
 ├── G2: (scan p,cols=(1-5))
 │    └── []
 │         ├── best: (scan p,cols=(1-5))
 │         └── cost: 1129.02
 ├── G3: (filters G8 G9)
 ├── G4: (select G10 G11)
 │    └── []
 │         ├── best: (select G10 G11)
 │         └── cost: 28.95
 ├── G5: (select G12 G13)
 │    └── []
 │         ├── best: (select G12 G13)
 │         └── cost: 368.05
 ├── G6: (scan p@idx,partial,cols=(1-4),constrained)
 │    └── []
 │         ├── best: (scan p@idx,partial,cols=(1-4),constrained)
 │         └── cost: 28.10
 ├── G7: (scan p@idx2,partial,cols=(1,4),constrained)
 │    └── []
 │         ├── best: (scan p@idx2,partial,cols=(1,4),constrained)
 │         └── cost: 27.73
 ├── G8: (gt G14 G15)
 ├── G9: (eq G16 G17)
 ├── G10: (scan p@idx,partial,cols=(1-4))
 │    └── []
 │         ├── best: (scan p@idx,partial,cols=(1-4))
 │         └── cost: 28.82
 ├── G11: (filters G8)
 ├── G12: (scan p@idx2,partial,cols=(1,4))
 │    └── []
 │         ├── best: (scan p@idx2,partial,cols=(1,4))
 │         └── cost: 364.69
 ├── G13: (filters G9)
 ├── G14: (variable i)
 ├── G15: (const 0)
 ├── G16: (variable s)
 └── G17: (const 'foo')

# GeneratePartialIndexScans will be triggered, but not add an index
# scan to the memo since NO_INDEX_JOIN is specified.
memo expect-not=GeneratePartialIndexScans
SELECT * FROM p@{NO_INDEX_JOIN} WHERE i > 0 AND s = 'foo'
----
memo (optimized, ~10KB, required=[presentation: k:1,i:2,f:3,s:4,b:5])
 ├── G1: (select G2 G3)
 │    └── [presentation: k:1,i:2,f:3,s:4,b:5]
 │         ├── best: (select G2 G3)
 │         └── cost: 1139.06
 ├── G2: (scan p,cols=(1-5))
 │    └── []
 │         ├── best: (scan p,cols=(1-5))
 │         └── cost: 1129.02
 ├── G3: (filters G4 G5)
 ├── G4: (gt G6 G7)
 ├── G5: (eq G8 G9)
 ├── G6: (variable i)
 ├── G7: (const 0)
 ├── G8: (variable s)
 └── G9: (const 'foo')


# Do not generate a partial index scan when the predicate is not implied by the
# filter.
memo expect-not=GeneratePartialIndexScans
SELECT i FROM p WHERE s = 'bar'
----
memo (optimized, ~11KB, required=[presentation: i:2])
 ├── G1: (project G2 G3 i)
 │    └── [presentation: i:2]
 │         ├── best: (project G2 G3 i)
 │         └── cost: 1108.87
 ├── G2: (select G4 G5)
 │    └── []
 │         ├── best: (select G4 G5)
 │         └── cost: 1108.75
 ├── G3: (projections)
 ├── G4: (scan p,cols=(2,4))
 │    └── []
 │         ├── best: (scan p,cols=(2,4))
 │         └── cost: 1098.72
 ├── G5: (filters G6)
 ├── G6: (eq G7 G8)
 ├── G7: (variable s)
 └── G8: (const 'bar')

exec-ddl
DROP INDEX idx
----

exec-ddl
DROP INDEX idx2
----

exec-ddl
CREATE INDEX idx ON p (b) WHERE s IS NULL AND i = 0
----

# Project constant columns in a partial index predicate rather than performing
# an index join.
opt expect=GeneratePartialIndexScans
SELECT k, b, s, i FROM p WHERE s IS NULL AND i = 0
----
project
 ├── columns: k:1!null b:5 s:4 i:2!null
 ├── key: (1)
 ├── fd: ()-->(2,4), (1)-->(5)
 ├── scan p@idx,partial
 │    ├── columns: k:1!null b:5
 │    ├── key: (1)
 │    └── fd: (1)-->(5)
 └── projections
      ├── 0 [as=i:2]
      └── CAST(NULL AS STRING) [as=s:4]

# Do not project constant columns in a partial index predicate when an index
# join must be performed to fetch other columns.
opt expect=GeneratePartialIndexScans
SELECT k, b, s, i, f FROM p WHERE s IS NULL AND i = 0
----
index-join p
 ├── columns: k:1!null b:5 s:4 i:2!null f:3
 ├── key: (1)
 ├── fd: ()-->(2,4), (1)-->(3,5)
 └── scan p@idx,partial
      ├── columns: k:1!null b:5
      ├── key: (1)
      └── fd: (1)-->(5)

exec-ddl
DROP INDEX idx
----

exec-ddl
CREATE INDEX idx ON p (b) STORING (i) WHERE s IS NULL
----

# Project constant columns in a partial index predicate and apply a filter.
opt expect=GeneratePartialIndexScans
SELECT k, b, s FROM p WHERE s IS NULL AND i = 0
----
project
 ├── columns: k:1!null b:5 s:4
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(5)
 └── select
      ├── columns: k:1!null i:2!null s:4 b:5
      ├── key: (1)
      ├── fd: ()-->(2,4), (1)-->(5)
      ├── project
      │    ├── columns: s:4 k:1!null i:2 b:5
      │    ├── key: (1)
      │    ├── fd: ()-->(4), (1)-->(2,5)
      │    ├── scan p@idx,partial
      │    │    ├── columns: k:1!null i:2 b:5
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2,5)
      │    └── projections
      │         └── CAST(NULL AS STRING) [as=s:4]
      └── filters
           └── i:2 = 0 [outer=(2), constraints=(/2: [/0 - /0]; tight), fd=()-->(2)]

exec-ddl
DROP INDEX idx
----

exec-ddl
CREATE INDEX idx ON p (f, s) STORING (b) WHERE s IS NULL AND b AND i = 0
----

# Project only constant columns in the partial index predicate that do not exist
# in the index.
opt expect=GeneratePartialIndexScans
SELECT * FROM p WHERE s IS NULL AND b AND i = 0
----
project
 ├── columns: k:1!null i:2!null f:3 s:4 b:5!null
 ├── key: (1)
 ├── fd: ()-->(2,4,5), (1)-->(3)
 ├── scan p@idx,partial
 │    ├── columns: k:1!null f:3 s:4 b:5!null
 │    ├── key: (1)
 │    └── fd: ()-->(4,5), (1)-->(3)
 └── projections
      └── 0 [as=i:2]

exec-ddl
DROP INDEX idx
----

exec-ddl
CREATE INDEX idx ON p (i) WHERE f = 1.0
----

# Do not project constant columns with composite key encodings.
opt expect=GeneratePartialIndexScans
SELECT i, f FROM p WHERE f = 1.0
----
index-join p
 ├── columns: i:2 f:3!null
 ├── fd: ()-->(3)
 └── scan p@idx,partial
      ├── columns: k:1!null i:2
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
DROP INDEX idx
----

opt
SELECT k FROM virtual WHERE c IN (10, 20, 30)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan virtual@virtual_a_idx,partial
      ├── columns: k:1!null
      └── key: (1)

# TODO(mgartner): The normalization of the query filter from (c = 20) to ((b +
# 10) = 20) to (b = 10) and the lack of constraints for the partial index
# predicate ((b + 10) IN (10, 20, 30)) prevents implication from being proven. A
# normalization rule similar to NormalizeCmpPlusConst, but that applies to an IN
# expression might resolve this specific issue. However, there could be many
# more combinations of filters and predicates that make partial index
# implication difficult or impossible.
opt
SELECT k FROM virtual WHERE c = 20
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null b:3!null
      ├── key: (1)
      ├── fd: ()-->(3)
      ├── scan virtual
      │    ├── columns: k:1!null b:3
      │    ├── computed column expressions
      │    │    ├── c:4
      │    │    │    └── b:3 + 10
      │    │    ├── lower_s:6
      │    │    │    └── lower(s:5)
      │    │    └── upper_s:7
      │    │         └── upper(s:5)
      │    ├── partial index predicates
      │    │    ├── virtual_a_idx: filters
      │    │    │    └── (b:3 + 10) IN (10, 20, 30) [outer=(3), immutable]
      │    │    ├── virtual_a_idx1: filters
      │    │    │    └── upper(s:5) = 'FOO' [outer=(5), immutable]
      │    │    ├── virtual_c_idx: filters
      │    │    │    └── b:3 > 90 [outer=(3), constraints=(/3: [/91 - ]; tight)]
      │    │    └── virtual_b_idx: filters
      │    │         └── upper(s:5) IS NOT NULL [outer=(5), immutable]
      │    ├── key: (1)
      │    └── fd: (1)-->(3)
      └── filters
           └── b:3 = 10 [outer=(3), constraints=(/3: [/10 - /10]; tight), fd=()-->(3)]

opt
SELECT k FROM virtual WHERE (b + 10) IN (10, 20, 30)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan virtual@virtual_a_idx,partial
      ├── columns: k:1!null
      └── key: (1)

# --------------------------------------------------
# GenerateConstrainedScans
# --------------------------------------------------

opt
SELECT k FROM a WHERE k = 1
----
scan a
 ├── columns: k:1!null
 ├── constraint: /1: [/1 - /1]
 ├── cardinality: [0 - 1]
 ├── key: ()
 └── fd: ()-->(1)

memo
SELECT k FROM a WHERE k = 1
----
memo (optimized, ~7KB, required=[presentation: k:1])
 ├── G1: (select G2 G3) (scan a,cols=(1),constrained)
 │    └── [presentation: k:1]
 │         ├── best: (scan a,cols=(1),constrained)
 │         └── cost: 9.05
 ├── G2: (scan a,cols=(1)) (scan a@u,cols=(1)) (scan a@v,cols=(1))
 │    └── []
 │         ├── best: (scan a,cols=(1))
 │         └── cost: 1068.42
 ├── G3: (filters G4)
 ├── G4: (eq G5 G6)
 ├── G5: (variable k)
 └── G6: (const 1)

opt
SELECT k FROM a WHERE v > 1
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── scan a@v
      ├── columns: k:1!null v:3!null
      ├── constraint: /3: [/2 - ]
      ├── key: (1)
      └── fd: (1)-->(3), (3)-->(1)

memo
SELECT k FROM a WHERE v > 1
----
memo (optimized, ~8KB, required=[presentation: k:1])
 ├── G1: (project G2 G3 k)
 │    └── [presentation: k:1]
 │         ├── best: (project G2 G3 k)
 │         └── cost: 368.19
 ├── G2: (select G4 G5) (scan a@v,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan a@v,cols=(1,3),constrained)
 │         └── cost: 364.87
 ├── G3: (projections)
 ├── G4: (scan a,cols=(1,3)) (scan a@u,cols=(1,3)) (scan a@v,cols=(1,3))
 │    └── []
 │         ├── best: (scan a,cols=(1,3))
 │         └── cost: 1078.52
 ├── G5: (filters G6)
 ├── G6: (gt G7 G8)
 ├── G7: (variable v)
 └── G8: (const 1)

opt
SELECT k FROM a WHERE u = 1 AND k = 5
----
project
 ├── columns: k:1!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── scan a@u
      ├── columns: k:1!null u:2!null
      ├── constraint: /2/1: [/1/5 - /1/5]
      ├── cardinality: [0 - 1]
      ├── key: ()
      └── fd: ()-->(1,2)

memo
SELECT k FROM a WHERE u = 1 AND k = 5
----
memo (optimized, ~10KB, required=[presentation: k:1])
 ├── G1: (project G2 G3 k)
 │    └── [presentation: k:1]
 │         ├── best: (project G2 G3 k)
 │         └── cost: 9.08
 ├── G2: (select G4 G5) (select G6 G7) (scan a@u,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan a@u,cols=(1,2),constrained)
 │         └── cost: 9.06
 ├── G3: (projections)
 ├── G4: (scan a,cols=(1,2)) (scan a@u,cols=(1,2)) (scan a@v,cols=(1,2))
 │    └── []
 │         ├── best: (scan a,cols=(1,2))
 │         └── cost: 1078.52
 ├── G5: (filters G8 G9)
 ├── G6: (scan a,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan a,cols=(1,2),constrained)
 │         └── cost: 9.06
 ├── G7: (filters G8)
 ├── G8: (eq G10 G11)
 ├── G9: (eq G12 G13)
 ├── G10: (variable u)
 ├── G11: (const 1)
 ├── G12: (variable k)
 └── G13: (const 5)

# Constraint + remaining filter.
opt
SELECT k FROM a WHERE u = 1 AND k+u = 1
----
project
 ├── columns: k:1!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── scan a@u
      ├── columns: k:1!null u:2!null
      ├── constraint: /2/1: [/1/0 - /1/0]
      ├── cardinality: [0 - 1]
      ├── key: ()
      └── fd: ()-->(1,2)

memo
SELECT k FROM a WHERE u = 1 AND k+u = 1
----
memo (optimized, ~11KB, required=[presentation: k:1])
 ├── G1: (project G2 G3 k)
 │    └── [presentation: k:1]
 │         ├── best: (project G2 G3 k)
 │         └── cost: 9.08
 ├── G2: (select G4 G5) (select G6 G7) (scan a@u,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan a@u,cols=(1,2),constrained)
 │         └── cost: 9.06
 ├── G3: (projections)
 ├── G4: (scan a,cols=(1,2)) (scan a@u,cols=(1,2)) (scan a@v,cols=(1,2))
 │    └── []
 │         ├── best: (scan a,cols=(1,2))
 │         └── cost: 1078.52
 ├── G5: (filters G8 G9)
 ├── G6: (scan a,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan a,cols=(1,2),constrained)
 │         └── cost: 9.06
 ├── G7: (filters G8)
 ├── G8: (eq G10 G11)
 ├── G9: (eq G12 G13)
 ├── G10: (variable u)
 ├── G11: (const 1)
 ├── G12: (variable k)
 └── G13: (const 0)

opt
SELECT k FROM a WHERE u = 1 AND v = 5
----
project
 ├── columns: k:1!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── select
      ├── columns: k:1!null u:2!null v:3!null
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(1-3)
      ├── scan a@v
      │    ├── columns: k:1!null u:2 v:3!null
      │    ├── constraint: /3: [/5 - /5]
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    └── fd: ()-->(1-3)
      └── filters
           └── u:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]

memo
SELECT k FROM a WHERE u = 1 AND v = 5
----
memo (optimized, ~12KB, required=[presentation: k:1])
 ├── G1: (project G2 G3 k)
 │    └── [presentation: k:1]
 │         ├── best: (project G2 G3 k)
 │         └── cost: 9.12
 ├── G2: (select G4 G5) (select G6 G7) (select G8 G9)
 │    └── []
 │         ├── best: (select G8 G9)
 │         └── cost: 9.10
 ├── G3: (projections)
 ├── G4: (scan a,cols=(1-3)) (scan a@u,cols=(1-3)) (scan a@v,cols=(1-3))
 │    └── []
 │         ├── best: (scan a,cols=(1-3))
 │         └── cost: 1088.62
 ├── G5: (filters G10 G11)
 ├── G6: (scan a@u,cols=(1-3),constrained)
 │    └── []
 │         ├── best: (scan a@u,cols=(1-3),constrained)
 │         └── cost: 28.62
 ├── G7: (filters G11)
 ├── G8: (scan a@v,cols=(1-3),constrained)
 │    └── []
 │         ├── best: (scan a@v,cols=(1-3),constrained)
 │         └── cost: 9.07
 ├── G9: (filters G10)
 ├── G10: (eq G12 G13)
 ├── G11: (eq G14 G15)
 ├── G12: (variable u)
 ├── G13: (const 1)
 ├── G14: (variable v)
 └── G15: (const 5)

# Only not-null constraint is pushed down.
opt
SELECT k FROM a WHERE u=v
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null u:2!null v:3!null
      ├── key: (1)
      ├── fd: (1)-->(2,3), (3)-->(1), (2)==(3), (3)==(2)
      ├── scan a@u
      │    ├── columns: k:1!null u:2!null v:3
      │    ├── constraint: /2/1: (/NULL - ]
      │    ├── key: (1)
      │    └── fd: (1)-->(2,3), (3)~~>(1,2)
      └── filters
           └── u:2 = v:3 [outer=(2,3), constraints=(/2: (/NULL - ]; /3: (/NULL - ]), fd=(2)==(3), (3)==(2)]

# Don't push constraint into already limited scan.
opt
SELECT k FROM (SELECT k FROM a ORDER BY u LIMIT 1) a WHERE k = 1
----
project
 ├── columns: k:1!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── select
      ├── columns: k:1!null u:2
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(1,2)
      ├── scan a@u
      │    ├── columns: k:1!null u:2
      │    ├── limit: 1
      │    ├── key: ()
      │    └── fd: ()-->(1,2)
      └── filters
           └── k:1 = 1 [outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]

# Constraint + index-join, with no remainder filter.
opt
SELECT * FROM b WHERE v >= 1 AND v <= 10
----
index-join b
 ├── columns: k:1!null u:2 v:3!null j:4
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)-->(1,2,4)
 └── scan b@v
      ├── columns: k:1!null v:3!null
      ├── constraint: /3: [/1 - /10]
      ├── cardinality: [0 - 10]
      ├── key: (1)
      └── fd: (1)-->(3), (3)-->(1)

memo
SELECT * FROM b WHERE v >= 1 AND v <= 10
----
memo (optimized, ~8KB, required=[presentation: k:1,u:2,v:3,j:4])
 ├── G1: (select G2 G3) (index-join G4 b,cols=(1-4))
 │    └── [presentation: k:1,u:2,v:3,j:4]
 │         ├── best: (index-join G4 b,cols=(1-4))
 │         └── cost: 79.12
 ├── G2: (scan b,cols=(1-4))
 │    └── []
 │         ├── best: (scan b,cols=(1-4))
 │         └── cost: 1108.82
 ├── G3: (filters G5)
 ├── G4: (scan b@v,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan b@v,cols=(1,3),constrained)
 │         └── cost: 18.41
 ├── G5: (range G6)
 ├── G6: (and G7 G8)
 ├── G7: (ge G9 G10)
 ├── G8: (le G9 G11)
 ├── G9: (variable v)
 ├── G10: (const 1)
 └── G11: (const 10)

# Don't choose lookup join if it's not beneficial.
opt
SELECT * FROM b WHERE v > 1
----
select
 ├── columns: k:1!null u:2 v:3!null j:4
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)-->(1,2,4)
 ├── scan b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3)~~>(1,2,4)
 └── filters
      └── v:3 > 1 [outer=(3), constraints=(/3: [/2 - ]; tight)]

opt
SELECT * FROM b WHERE v >= 1 AND v <= 10 AND k > 5
----
index-join b
 ├── columns: k:1!null u:2 v:3!null j:4
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)-->(1,2,4)
 └── select
      ├── columns: k:1!null v:3!null
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(3), (3)-->(1)
      ├── scan b@v
      │    ├── columns: k:1!null v:3!null
      │    ├── constraint: /3: [/1 - /10]
      │    ├── cardinality: [0 - 10]
      │    ├── key: (1)
      │    └── fd: (1)-->(3), (3)-->(1)
      └── filters
           └── k:1 > 5 [outer=(1), constraints=(/1: [/6 - ]; tight)]

memo
SELECT * FROM b WHERE v >= 1 AND v <= 10 AND k > 5
----
memo (optimized, ~11KB, required=[presentation: k:1,u:2,v:3,j:4])
 ├── G1: (select G2 G3) (select G4 G5) (index-join G6 b,cols=(1-4))
 │    └── [presentation: k:1,u:2,v:3,j:4]
 │         ├── best: (index-join G6 b,cols=(1-4))
 │         └── cost: 51.19
 ├── G2: (scan b,cols=(1-4))
 │    └── []
 │         ├── best: (scan b,cols=(1-4))
 │         └── cost: 1108.82
 ├── G3: (filters G7 G8)
 ├── G4: (scan b,cols=(1-4),constrained)
 │    └── []
 │         ├── best: (scan b,cols=(1-4),constrained)
 │         └── cost: 378.02
 ├── G5: (filters G7)
 ├── G6: (select G9 G10)
 │    └── []
 │         ├── best: (select G9 G10)
 │         └── cost: 18.53
 ├── G7: (range G11)
 ├── G8: (gt G12 G13)
 ├── G9: (scan b@v,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan b@v,cols=(1,3),constrained)
 │         └── cost: 18.41
 ├── G10: (filters G8)
 ├── G11: (and G14 G15)
 ├── G12: (variable k)
 ├── G13: (const 5)
 ├── G14: (ge G16 G17)
 ├── G15: (le G16 G18)
 ├── G16: (variable v)
 ├── G17: (const 1)
 └── G18: (const 10)

# Ensure the rule doesn't match at all when the first column of the index is
# not in the filter (i.e. the @v index is not matched by ConstrainScans).
exploretrace rule=GenerateConstrainedScans
SELECT k FROM a WHERE u = 1
----
----
================================================================================
GenerateConstrainedScans
================================================================================
Source expression:
  project
   ├── columns: k:1!null
   ├── key: (1)
   └── select
        ├── columns: k:1!null u:2!null
        ├── key: (1)
        ├── fd: ()-->(2)
        ├── scan a
        │    ├── columns: k:1!null u:2
        │    ├── key: (1)
        │    └── fd: (1)-->(2)
        └── filters
             └── u:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]

New expression 1 of 1:
  project
   ├── columns: k:1!null
   ├── key: (1)
   └── scan a@u
        ├── columns: k:1!null u:2!null
        ├── constraint: /2/1: [/1 - /1]
        ├── key: (1)
        └── fd: ()-->(2)
----
----

# Constraint + index join + remaining filter.
opt
SELECT * FROM b WHERE v >= 1 AND v <= 10 AND k+u = 1
----
select
 ├── columns: k:1!null u:2 v:3!null j:4
 ├── cardinality: [0 - 10]
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)-->(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── cardinality: [0 - 10]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)-->(1), (3)~~>(1,2,4)
 │    └── scan b@v
 │         ├── columns: k:1!null v:3!null
 │         ├── constraint: /3: [/1 - /10]
 │         ├── cardinality: [0 - 10]
 │         ├── key: (1)
 │         └── fd: (1)-->(3), (3)-->(1)
 └── filters
      └── (k:1 + u:2) = 1 [outer=(1,2), immutable]

memo
SELECT * FROM b WHERE v >= 1 AND v <= 10 AND k+u = 1
----
memo (optimized, ~9KB, required=[presentation: k:1,u:2,v:3,j:4])
 ├── G1: (select G2 G3) (select G4 G5)
 │    └── [presentation: k:1,u:2,v:3,j:4]
 │         ├── best: (select G4 G5)
 │         └── cost: 79.24
 ├── G2: (scan b,cols=(1-4))
 │    └── []
 │         ├── best: (scan b,cols=(1-4))
 │         └── cost: 1108.82
 ├── G3: (filters G6 G7)
 ├── G4: (index-join G8 b,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G8 b,cols=(1-4))
 │         └── cost: 79.12
 ├── G5: (filters G7)
 ├── G6: (range G9)
 ├── G7: (eq G10 G11)
 ├── G8: (scan b@v,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan b@v,cols=(1,3),constrained)
 │         └── cost: 18.41
 ├── G9: (and G12 G13)
 ├── G10: (plus G14 G15)
 ├── G11: (const 1)
 ├── G12: (ge G16 G11)
 ├── G13: (le G16 G17)
 ├── G14: (variable k)
 ├── G15: (variable u)
 ├── G16: (variable v)
 └── G17: (const 10)

opt
SELECT * FROM b WHERE v >= 1 AND v <= 10 AND k+u = 1 AND k > 5
----
select
 ├── columns: k:1!null u:2 v:3!null j:4
 ├── cardinality: [0 - 10]
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)-->(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── cardinality: [0 - 10]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)-->(1), (3)~~>(1,2,4)
 │    └── select
 │         ├── columns: k:1!null v:3!null
 │         ├── cardinality: [0 - 10]
 │         ├── key: (1)
 │         ├── fd: (1)-->(3), (3)-->(1)
 │         ├── scan b@v
 │         │    ├── columns: k:1!null v:3!null
 │         │    ├── constraint: /3: [/1 - /10]
 │         │    ├── cardinality: [0 - 10]
 │         │    ├── key: (1)
 │         │    └── fd: (1)-->(3), (3)-->(1)
 │         └── filters
 │              └── k:1 > 5 [outer=(1), constraints=(/1: [/6 - ]; tight)]
 └── filters
      └── (k:1 + u:2) = 1 [outer=(1,2), immutable]

memo
SELECT * FROM b WHERE v >= 1 AND v <= 10 AND k+u = 1 AND k > 5
----
memo (optimized, ~12KB, required=[presentation: k:1,u:2,v:3,j:4])
 ├── G1: (select G2 G3) (select G4 G5) (select G6 G7)
 │    └── [presentation: k:1,u:2,v:3,j:4]
 │         ├── best: (select G6 G7)
 │         └── cost: 38.83
 ├── G2: (scan b,cols=(1-4))
 │    └── []
 │         ├── best: (scan b,cols=(1-4))
 │         └── cost: 1108.82
 ├── G3: (filters G8 G9 G10)
 ├── G4: (scan b,cols=(1-4),constrained)
 │    └── []
 │         ├── best: (scan b,cols=(1-4),constrained)
 │         └── cost: 378.02
 ├── G5: (filters G8 G9)
 ├── G6: (index-join G11 b,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G11 b,cols=(1-4))
 │         └── cost: 38.77
 ├── G7: (filters G9)
 ├── G8: (range G12)
 ├── G9: (eq G13 G14)
 ├── G10: (gt G15 G16)
 ├── G11: (select G17 G18)
 │    └── []
 │         ├── best: (select G17 G18)
 │         └── cost: 18.53
 ├── G12: (and G19 G20)
 ├── G13: (plus G15 G21)
 ├── G14: (const 1)
 ├── G15: (variable k)
 ├── G16: (const 5)
 ├── G17: (scan b@v,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan b@v,cols=(1,3),constrained)
 │         └── cost: 18.41
 ├── G18: (filters G10)
 ├── G19: (ge G22 G14)
 ├── G20: (le G22 G23)
 ├── G21: (variable u)
 ├── G22: (variable v)
 └── G23: (const 10)

# Constraint + index-join.
opt
SELECT * FROM b WHERE (u, k, v) > (1, 2, 3) AND (u, k, v) < (8, 9, 10)
----
select
 ├── columns: k:1!null u:2!null v:3 j:4
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── scan b@u
 │         ├── columns: k:1!null u:2!null
 │         ├── constraint: /2/1: [/1/2 - /8/9]
 │         ├── key: (1)
 │         └── fd: (1)-->(2)
 └── filters
      ├── (u:2, k:1, v:3) > (1, 2, 3) [outer=(1-3), immutable, constraints=(/2/1/3: [/1/2/4 - ]; tight)]
      └── (u:2, k:1, v:3) < (8, 9, 10) [outer=(1-3), immutable, constraints=(/2/1/3: (/NULL - /8/9/9]; tight)]

memo
SELECT * FROM b WHERE (u, k, v) > (1, 2, 3) AND (u, k, v) < (8, 9, 10)
----
memo (optimized, ~9KB, required=[presentation: k:1,u:2,v:3,j:4])
 ├── G1: (select G2 G3) (select G4 G3)
 │    └── [presentation: k:1,u:2,v:3,j:4]
 │         ├── best: (select G4 G3)
 │         └── cost: 587.68
 ├── G2: (scan b,cols=(1-4))
 │    └── []
 │         ├── best: (scan b,cols=(1-4))
 │         └── cost: 1108.82
 ├── G3: (filters G5 G6)
 ├── G4: (index-join G7 b,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G7 b,cols=(1-4))
 │         └── cost: 586.84
 ├── G5: (gt G8 G9)
 ├── G6: (lt G8 G10)
 ├── G7: (scan b@u,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan b@u,cols=(1,2),constrained)
 │         └── cost: 101.22
 ├── G8: (tuple G11)
 ├── G9: (tuple G12)
 ├── G10: (tuple G13)
 ├── G11: (scalar-list G14 G15 G16)
 ├── G12: (scalar-list G17 G18 G19)
 ├── G13: (scalar-list G20 G21 G22)
 ├── G14: (variable u)
 ├── G15: (variable k)
 ├── G16: (variable v)
 ├── G17: (const 1)
 ├── G18: (const 2)
 ├── G19: (const 3)
 ├── G20: (const 8)
 ├── G21: (const 9)
 └── G22: (const 10)

# GenerateConstrainedScans propagates row-level locking information.
opt
SELECT k FROM a WHERE k = 1 FOR UPDATE
----
scan a
 ├── columns: k:1!null
 ├── constraint: /1: [/1 - /1]
 ├── locking: for-update
 ├── cardinality: [0 - 1]
 ├── volatile
 ├── key: ()
 └── fd: ()-->(1)

opt
SELECT * FROM b WHERE v >= 1 AND v <= 10 FOR UPDATE
----
index-join b
 ├── columns: k:1!null u:2 v:3!null j:4
 ├── locking: for-update
 ├── cardinality: [0 - 10]
 ├── volatile
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)-->(1,2,4)
 └── scan b@v
      ├── columns: k:1!null v:3!null
      ├── constraint: /3: [/1 - /10]
      ├── locking: for-update
      ├── cardinality: [0 - 10]
      ├── volatile
      ├── key: (1)
      └── fd: (1)-->(3), (3)-->(1)

opt
SELECT * FROM b WHERE v >= 1 AND v <= 10 AND k+u = 1 FOR UPDATE
----
select
 ├── columns: k:1!null u:2 v:3!null j:4
 ├── cardinality: [0 - 10]
 ├── volatile
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)-->(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── locking: for-update
 │    ├── cardinality: [0 - 10]
 │    ├── volatile
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)-->(1), (3)~~>(1,2,4)
 │    └── scan b@v
 │         ├── columns: k:1!null v:3!null
 │         ├── constraint: /3: [/1 - /10]
 │         ├── locking: for-update
 │         ├── cardinality: [0 - 10]
 │         ├── volatile
 │         ├── key: (1)
 │         └── fd: (1)-->(3), (3)-->(1)
 └── filters
      └── (k:1 + u:2) = 1 [outer=(1,2), immutable]

exec-ddl
CREATE TABLE kifs
(
    k INT PRIMARY KEY,
    i INT,
    f FLOAT,
    s STRING,
    j JSON,
    INDEX s_idx (s) STORING (i, f),
    INDEX si_idx (s DESC, i DESC) STORING (j)
)
----

# Constrain the kifs@si_idx so that an index join is generated.
exploretrace rule=GenerateConstrainedScans
SELECT s, i, f FROM kifs WHERE s='foo' ORDER BY s, k, i
----
----
================================================================================
GenerateConstrainedScans
================================================================================
Source expression:
  sort
   ├── columns: s:4!null i:2 f:3  [hidden: k:1!null]
   ├── key: (1)
   ├── fd: ()-->(4), (1)-->(2,3)
   ├── ordering: +1 opt(4) [actual: +1]
   └── select
        ├── columns: k:1!null i:2 f:3 s:4!null
        ├── key: (1)
        ├── fd: ()-->(4), (1)-->(2,3)
        ├── scan kifs@s_idx
        │    ├── columns: k:1!null i:2 f:3 s:4
        │    ├── key: (1)
        │    └── fd: (1)-->(2-4)
        └── filters
             └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

New expression 1 of 2:
  scan kifs@s_idx
   ├── columns: s:4!null i:2 f:3  [hidden: k:1!null]
   ├── constraint: /4/1: [/'foo' - /'foo']
   ├── key: (1)
   ├── fd: ()-->(4), (1)-->(2,3)
   └── ordering: +1 opt(4) [actual: +1]

New expression 2 of 2:
  index-join kifs
   ├── columns: s:4!null i:2 f:3  [hidden: k:1!null]
   ├── key: (1)
   ├── fd: ()-->(4), (1)-->(2,3)
   ├── ordering: +1 opt(4) [actual: +1]
   └── sort
        ├── columns: k:1!null i:2 s:4!null
        ├── key: (1)
        ├── fd: ()-->(4), (1)-->(2)
        ├── ordering: +1 opt(4) [actual: +1]
        └── scan kifs@si_idx
             ├── columns: k:1!null i:2 s:4!null
             ├── constraint: /-4/-2/1: [/'foo' - /'foo']
             ├── key: (1)
             └── fd: ()-->(4), (1)-->(2)
----
----

memo
SELECT s, i, f FROM kifs WHERE s='foo' ORDER BY s DESC, i
----
memo (optimized, ~8KB, required=[presentation: s:4,i:2,f:3] [ordering: +2 opt(4)])
 ├── G1: (select G2 G3) (scan kifs@s_idx,cols=(2-4),constrained) (index-join G4 kifs,cols=(2-4))
 │    ├── [presentation: s:4,i:2,f:3] [ordering: +2 opt(4)]
 │    │    ├── best: (sort G1)
 │    │    └── cost: 29.90
 │    └── []
 │         ├── best: (scan kifs@s_idx,cols=(2-4),constrained)
 │         └── cost: 28.72
 ├── G2: (scan kifs,cols=(2-4)) (scan kifs@s_idx,cols=(2-4))
 │    ├── [ordering: +2 opt(4)]
 │    │    ├── best: (sort G2="[ordering: +4]")
 │    │    └── cost: 1181.96
 │    ├── [ordering: +4]
 │    │    ├── best: (scan kifs@s_idx,cols=(2-4))
 │    │    └── cost: 1098.72
 │    └── []
 │         ├── best: (scan kifs@s_idx,cols=(2-4))
 │         └── cost: 1098.72
 ├── G3: (filters G5)
 ├── G4: (scan kifs@si_idx,cols=(1,2,4),constrained)
 │    ├── [ordering: +2 opt(4)]
 │    │    ├── best: (scan kifs@si_idx,rev,cols=(1,2,4),constrained)
 │    │    └── cost: 29.05
 │    └── []
 │         ├── best: (scan kifs@si_idx,cols=(1,2,4),constrained)
 │         └── cost: 28.72
 ├── G5: (eq G6 G7)
 ├── G6: (variable s)
 └── G7: (const 'foo')

memo
SELECT j FROM kifs WHERE s = 'foo'
----
memo (optimized, ~10KB, required=[presentation: j:5])
 ├── G1: (project G2 G3 j)
 │    └── [presentation: j:5]
 │         ├── best: (project G2 G3 j)
 │         └── cost: 28.74
 ├── G2: (select G4 G5) (index-join G6 kifs,cols=(4,5)) (scan kifs@si_idx,cols=(4,5),constrained)
 │    └── []
 │         ├── best: (scan kifs@si_idx,cols=(4,5),constrained)
 │         └── cost: 28.62
 ├── G3: (projections)
 ├── G4: (scan kifs,cols=(4,5)) (scan kifs@si_idx,cols=(4,5))
 │    └── []
 │         ├── best: (scan kifs@si_idx,cols=(4,5))
 │         └── cost: 1088.62
 ├── G5: (filters G7)
 ├── G6: (scan kifs@s_idx,cols=(1,4),constrained)
 │    └── []
 │         ├── best: (scan kifs@s_idx,cols=(1,4),constrained)
 │         └── cost: 28.62
 ├── G7: (eq G8 G9)
 ├── G8: (variable s)
 └── G9: (const 'foo')

memo
SELECT i, k FROM kifs WHERE s >= 'foo'
----
memo (optimized, ~9KB, required=[presentation: i:2,k:1])
 ├── G1: (project G2 G3 k i)
 │    └── [presentation: i:2,k:1]
 │         ├── best: (project G2 G3 k i)
 │         └── cost: 378.04
 ├── G2: (select G4 G5) (scan kifs@s_idx,cols=(1,2,4),constrained) (scan kifs@si_idx,cols=(1,2,4),constrained)
 │    └── []
 │         ├── best: (scan kifs@s_idx,cols=(1,2,4),constrained)
 │         └── cost: 374.69
 ├── G3: (projections)
 ├── G4: (scan kifs,cols=(1,2,4)) (scan kifs@s_idx,cols=(1,2,4)) (scan kifs@si_idx,cols=(1,2,4))
 │    └── []
 │         ├── best: (scan kifs@s_idx,cols=(1,2,4))
 │         └── cost: 1098.72
 ├── G5: (filters G6)
 ├── G6: (ge G7 G8)
 ├── G7: (variable s)
 └── G8: (const 'foo')

# Collated strings are treated properly.
exec-ddl
CREATE TABLE x (s STRING COLLATE en_u_ks_level1 PRIMARY KEY)
----

opt
SELECT s FROM x WHERE s < 'hello' COLLATE en_u_ks_level1
----
scan x
 ├── columns: s:1!null
 ├── constraint: /1: [ - /'hello' COLLATE en_u_ks_level1)
 └── key: (1)

opt
SELECT s FROM x WHERE s = 'hello' COLLATE en_u_ks_level1
----
scan x
 ├── columns: s:1!null
 ├── constraint: /1: [/'hello' COLLATE en_u_ks_level1 - /'hello' COLLATE en_u_ks_level1]
 ├── cardinality: [0 - 1]
 ├── key: ()
 └── fd: ()-->(1)

# Can't generate spans for other collations.
opt
SELECT s FROM x WHERE s COLLATE en = 'hello' COLLATE en
----
select
 ├── columns: s:1!null
 ├── key: (1)
 ├── scan x
 │    ├── columns: s:1!null
 │    └── key: (1)
 └── filters
      └── s:1 COLLATE en = 'hello' COLLATE en [outer=(1)]

# Realistic example where using constraints as filters help.
# An even more realistic example would have a creation timestamp instead of a
# seq_num integer, but that makes the plans much more cluttered.
exec-ddl
CREATE TABLE "orders" (
  region STRING NOT NULL,
  id INT NOT NULL,
  total DECIMAL NOT NULL,
  seq_num INT NOT NULL,
  PRIMARY KEY (region, id),
  UNIQUE INDEX orders_by_seq_num (region, seq_num, id) STORING (total),
  CHECK (region IN ('us-east1', 'us-west1', 'europe-west2'))
)
----

exec-ddl
ALTER TABLE "orders" INJECT STATISTICS '[
  {
    "columns": ["region"],
    "distinct_count": 3,
    "null_count": 0,
    "row_count": 100,
    "created_at": "2018-01-01 1:00:00.00000+00:00"
  },
  {
    "columns": ["id"],
    "distinct_count": 100,
    "null_count": 0,
    "row_count": 100,
    "created_at": "2018-01-01 1:00:00.00000+00:00"
  },
  {
    "columns": ["total"],
    "distinct_count": 100,
    "null_count": 0,
    "row_count": 100,
    "created_at": "2018-01-01 1:00:00.00000+00:00"
  },
  {
    "columns": ["seq_num"],
    "distinct_count": 50,
    "null_count": 0,
    "row_count": 100,
    "created_at": "2018-01-01 1:00:00.00000+00:00"
  }
]'
----

opt
SELECT sum(total) FROM orders WHERE seq_num >= 10 AND seq_num < 20
----
scalar-group-by
 ├── columns: sum:7
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(7)
 ├── scan orders@orders_by_seq_num
 │    ├── columns: total:3!null seq_num:4!null
 │    └── constraint: /1/4/2
 │         ├── [/'europe-west2'/10 - /'europe-west2'/19]
 │         ├── [/'us-east1'/10 - /'us-east1'/19]
 │         └── [/'us-west1'/10 - /'us-west1'/19]
 └── aggregations
      └── sum [as=sum:7, outer=(3)]
           └── total:3

exec-ddl
CREATE TABLE xyz (
  x INT PRIMARY KEY,
  y INT NOT NULL,
  z STRING NOT NULL,
  CHECK (x < 10 AND x > 1),
  CHECK (y < 10 AND y > 1),
  CHECK (z in ('first', 'second')),
  INDEX secondary (y, x),
  INDEX tertiary (z, y, x))
----

opt
SELECT x, y  FROM xyz WHERE x > 5
----
select
 ├── columns: x:1!null y:2!null
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── scan xyz@tertiary
 │    ├── columns: x:1!null y:2!null
 │    ├── constraint: /3/2/1
 │    │    ├── [/'first'/2/6 - /'first'/9/9]
 │    │    └── [/'second'/2/6 - /'second'/9/9]
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters
      └── x:1 > 5 [outer=(1), constraints=(/1: [/6 - ]; tight)]

opt
SELECT * FROM xyz WHERE x > 5
----
select
 ├── columns: x:1!null y:2!null z:3!null
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── scan xyz@tertiary
 │    ├── columns: x:1!null y:2!null z:3!null
 │    ├── constraint: /3/2/1
 │    │    ├── [/'first'/2/6 - /'first'/9/9]
 │    │    └── [/'second'/2/6 - /'second'/9/9]
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 └── filters
      └── x:1 > 5 [outer=(1), constraints=(/1: [/6 - ]; tight)]

# Check constraint used only for the non nullable column. Constraints on x are ignored.
exec-ddl
CREATE TABLE xy (
  x INT,
  y INT NOT NULL,
  CHECK (x < 10 AND x > 1),
  CHECK (y < 10 AND y > 1),
  INDEX secondary (y, x))
----

opt
SELECT x, y FROM xy WHERE x > 5
----
select
 ├── columns: x:1!null y:2!null
 ├── scan xy@secondary
 │    ├── columns: x:1 y:2!null
 │    └── constraint: /2/1/3: [/2/6 - /9]
 └── filters
      └── x:1 > 5 [outer=(1), constraints=(/1: [/6 - ]; tight)]

# Check constraints that can evaluate to NULL are ignored.
exec-ddl
CREATE TABLE null_constraint (
  y INT NOT NULL,
  CHECK (y IN (1, 2, NULL)),
  INDEX index_1 (y))
----

opt
SELECT y FROM null_constraint WHERE y > 0
----
scan null_constraint@index_1
 ├── columns: y:1!null
 └── constraint: /1/2: [/1 - ]

exec-ddl
CREATE TABLE null_constraint_2 (
  y INT NOT NULL,
  CHECK ((y IN (1, 2, NULL)) AND (y > 10)),
  CHECK (y < 15),
  INDEX index_1 (y))
----

opt
SELECT y FROM null_constraint_2 WHERE y > 0
----
scan null_constraint_2@index_1
 ├── columns: y:1!null
 └── constraint: /1/2: [/1 - /14]

# Unvalidated constraints are ignored.
exec-ddl
CREATE TABLE check_constraint_validity (
 a int NOT NULL,
 INDEX secondary (a),
 CONSTRAINT "check:unvalidated" CHECK (a < 10),
 CONSTRAINT "check:validated" CHECK (a < 20))
----

opt
SELECT * FROM check_constraint_validity WHERE a > 6
----
scan check_constraint_validity@secondary
 ├── columns: a:1!null
 └── constraint: /1/2: [/7 - /19]

# Test that we can constrain indexes using the results of now().
exec-ddl
CREATE TABLE with_time_index (k INT PRIMARY KEY, time TIMESTAMP, INDEX(time))
----

opt
SELECT * FROM with_time_index WHERE time > now() - INTERVAL '1 hour'
----
scan with_time_index@with_time_index_"time"_idx
 ├── columns: k:1!null time:2!null
 ├── constraint: /2/1: [/'2017-05-10 12:00:00.000001' - ]
 ├── key: (1)
 └── fd: (1)-->(2)

opt
SELECT * FROM with_time_index WHERE time >= 'today'
----
scan with_time_index@with_time_index_"time"_idx
 ├── columns: k:1!null time:2!null
 ├── constraint: /2/1: [/'2017-05-10 00:00:00' - ]
 ├── key: (1)
 └── fd: (1)-->(2)

# Test that we can constrain an index on a virtual column with a function in the
# expression.
opt expect=GenerateConstrainedScans
SELECT k FROM virtual WHERE lower_s = 'foo'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan virtual@virtual_lower_s_idx
      ├── columns: k:1!null
      ├── constraint: /6/1: [/'foo' - /'foo']
      └── key: (1)

# Constrained partial index scan.

exec-ddl
CREATE INDEX idx ON p (i) STORING (f, s) WHERE s = 'foo'
----

opt
SELECT i FROM p WHERE s = 'foo' AND i > 5 AND i < 10
----
project
 ├── columns: i:2!null
 └── scan p@idx,partial
      ├── columns: i:2!null s:4!null
      ├── constraint: /2/1: [/6 - /9]
      └── fd: ()-->(4)

exec-ddl
DROP INDEX idx
----

# Constrained partial index scan with indexed column in predicate.

exec-ddl
CREATE INDEX idx ON p (i) WHERE i > 0
----

opt
SELECT i FROM p WHERE i > 10 AND i < 20
----
scan p@idx,partial
 ├── columns: i:2!null
 └── constraint: /2/1: [/11 - /19]

exec-ddl
DROP INDEX idx
----

# Constrained partial index scan with index-join.

exec-ddl
CREATE INDEX idx ON p (i) WHERE s = 'foo'
----

opt
SELECT * FROM p WHERE s = 'foo' AND i > 5 AND i < 10
----
index-join p
 ├── columns: k:1!null i:2!null f:3 s:4!null b:5
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3,5)
 └── scan p@idx,partial
      ├── columns: k:1!null i:2!null
      ├── constraint: /2/1: [/6 - /9]
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
DROP INDEX idx
----

# Constrained partial index scan with additional filter.

exec-ddl
CREATE INDEX idx ON p (i) WHERE i > 0
----

opt
SELECT i FROM p WHERE i > 10 AND i < 20 AND b
----
project
 ├── columns: i:2!null
 └── select
      ├── columns: i:2!null b:5!null
      ├── fd: ()-->(5)
      ├── index-join p
      │    ├── columns: i:2 b:5
      │    └── scan p@idx,partial
      │         ├── columns: k:1!null i:2!null
      │         ├── constraint: /2/1: [/11 - /19]
      │         ├── key: (1)
      │         └── fd: (1)-->(2)
      └── filters
           └── b:5 [outer=(5), constraints=(/5: [/true - /true]; tight), fd=()-->(5)]

exec-ddl
DROP INDEX idx
----

# Constrained partial index scan with additional filters before and after an
# index-join.

exec-ddl
CREATE INDEX idx ON p (i) STORING (s) where i > 0
----

opt
SELECT * FROM p WHERE i > 10 AND i < 20 AND s = 'bar' AND b
----
select
 ├── columns: k:1!null i:2!null f:3 s:4!null b:5!null
 ├── key: (1)
 ├── fd: ()-->(4,5), (1)-->(2,3)
 ├── index-join p
 │    ├── columns: k:1!null i:2 f:3 s:4 b:5
 │    ├── key: (1)
 │    ├── fd: ()-->(4), (1)-->(2,3,5)
 │    └── select
 │         ├── columns: k:1!null i:2!null s:4!null
 │         ├── key: (1)
 │         ├── fd: ()-->(4), (1)-->(2)
 │         ├── scan p@idx,partial
 │         │    ├── columns: k:1!null i:2!null s:4
 │         │    ├── constraint: /2/1: [/11 - /19]
 │         │    ├── key: (1)
 │         │    └── fd: (1)-->(2,4)
 │         └── filters
 │              └── s:4 = 'bar' [outer=(4), constraints=(/4: [/'bar' - /'bar']; tight), fd=()-->(4)]
 └── filters
      └── b:5 [outer=(5), constraints=(/5: [/true - /true]; tight), fd=()-->(5)]

exec-ddl
DROP INDEX idx
----

# Generate multiple partial index scans in the memo when the filter implies
# multiple partial index predicates.

exec-ddl
CREATE INDEX idx ON p (s) WHERE i > 0
----

exec-ddl
CREATE INDEX idx2 ON p (i) WHERE s = 'foo'
----

memo
SELECT i FROM p WHERE i = 3 AND s = 'foo'
----
memo (optimized, ~24KB, required=[presentation: i:2])
 ├── G1: (project G2 G3 i)
 │    └── [presentation: i:2]
 │         ├── best: (project G2 G3 i)
 │         └── cost: 19.03
 ├── G2: (select G4 G5) (select G6 G7) (select G8 G7) (select G9 G7) (project G10 G11 i)
 │    └── []
 │         ├── best: (project G10 G11 i)
 │         └── cost: 19.00
 ├── G3: (projections)
 ├── G4: (scan p,cols=(2,4))
 │    └── []
 │         ├── best: (scan p,cols=(2,4))
 │         └── cost: 1098.72
 ├── G5: (filters G12 G13)
 ├── G6: (index-join G14 p,cols=(2,4))
 │    └── []
 │         ├── best: (index-join G14 p,cols=(2,4))
 │         └── cost: 388.66
 ├── G7: (filters G12)
 ├── G8: (project G15 G11 i)
 │    └── []
 │         ├── best: (project G15 G11 i)
 │         └── cost: 28.54
 ├── G9: (index-join G16 p,cols=(2,4))
 │    └── []
 │         ├── best: (index-join G16 p,cols=(2,4))
 │         └── cost: 84.40
 ├── G10: (scan p@idx2,partial,cols=(2),constrained)
 │    └── []
 │         ├── best: (scan p@idx2,partial,cols=(2),constrained)
 │         └── cost: 18.96
 ├── G11: (projections G17)
 ├── G12: (eq G18 G19)
 ├── G13: (eq G20 G17)
 ├── G14: (select G21 G22)
 │    └── []
 │         ├── best: (select G21 G22)
 │         └── cost: 368.05
 ├── G15: (scan p@idx2,partial,cols=(2))
 │    └── []
 │         ├── best: (scan p@idx2,partial,cols=(2))
 │         └── cost: 28.32
 ├── G16: (scan p@idx,partial,cols=(1,4),constrained)
 │    └── []
 │         ├── best: (scan p@idx,partial,cols=(1,4),constrained)
 │         └── cost: 27.73
 ├── G17: (const 'foo')
 ├── G18: (variable i)
 ├── G19: (const 3)
 ├── G20: (variable s)
 ├── G21: (scan p@idx,partial,cols=(1,4))
 │    └── []
 │         ├── best: (scan p@idx,partial,cols=(1,4))
 │         └── cost: 364.69
 └── G22: (filters G13)

exec-ddl
DROP INDEX idx
----

exec-ddl
DROP INDEX idx2
----

# Do not use partial indexes when the predicate is not implied by the filter.

exec-ddl
CREATE INDEX idx ON p (i) WHERE s = 'foo'
----

memo expect-not=GenerateConstrainedScans
SELECT i FROM p WHERE s = 'bar' AND i = 5
----
memo (optimized, ~10KB, required=[presentation: i:2])
 ├── G1: (project G2 G3 i)
 │    └── [presentation: i:2]
 │         ├── best: (project G2 G3 i)
 │         └── cost: 1108.79
 ├── G2: (select G4 G5)
 │    └── []
 │         ├── best: (select G4 G5)
 │         └── cost: 1108.76
 ├── G3: (projections)
 ├── G4: (scan p,cols=(2,4))
 │    └── []
 │         ├── best: (scan p,cols=(2,4))
 │         └── cost: 1098.72
 ├── G5: (filters G6 G7)
 ├── G6: (eq G8 G9)
 ├── G7: (eq G10 G11)
 ├── G8: (variable s)
 ├── G9: (const 'bar')
 ├── G10: (variable i)
 └── G11: (const 5)

exec-ddl
DROP INDEX idx
----

exec-ddl
CREATE INDEX idx ON p (b) WHERE s IS NULL AND i = 0
----

# Project constant columns in a partial index predicate rather than performing
# an index join.
opt expect=GenerateConstrainedScans
SELECT k, b, s, i FROM p WHERE b AND s IS NULL AND i = 0
----
project
 ├── columns: k:1!null b:5!null s:4 i:2!null
 ├── key: (1)
 ├── fd: ()-->(2,4,5)
 ├── scan p@idx,partial
 │    ├── columns: k:1!null b:5!null
 │    ├── constraint: /5/1: [/true - /true]
 │    ├── key: (1)
 │    └── fd: ()-->(5)
 └── projections
      ├── 0 [as=i:2]
      └── CAST(NULL AS STRING) [as=s:4]

# Do not project constant columns in a partial index predicate when an index
# join must be performed to fetch other columns.
opt expect=GenerateConstrainedScans
SELECT k, b, s, i, f FROM p WHERE b AND s IS NULL AND i = 0
----
index-join p
 ├── columns: k:1!null b:5!null s:4 i:2!null f:3
 ├── key: (1)
 ├── fd: ()-->(2,4,5), (1)-->(3)
 └── scan p@idx,partial
      ├── columns: k:1!null b:5!null
      ├── constraint: /5/1: [/true - /true]
      ├── key: (1)
      └── fd: ()-->(5)

exec-ddl
DROP INDEX idx
----

exec-ddl
CREATE INDEX idx ON p (b) STORING (i) WHERE s IS NULL
----

# Project constant columns in a partial index predicate and apply a filter.
opt expect=GenerateConstrainedScans
SELECT k, b, s FROM p WHERE b AND s IS NULL AND i = 0
----
project
 ├── columns: k:1!null b:5!null s:4
 ├── key: (1)
 ├── fd: ()-->(4,5)
 └── select
      ├── columns: k:1!null i:2!null s:4 b:5!null
      ├── key: (1)
      ├── fd: ()-->(2,4,5)
      ├── project
      │    ├── columns: s:4 k:1!null i:2 b:5!null
      │    ├── key: (1)
      │    ├── fd: ()-->(4,5), (1)-->(2)
      │    ├── scan p@idx,partial
      │    │    ├── columns: k:1!null i:2 b:5!null
      │    │    ├── constraint: /5/1: [/true - /true]
      │    │    ├── key: (1)
      │    │    └── fd: ()-->(5), (1)-->(2)
      │    └── projections
      │         └── CAST(NULL AS STRING) [as=s:4]
      └── filters
           └── i:2 = 0 [outer=(2), constraints=(/2: [/0 - /0]; tight), fd=()-->(2)]

exec-ddl
DROP INDEX idx
----

exec-ddl
CREATE INDEX idx ON p (f, s) STORING (b) WHERE s IS NULL AND b AND i = 0
----

# Project only constant columns in the partial index predicate that do not exist
# in the index.
opt expect=GenerateConstrainedScans
SELECT * FROM p WHERE f > 1.0 AND s IS NULL AND b AND i = 0
----
project
 ├── columns: k:1!null i:2!null f:3!null s:4 b:5!null
 ├── key: (1)
 ├── fd: ()-->(2,4,5), (1)-->(3)
 ├── scan p@idx,partial
 │    ├── columns: k:1!null f:3!null s:4 b:5!null
 │    ├── constraint: /3/4/1: [/1.0000000000000002 - ]
 │    ├── key: (1)
 │    └── fd: ()-->(4,5), (1)-->(3)
 └── projections
      └── 0 [as=i:2]

exec-ddl
DROP INDEX idx
----

exec-ddl
CREATE INDEX idx ON p (i) WHERE f = 1.0
----

# Do not project constant columns with composite key encodings.
opt expect=GenerateConstrainedScans
SELECT i, f FROM p WHERE i = 0 AND f = 1.0
----
index-join p
 ├── columns: i:2!null f:3!null
 ├── fd: ()-->(2,3)
 └── scan p@idx,partial
      ├── columns: k:1!null i:2!null
      ├── constraint: /2/1: [/0 - /0]
      ├── key: (1)
      └── fd: ()-->(2)

exec-ddl
DROP INDEX idx
----

# Constrained partial index scan with a virtual computed column in the
# predicate.
opt expect=GenerateConstrainedScans
SELECT k FROM virtual WHERE a > 10 AND a < 20 AND c IN (10, 20, 30)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan virtual@virtual_a_idx,partial
      ├── columns: k:1!null a:2!null
      ├── constraint: /2/1: [/11 - /19]
      ├── key: (1)
      └── fd: (1)-->(2)

opt expect=GenerateConstrainedScans
SELECT k FROM virtual WHERE b = 10 AND upper_s = 'FOO'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null b:3!null s:5
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(5)
      ├── index-join virtual
      │    ├── columns: k:1!null b:3 s:5
      │    ├── key: (1)
      │    ├── fd: ()-->(3), (1)-->(5)
      │    └── scan virtual@virtual_b_idx,partial
      │         ├── columns: k:1!null b:3!null
      │         ├── constraint: /3/1: [/10 - /10]
      │         ├── key: (1)
      │         └── fd: ()-->(3)
      └── filters
           └── upper(s:5) = 'FOO' [outer=(5), immutable]

opt expect=GenerateConstrainedScans
SELECT k FROM virtual WHERE b = 10 AND upper_s > 'FOO'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null b:3!null s:5
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(5)
      ├── index-join virtual
      │    ├── columns: k:1!null b:3 s:5
      │    ├── key: (1)
      │    ├── fd: ()-->(3), (1)-->(5)
      │    └── scan virtual@virtual_b_idx,partial
      │         ├── columns: k:1!null b:3!null
      │         ├── constraint: /3/1: [/10 - /10]
      │         ├── key: (1)
      │         └── fd: ()-->(3)
      └── filters
           └── upper(s:5) > 'FOO' [outer=(5), immutable]

opt expect-not=GenerateConstrainedScans set=(optimizer_prove_implication_with_virtual_computed_columns=false)
SELECT k FROM virtual WHERE b = 10 AND upper_s = 'FOO'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null b:3!null s:5
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(5)
      ├── scan virtual
      │    ├── columns: k:1!null b:3 s:5
      │    ├── computed column expressions
      │    │    ├── c:4
      │    │    │    └── b:3 + 10
      │    │    ├── lower_s:6
      │    │    │    └── lower(s:5)
      │    │    └── upper_s:7
      │    │         └── upper(s:5)
      │    ├── partial index predicates
      │    │    ├── virtual_a_idx: filters
      │    │    │    └── (b:3 + 10) IN (10, 20, 30) [outer=(3), immutable]
      │    │    ├── virtual_a_idx1: filters
      │    │    │    └── upper(s:5) = 'FOO' [outer=(5), immutable]
      │    │    ├── virtual_c_idx: filters
      │    │    │    └── b:3 > 90 [outer=(3), constraints=(/3: [/91 - ]; tight)]
      │    │    └── virtual_b_idx: filters
      │    │         └── upper(s:5) IS NOT NULL [outer=(5), immutable]
      │    ├── key: (1)
      │    └── fd: (1)-->(3,5)
      └── filters
           ├── upper(s:5) = 'FOO' [outer=(5), immutable]
           └── b:3 = 10 [outer=(3), constraints=(/3: [/10 - /10]; tight), fd=()-->(3)]

# TODO(mgartner): The normalization of the query filter from (c = 20) to ((b +
# 10) = 20) to (b = 10) and the lack of constraints for the partial index
# predicate ((b + 10) IN (10, 20, 30)) prevents implication from being proven. A
# normalization rule similar to NormalizeCmpPlusConst, but that applies to an IN
# expression might resolve this specific issue. However, there could be many
# more combinations of filters and predicates that make partial index
# implication difficult or impossible.
opt
SELECT k FROM virtual WHERE a > 10 AND a < 20 AND c = 20
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null a:2!null b:3!null
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(2)
      ├── scan virtual
      │    ├── columns: k:1!null a:2 b:3
      │    ├── computed column expressions
      │    │    ├── c:4
      │    │    │    └── b:3 + 10
      │    │    ├── lower_s:6
      │    │    │    └── lower(s:5)
      │    │    └── upper_s:7
      │    │         └── upper(s:5)
      │    ├── partial index predicates
      │    │    ├── virtual_a_idx: filters
      │    │    │    └── (b:3 + 10) IN (10, 20, 30) [outer=(3), immutable]
      │    │    ├── virtual_a_idx1: filters
      │    │    │    └── upper(s:5) = 'FOO' [outer=(5), immutable]
      │    │    ├── virtual_c_idx: filters
      │    │    │    └── b:3 > 90 [outer=(3), constraints=(/3: [/91 - ]; tight)]
      │    │    └── virtual_b_idx: filters
      │    │         └── upper(s:5) IS NOT NULL [outer=(5), immutable]
      │    ├── key: (1)
      │    └── fd: (1)-->(2,3)
      └── filters
           ├── (a:2 > 10) AND (a:2 < 20) [outer=(2), constraints=(/2: [/11 - /19]; tight)]
           └── b:3 = 10 [outer=(3), constraints=(/3: [/10 - /10]; tight), fd=()-->(3)]

# Test that we can constrain a partial index with a virtual column in the
# predicate.
opt expect=GenerateConstrainedScans
SELECT k FROM virtual WHERE a > 1 AND a < 10 AND upper_s = 'FOO'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan virtual@virtual_a_idx1,partial
      ├── columns: k:1!null a:2!null
      ├── constraint: /2/1: [/2 - /9]
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
CREATE TABLE t100206 (
        a INT NOT NULL,
        b INT NOT NULL,
        c INT NOT NULL AS (a+b) VIRTUAL,
        d INT,
        CONSTRAINT pkey PRIMARY KEY (c ASC, b ASC, a ASC));
----

# This should derive the value of c from a and b.
opt expect=GenerateConstrainedScans
SELECT a,b,c FROM t100206 WHERE (a,b) IN ((3,4), (5,6))
----
scan t100206
 ├── columns: a:1!null b:2!null c:3!null
 ├── constraint: /3/2/1
 │    ├── [/7/4/3 - /7/4/3]
 │    └── [/11/6/5 - /11/6/5]
 ├── cardinality: [0 - 2]
 ├── key: (1,2)
 └── fd: (1,2)-->(3)

# This should not derive the value of c when improved filter derivation is
# disabled.
opt expect-not=GenerateConstrainedScans set=optimizer_use_improved_computed_column_filters_derivation=false
SELECT a,b,c FROM t100206 WHERE (a,b) IN ((3,4), (5,6))
----
select
 ├── columns: a:1!null b:2!null c:3!null
 ├── cardinality: [0 - 2]
 ├── key: (1,2)
 ├── fd: (1,2)-->(3)
 ├── scan t100206
 │    ├── columns: a:1!null b:2!null c:3!null
 │    ├── computed column expressions
 │    │    └── c:3
 │    │         └── a:1 + b:2
 │    ├── key: (1,2)
 │    └── fd: (1,2)-->(3)
 └── filters
      └── (a:1, b:2) IN ((3, 4), (5, 6)) [outer=(1,2), constraints=(/1/2: [/3/4 - /3/4] [/5/6 - /5/6]; /2: [/4 - /4] [/6 - /6]; tight)]

# The presence of a non-single-key constraint should not prevent derivation
# of filters on computed columns.
opt expect=GenerateConstrainedScans
SELECT * FROM t100206 WHERE (a,b) IN ((3,4), (5,6)) AND a > 4
----
select
 ├── columns: a:1!null b:2!null c:3!null d:4
 ├── cardinality: [0 - 2]
 ├── key: (1,2)
 ├── fd: (1-3)-->(4), (1,2)-->(3)
 ├── scan t100206
 │    ├── columns: a:1!null b:2!null c:3!null d:4
 │    ├── constraint: /3/2/1
 │    │    ├── [/7/4/3 - /7/4/3]
 │    │    └── [/11/6/5 - /11/6/5]
 │    ├── cardinality: [0 - 2]
 │    ├── key: (1,2)
 │    └── fd: (1-3)-->(4), (1,2)-->(3)
 └── filters
      └── a:1 > 4 [outer=(1), constraints=(/1: [/5 - ]; tight)]

# This should derive the value of c from a and b.
opt expect=GenerateConstrainedScans
SELECT * FROM t100206 WHERE (a,b) IN ((3,4), (5,6)) AND d > 4
----
select
 ├── columns: a:1!null b:2!null c:3!null d:4!null
 ├── cardinality: [0 - 2]
 ├── key: (1,2)
 ├── fd: (1-3)-->(4), (1,2)-->(3)
 ├── scan t100206
 │    ├── columns: a:1!null b:2!null c:3!null d:4
 │    ├── constraint: /3/2/1
 │    │    ├── [/7/4/3 - /7/4/3]
 │    │    └── [/11/6/5 - /11/6/5]
 │    ├── cardinality: [0 - 2]
 │    ├── key: (1,2)
 │    └── fd: (1-3)-->(4), (1,2)-->(3)
 └── filters
      └── d:4 > 4 [outer=(4), constraints=(/4: [/5 - ]; tight)]

# Prepared statements should be able to derive constraints.
assign-placeholders-opt query-args=(1, 2, 3, 4)
SELECT a,b,c FROM t100206 WHERE (a,b) IN (($1,$2), ($3,$4))
----
scan t100206
 ├── columns: a:1!null b:2!null c:3!null
 ├── constraint: /3/2/1
 │    ├── [/3/2/1 - /3/2/1]
 │    └── [/7/4/3 - /7/4/3]
 ├── cardinality: [0 - 2]
 ├── key: (1,2)
 └── fd: (1,2)-->(3)

# This should not generate a constrained scan due to the presence of a null.
opt expect-not=GenerateConstrainedScans
SELECT a,b,c FROM t100206 WHERE (a,b) IN ((null,4), (5,6))
----
select
 ├── columns: a:1!null b:2!null c:3!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-3)
 ├── scan t100206
 │    ├── columns: a:1!null b:2!null c:3!null
 │    ├── computed column expressions
 │    │    └── c:3
 │    │         └── a:1 + b:2
 │    ├── key: (1,2)
 │    └── fd: (1,2)-->(3)
 └── filters
      └── (a:1, b:2) IN ((NULL, 4), (5, 6)) [outer=(1,2), constraints=(/1/2: [/5/6 - /5/6]; /2: [/4 - /4] [/6 - /6]), fd=()-->(1,2)]

exec-ddl
CREATE TABLE t100206n (
        a INT NOT NULL,
        b INT NOT NULL,
        c INT AS (CASE WHEN a = 5 THEN NULL ELSE b END) VIRTUAL,
        d INT,
        INDEX idx1 (c ASC, b ASC, a ASC));
----

# This should derive the value of c from a and b, including the null in c.
opt
SELECT a,b,c FROM t100206n@idx1 WHERE (a,b) IN ((3,4), (5,6))
----
project
 ├── columns: a:1!null b:2!null c:3
 ├── fd: (1,2)-->(3)
 ├── scan t100206n@idx1
 │    ├── columns: a:1!null b:2!null
 │    ├── constraint: /3/2/1/5
 │    │    ├── [/NULL/6/5 - /NULL/6/5]
 │    │    └── [/4/4/3 - /4/4/3]
 │    └── flags: force-index=idx1
 └── projections
      └── CASE WHEN a:1 = 5 THEN CAST(NULL AS INT8) ELSE b:2 END [as=c:3, outer=(1,2)]

exec-ddl
CREATE TABLE t100206nc (
        a INT NOT NULL,
        b INT NOT NULL,
        c INT AS (CASE WHEN a = 5 THEN CASE WHEN b = 6 THEN 1 ELSE d END ELSE b END) VIRTUAL,
        d INT,
        INDEX idx1 (c ASC, b ASC, a ASC));
----

# A nested CASE expression should derive the value of c from a and b.
opt expect=GenerateConstrainedScans
SELECT a,b,c FROM t100206nc@idx1 WHERE (a,b) IN ((3,4), (5,6))
----
project
 ├── columns: a:1!null b:2!null c:3
 ├── index-join t100206nc
 │    ├── columns: a:1!null b:2!null d:4
 │    └── scan t100206nc@idx1
 │         ├── columns: a:1!null b:2!null rowid:5!null
 │         ├── constraint: /3/2/1/5
 │         │    ├── [/1/6/5 - /1/6/5]
 │         │    └── [/4/4/3 - /4/4/3]
 │         ├── flags: force-index=idx1
 │         ├── key: (5)
 │         └── fd: (5)-->(1,2)
 └── projections
      └── CASE WHEN a:1 = 5 THEN CASE WHEN b:2 = 6 THEN 1 ELSE d:4 END ELSE b:2 END [as=c:3, outer=(1,2,4)]

# When `c` relies on the value of `d`, we should not derive filters.
opt expect-not=GenerateConstrainedScans
SELECT a,b,c FROM t100206nc@idx1 WHERE (a,b) IN ((5,7), (5,6))
----
project
 ├── columns: a:1!null b:2!null c:3
 ├── fd: ()-->(1)
 ├── select
 │    ├── columns: a:1!null b:2!null d:4
 │    ├── fd: ()-->(1)
 │    ├── index-join t100206nc
 │    │    ├── columns: a:1!null b:2!null d:4
 │    │    └── scan t100206nc@idx1
 │    │         ├── columns: a:1!null b:2!null rowid:5!null
 │    │         ├── flags: force-index=idx1
 │    │         ├── key: (5)
 │    │         └── fd: (5)-->(1,2)
 │    └── filters
 │         └── (a:1, b:2) IN ((5, 7), (5, 6)) [outer=(1,2), constraints=(/1/2: [/5/6 - /5/6] [/5/7 - /5/7]; /2: [/6 - /6] [/7 - /7]; tight), fd=()-->(1)]
 └── projections
      └── CASE WHEN a:1 = 5 THEN CASE WHEN b:2 = 6 THEN 1 ELSE d:4 END ELSE b:2 END [as=c:3, outer=(1,2,4)]

# When `d` is specified in a separate conjunct, we can't derive filters.
opt expect-not=GenerateConstrainedScans
SELECT a,b,c FROM t100206nc@idx1 WHERE (a,b) IN ((5,7), (5,6)) AND d IN (9,10)
----
project
 ├── columns: a:1!null b:2!null c:3!null
 ├── fd: ()-->(1)
 ├── select
 │    ├── columns: a:1!null b:2!null d:4!null
 │    ├── fd: ()-->(1)
 │    ├── index-join t100206nc
 │    │    ├── columns: a:1!null b:2!null d:4
 │    │    └── scan t100206nc@idx1
 │    │         ├── columns: a:1!null b:2!null rowid:5!null
 │    │         ├── flags: force-index=idx1
 │    │         ├── key: (5)
 │    │         └── fd: (5)-->(1,2)
 │    └── filters
 │         ├── (a:1, b:2) IN ((5, 7), (5, 6)) [outer=(1,2), constraints=(/1/2: [/5/6 - /5/6] [/5/7 - /5/7]; /2: [/6 - /6] [/7 - /7]; tight), fd=()-->(1)]
 │         └── d:4 IN (9, 10) [outer=(4), constraints=(/4: [/9 - /9] [/10 - /10]; tight)]
 └── projections
      └── CASE WHEN a:1 = 5 THEN CASE WHEN b:2 = 6 THEN 1 ELSE d:4 END ELSE b:2 END [as=c:3, outer=(1,2,4)]

# An IN clause which could derive terms will not work if OR'ed with another
# term.
opt expect-not=GenerateConstrainedScans
SELECT a,b,c FROM t100206nc@idx1 WHERE (a,b) IN ((3,4), (5,6)) OR d IN (9,10)
----
project
 ├── columns: a:1!null b:2!null c:3
 ├── select
 │    ├── columns: a:1!null b:2!null d:4
 │    ├── index-join t100206nc
 │    │    ├── columns: a:1!null b:2!null d:4
 │    │    └── scan t100206nc@idx1
 │    │         ├── columns: a:1!null b:2!null rowid:5!null
 │    │         ├── flags: force-index=idx1
 │    │         ├── key: (5)
 │    │         └── fd: (5)-->(1,2)
 │    └── filters
 │         └── ((a:1, b:2) IN ((3, 4), (5, 6))) OR (d:4 IN (9, 10)) [outer=(1,2,4)]
 └── projections
      └── CASE WHEN a:1 = 5 THEN CASE WHEN b:2 = 6 THEN 1 ELSE d:4 END ELSE b:2 END [as=c:3, outer=(1,2,4)]

# TODO(msirek): Derive predicates on computed columns using ORed predicates.
opt expect-not=GenerateConstrainedScans
SELECT * FROM t100206 WHERE (a = 3 AND b = 4) OR (a = 5 AND b = 6)
----
select
 ├── columns: a:1!null b:2!null c:3!null d:4
 ├── key: (1,2)
 ├── fd: (1-3)-->(4), (1,2)-->(3)
 ├── scan t100206
 │    ├── columns: a:1!null b:2!null c:3!null d:4
 │    ├── computed column expressions
 │    │    └── c:3
 │    │         └── a:1 + b:2
 │    ├── key: (1,2)
 │    └── fd: (1-3)-->(4), (1,2)-->(3)
 └── filters
      └── ((a:1 = 3) AND (b:2 = 4)) OR ((a:1 = 5) AND (b:2 = 6)) [outer=(1,2), constraints=(/1: [/3 - /3] [/5 - /5]; /2: [/4 - /4] [/6 - /6])]

# We should build a single constrained scan in the present of ORed terms.
opt expect=(GenerateConstrainedScans,SplitDisjunction)
SELECT * FROM t100206 WHERE (a,b) IN ((3,4), (5,6)) OR c=1
----
scan t100206
 ├── columns: a:1!null b:2!null c:3!null d:4
 ├── constraint: /3/2/1
 │    ├── [/1 - /1]
 │    ├── [/7/4/3 - /7/4/3]
 │    └── [/11/6/5 - /11/6/5]
 ├── key: (1,2)
 └── fd: (1-3)-->(4), (1,2)-->(3)

exec-ddl
CREATE TABLE t100206b (
        a INT NOT NULL,
        b INT NOT NULL,
        c INT NOT NULL AS (a+b) VIRTUAL,
        CHECK ((a,b) IN ((1,2),(3,4),(5,6))),
        CONSTRAINT pkey PRIMARY KEY (c ASC, b ASC, a ASC));
----

# This should derive the value of c from a and b.
opt expect=GenerateConstrainedScans
SELECT * FROM t100206b WHERE a = 1
----
select
 ├── columns: a:1!null b:2!null c:3!null
 ├── key: (2)
 ├── fd: ()-->(1), (2)-->(3)
 ├── scan t100206b
 │    ├── columns: a:1!null b:2!null c:3!null
 │    ├── constraint: /3/2/1
 │    │    ├── [/3/2/1 - /3/2/1]
 │    │    ├── [/7/4/3 - /7/4/3]
 │    │    └── [/11/6/5 - /11/6/5]
 │    ├── cardinality: [0 - 3]
 │    ├── key: (1,2)
 │    └── fd: (1,2)-->(3)
 └── filters
      └── a:1 = 1 [outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]

exec-ddl
CREATE TABLE t103755 (
        a INT NOT NULL,
        b INT NOT NULL,
        c INT NOT NULL AS (a+1) VIRTUAL,
        d INT,
        CONSTRAINT pkey PRIMARY KEY (c ASC, a ASC));
----

# The presence of an ORed inequality predicate should disqualify the derivation of the
# value of column `c`.
opt expect-not=GenerateConstrainedScans
SELECT * FROM t103755 WHERE a IN (VALUES(4)) OR a < 0
----
select
 ├── columns: a:1!null b:2!null c:3!null d:4
 ├── key: (1)
 ├── fd: (1,3)-->(2,4), (1)-->(3)
 ├── scan t103755
 │    ├── columns: a:1!null b:2!null c:3!null d:4
 │    ├── computed column expressions
 │    │    └── c:3
 │    │         └── a:1 + 1
 │    ├── key: (1)
 │    └── fd: (1,3)-->(2,4), (1)-->(3)
 └── filters
      └── (a:1 = 4) OR (a:1 < 0) [outer=(1), constraints=(/1: (/NULL - /-1] [/4 - /4]; tight)]

# The presence of an ORed equality predicate should allow derivation of the
# value of column `c`.
opt expect=GenerateConstrainedScans
SELECT * FROM t103755 WHERE a IN (VALUES(4)) OR a = 1
----
scan t103755
 ├── columns: a:1!null b:2!null c:3!null d:4
 ├── constraint: /3/1
 │    ├── [/2/1 - /2/1]
 │    └── [/5/4 - /5/4]
 ├── cardinality: [0 - 2]
 ├── key: (1)
 └── fd: (1,3)-->(2,4), (1)-->(3)

# The presence of an ORed inequality predicate should disqualify the derivation of the
# value of column `c`.
opt expect-not=GenerateConstrainedScans
SELECT * FROM t103755 WHERE a IN (VALUES(4)) OR b < 0
----
select
 ├── columns: a:1!null b:2!null c:3!null d:4
 ├── key: (1)
 ├── fd: (1,3)-->(2,4), (1)-->(3)
 ├── scan t103755
 │    ├── columns: a:1!null b:2!null c:3!null d:4
 │    ├── computed column expressions
 │    │    └── c:3
 │    │         └── a:1 + 1
 │    ├── key: (1)
 │    └── fd: (1,3)-->(2,4), (1)-->(3)
 └── filters
      └── (a:1 = 4) OR (b:2 < 0) [outer=(1,2)]

# Constrained partial index scan with an indexed virtual computed column also in
# the predicate.
# TODO(mgartner): Teach indexConstraintCtx.simplifyFilter to remove b=140 from
# the remaining filters after the index constraint is built. This will eliminate
# the unnecessary index-join and select expressions.
opt
SELECT k FROM virtual WHERE c = 150
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null b:3!null
      ├── key: (1)
      ├── fd: ()-->(3)
      ├── index-join virtual
      │    ├── columns: k:1!null b:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── scan virtual@virtual_c_idx,partial
      │         ├── columns: k:1!null
      │         ├── constraint: /4/1: [/150 - /150]
      │         └── key: (1)
      └── filters
           └── b:3 = 140 [outer=(3), constraints=(/3: [/140 - /140]; tight), fd=()-->(3)]

# Constrain a scan on a partial index on virtual column c constraining the
# dependent column b.
# TODO(mgartner): Teach indexConstraintCtx.simplifyFilter to remove b=140 from
# the remaining filters after the index constraint is built. This will eliminate
# the unnecessary index-join and select expressions.
opt
SELECT k FROM virtual WHERE b = 140
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null b:3!null
      ├── key: (1)
      ├── fd: ()-->(3)
      ├── index-join virtual
      │    ├── columns: k:1!null b:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── scan virtual@virtual_c_idx,partial
      │         ├── columns: k:1!null
      │         ├── constraint: /4/1: [/150 - /150]
      │         └── key: (1)
      └── filters
           └── b:3 = 140 [outer=(3), constraints=(/3: [/140 - /140]; tight), fd=()-->(3)]

# Regression test for #55387. GenerateConstrainedScans should not reduce the
# input filters when proving partial index implication.
exec-ddl
CREATE TABLE t55387 (
    k INT PRIMARY KEY,
    a INT,
    b INT,
    INDEX (a) WHERE a > 1,
    INDEX (b) WHERE b > 2
)
----

opt
SELECT k FROM t55387 WHERE a > 1 AND b > 3
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null a:2!null b:3!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── index-join t55387
      │    ├── columns: k:1!null a:2 b:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── select
      │         ├── columns: k:1!null b:3!null
      │         ├── key: (1)
      │         ├── fd: (1)-->(3)
      │         ├── scan t55387@t55387_b_idx,partial
      │         │    ├── columns: k:1!null b:3!null
      │         │    ├── key: (1)
      │         │    └── fd: (1)-->(3)
      │         └── filters
      │              └── b:3 > 3 [outer=(3), constraints=(/3: [/4 - ]; tight)]
      └── filters
           └── a:2 > 1 [outer=(2), constraints=(/2: [/2 - ]; tight)]

# Check that we can generate constraints by recognizing computed column
# expressions.
opt
SELECT k FROM computed WHERE lower(s) = 'foo'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan computed@l_idx
      ├── columns: k:1!null
      ├── constraint: /3/1: [/'foo' - /'foo']
      └── key: (1)

opt
SELECT k FROM computed WHERE (j->'foo')::string LIKE 'bar%'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan computed@fieldstr_idx
      ├── columns: k:1!null
      ├── constraint: /6/1: [/'bar' - /'bas')
      └── key: (1)

# Regression test for #81906. A constrained scan should be generated over a
# partial index with a predicate that is normalized to true. In this case, the
# predicate is normalized to true because the column is NOT NULL.
exec-ddl
CREATE TABLE t81906 (
  k INT PRIMARY KEY,
  a INT NOT NULL,
  INDEX (a) WHERE a IS NOT NULL
)
----

opt
SELECT k FROM t81906 WHERE a = 1
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── scan t81906@t81906_a_idx,partial
      ├── columns: k:1!null a:2!null
      ├── constraint: /2/1: [/1 - /1]
      ├── key: (1)
      └── fd: ()-->(2)

# Regression test for #77364. Queries with filter in the form
# <tuple> = ANY(ARRAY[<tuple>, ...]) should be index accelerated.
opt expect=GenerateConstrainedScans
SELECT * FROM e WHERE (u, w) = ANY(ARRAY[(1, 10), (2, 20), (3, 30)])
----
index-join e
 ├── columns: k:1!null u:2!null v:3 w:4!null
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── scan e@uw
      ├── columns: k:1!null u:2!null w:4!null
      ├── constraint: /2/4/1
      │    ├── [/1/10 - /1/10]
      │    ├── [/2/20 - /2/20]
      │    └── [/3/30 - /3/30]
      ├── key: (1)
      └── fd: (1)-->(2,4)

# Regression test for #86168. Selecting from a table with a partitioned
# primary and secondary index, where the primary index is partitioned by
# region and the secondary index is partitioned by another column, should
# not cause an error.
exec-ddl
CREATE TYPE region_enum AS ENUM ('AP_SOUTHEAST', 'CA_CENTRAL', 'US_EAST');
----

exec-ddl
CREATE TABLE "user" (
  region region_enum NOT NULL,
  id uuid NOT NULL DEFAULT uuid_generate_v4(),
  col1 int2 AS (col2::int2) VIRTUAL,
  col2 varchar NOT NULL,
  PRIMARY KEY (region, id),
  UNIQUE (col1, col2) PARTITION BY LIST (col1) (
    PARTITION user_col1_col2_key_ap_southeast VALUES IN (43,32),
    PARTITION user_col1_col2_key_ca_central VALUES IN (1),
    PARTITION DEFAULT VALUES IN (default)
  )
) PARTITION BY LIST (region) (
  PARTITION user_ap_southeast VALUES IN ('AP_SOUTHEAST'),
  PARTITION user_us_east VALUES IN ('US_EAST'),
  PARTITION user_ca_central VALUES IN ('CA_CENTRAL'),
  PARTITION DEFAULT VALUES IN (default)
);
----

exec-ddl
ALTER PARTITION user_ap_southeast OF TABLE "user" CONFIGURE ZONE USING
  num_replicas = 5,
  num_voters = 3,
  lease_preferences = '[[+region=ap-southeast-2]]',
  voter_constraints = '[+region=ap-southeast-2]';
----

opt locality=(region=ap-southeast-2)
SELECT *
FROM "user"
WHERE region = 'AP_SOUTHEAST';
----
project
 ├── columns: region:1!null id:2!null col1:3!null col2:4!null
 ├── immutable
 ├── key: (2)
 ├── fd: ()-->(1), (2)-->(4), (4)-->(3)
 ├── distribution: ap-southeast-2
 ├── scan user
 │    ├── columns: region:1!null id:2!null col2:4!null
 │    ├── constraint: /1/2: [/'AP_SOUTHEAST' - /'AP_SOUTHEAST']
 │    ├── immutable
 │    ├── key: (2)
 │    ├── fd: ()-->(1), (2)-->(4)
 │    └── distribution: ap-southeast-2
 └── projections
      └── col2:4::INT2 [as=col1:3, outer=(4), immutable]

# Regression test for #114250 - don't generate a trivial constrained scan that
# scans the whole table using the table's check constraints.
exec-ddl
CREATE TABLE t114250 (x INT, y INT, z INT NOT NULL, PRIMARY KEY (x, y), CHECK (x > 0 AND x < 5));
----

opt
SELECT x FROM t114250 WHERE z = 5;
----
project
 ├── columns: x:1!null
 └── select
      ├── columns: x:1!null z:3!null
      ├── fd: ()-->(3)
      ├── scan t114250
      │    ├── columns: x:1!null z:3!null
      │    └── check constraint expressions
      │         └── (x:1 > 0) AND (x:1 < 5) [outer=(1), constraints=(/1: [/1 - /4]; tight)]
      └── filters
           └── z:3 = 5 [outer=(3), constraints=(/3: [/5 - /5]; tight), fd=()-->(3)]

# Regression test for #114798. A constrained scan should be planned on boolean
# virtual computed columns.

exec-ddl
CREATE TABLE t114798 (
  k INT PRIMARY KEY,
  b BOOL,
  s BOOL AS (COALESCE(b, false)) STORED,
  v BOOL AS (COALESCE(b, true)) VIRTUAL,
  INDEX (s),
  INDEX (v)
)
----

opt expect=GenerateConstrainedScans
SELECT k FROM t114798 WHERE COALESCE(b, false)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── scan t114798@t114798_s_idx
      ├── columns: k:1!null
      ├── constraint: /3/1: [/true - /true]
      └── key: (1)

opt expect=GenerateConstrainedScans
SELECT k FROM t114798 WHERE COALESCE(b, true)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── scan t114798@t114798_v_idx
      ├── columns: k:1!null
      ├── constraint: /4/1: [/true - /true]
      └── key: (1)

# Regression test for #114470 - allow a non-selective constraint for singleton
# tables.
#
# This is a simplified version of an internal query performed on the
# system.span_count table, which is vulnerable to the small increase in latency
# caused by performing a KV Scan instead of a Get.
opt expect=GenerateConstrainedScans
SELECT * FROM singleton_check WHERE singleton;
----
scan singleton_check
 ├── columns: singleton:1!null count:2!null
 ├── constraint: /1: [/true - /true]
 ├── cardinality: [0 - 1]
 ├── key: ()
 └── fd: ()-->(1,2)

# Regression test for #125422. An equality between with two variables implies
# that either variable is not null, allowing a scan over a partial index with an
# IS NOT NULL predicate.
exec-ddl
CREATE TABLE t125422 (
  k INT PRIMARY KEY,
  a INT,
  b INT,
  INDEX t125422_a_idx (a) WHERE a IS NOT NULL
)
----

opt
SELECT k FROM t125422@t125422_a_idx WHERE a = b
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null a:2!null b:3!null
      ├── key: (1)
      ├── fd: (1)-->(2,3), (2)==(3), (3)==(2)
      ├── index-join t125422
      │    ├── columns: k:1!null a:2 b:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── scan t125422@t125422_a_idx,partial
      │         ├── columns: k:1!null a:2!null
      │         ├── flags: force-index=t125422_a_idx
      │         ├── key: (1)
      │         └── fd: (1)-->(2)
      └── filters
           └── a:2 = b:3 [outer=(2,3), constraints=(/2: (/NULL - ]; /3: (/NULL - ]), fd=(2)==(3), (3)==(2)]

# Regression test for #132669. Deriving computed column constraints should not
# cause the stack to grow excessively.
exec-ddl
CREATE TABLE t132669 (
  a INT,
  b INT NOT NULL AS (mod(a, 16)) VIRTUAL,
  INDEX (b, a)
)
----

# Optimize the query with a significantly reduced max stack size. This allows
# unnecessary recursion to trigger a stack overflow without having to make the
# `IN` list below huge - triggering a stack overflow with Go's default max stack
# size requires a list of ~1.6 million elements.
opt max-stack=100kB skip-race format=hide-all
SELECT * FROM t132669
WHERE a IN (
    1,  2,  3,  4,  5,  6,  7, 8, 9, 10,
   11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
   21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
   31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
   41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
   51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
   61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
   71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
   81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
   91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
  101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
  111, 112, 113, 114, 115, 116, 117, 118, 119, 120,
  121, 122, 123, 124, 125, 126, 127, 128, 129, 130,
  131, 132, 133, 134, 135, 136, 137, 138, 139, 140,
  141, 142, 143, 144, 145, 146, 147, 148, 149, 150,
  151, 152, 153, 154, 155, 156, 157, 158, 159, 160,
  161, 162, 163, 164, 165, 166, 167, 168, 169, 170,
  171, 172, 173, 174, 175, 176, 177, 178, 179, 180,
  181, 182, 183, 184, 185, 186, 187, 188, 189, 190,
  191, 192, 193, 194, 195, 196, 197, 198, 199, 200
)
----
project
 ├── select
 │    ├── scan t132669
 │    │    └── computed column expressions
 │    │         └── b
 │    │              └── mod(a, 16)
 │    └── filters
 │         └── a IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200)
 └── projections
      └── mod(a, 16)

# --------------------------------------------------
# GenerateInvertedIndexScans
# --------------------------------------------------

# Query only the primary key with no remaining filter.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j @> '{"a": "b"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

memo expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j @> '{"a": "b"}'
----
memo (optimized, ~9KB, required=[presentation: k:1])
 ├── G1: (project G2 G3 k) (project G4 G3 k)
 │    └── [presentation: k:1]
 │         ├── best: (project G4 G3 k)
 │         └── cost: 133.58
 ├── G2: (select G5 G6) (index-join G4 b,cols=(1,4))
 │    └── []
 │         ├── best: (index-join G4 b,cols=(1,4))
 │         └── cost: 803.53
 ├── G3: (projections)
 ├── G4: (scan b@j_inv_idx,inverted,cols=(1),constrained inverted)
 │    └── []
 │         ├── best: (scan b@j_inv_idx,inverted,cols=(1),constrained inverted)
 │         └── cost: 132.46
 ├── G5: (scan b,cols=(1,4))
 │    └── []
 │         ├── best: (scan b,cols=(1,4))
 │         └── cost: 1088.62
 ├── G6: (filters G7)
 ├── G7: (contains G8 G9)
 ├── G8: (variable j)
 └── G9: (const '{"a": "b"}')

# GenerateInvertedIndexScans will be triggered, but not add an index
# scan to the memo since NO_INDEX_JOIN is specified.
memo expect-not=GenerateInvertedIndexScans
SELECT k FROM b@{NO_INDEX_JOIN} WHERE j @> '{"a": "b"}'
----
memo (optimized, ~7KB, required=[presentation: k:1])
 ├── G1: (project G2 G3 k)
 │    └── [presentation: k:1]
 │         ├── best: (project G2 G3 k)
 │         └── cost: 1099.77
 ├── G2: (select G4 G5)
 │    └── []
 │         ├── best: (select G4 G5)
 │         └── cost: 1098.65
 ├── G3: (projections)
 ├── G4: (scan b,cols=(1,4))
 │    └── []
 │         ├── best: (scan b,cols=(1,4))
 │         └── cost: 1088.62
 ├── G5: (filters G6)
 ├── G6: (contains G7 G8)
 ├── G7: (variable j)
 └── G8: (const '{"a": "b"}')

# Query requiring an index join with no remaining filter.
opt expect=GenerateInvertedIndexScans
SELECT u, k FROM b WHERE j @> '{"a": "b"}'
----
project
 ├── columns: u:2 k:1!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2)
 └── index-join b
      ├── columns: k:1!null u:2 j:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2,4)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null
           ├── inverted constraint: /7/1
           │    └── spans: ["a"/"b", "a"/"b"]
           └── key: (1)

opt expect=GenerateInvertedIndexScans
SELECT j, k FROM b WHERE j @> '{"a": "b"}'
----
index-join b
 ├── columns: j:4!null k:1!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(4)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j @> '{"a": "b"}'
----
index-join b
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j @> '2'
----
index-join b
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      └── inverted constraint: /7/1
           └── spans
                ├── [2, 2]
                └── [Arr/2, Arr/2]

# Disjunction.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j @> '2' OR j @> '1'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── [1, 1]
      │         ├── [2, 2]
      │         ├── [Arr/1, Arr/1]
      │         └── [Arr/2, Arr/2]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── [1, 1]
                     ├── [2, 2]
                     ├── [Arr/1, Arr/1]
                     └── [Arr/2, Arr/2]

# Disjunction with non-tight predicate.
opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j @> '[[1, 2]]' OR j @> '[[3, 4]]'
----
select
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: true
 │         │    ├── union spans: empty
 │         │    └── UNION
 │         │         ├── span expression
 │         │         │    ├── tight: false, unique: true
 │         │         │    ├── union spans: empty
 │         │         │    └── INTERSECTION
 │         │         │         ├── span expression
 │         │         │         │    ├── tight: true, unique: true
 │         │         │         │    └── union spans: [Arr/Arr/1, Arr/Arr/1]
 │         │         │         └── span expression
 │         │         │              ├── tight: true, unique: true
 │         │         │              └── union spans: [Arr/Arr/2, Arr/Arr/2]
 │         │         └── span expression
 │         │              ├── tight: false, unique: true
 │         │              ├── union spans: empty
 │         │              └── INTERSECTION
 │         │                   ├── span expression
 │         │                   │    ├── tight: true, unique: true
 │         │                   │    └── union spans: [Arr/Arr/3, Arr/Arr/3]
 │         │                   └── span expression
 │         │                        ├── tight: true, unique: true
 │         │                        └── union spans: [Arr/Arr/4, Arr/Arr/4]
 │         ├── key: (1)
 │         └── scan b@j_inv_idx,inverted
 │              ├── columns: k:1!null j_inverted_key:7!null
 │              └── inverted constraint: /7/1
 │                   └── spans
 │                        ├── [Arr/Arr/1, Arr/Arr/1]
 │                        ├── [Arr/Arr/2, Arr/Arr/2]
 │                        ├── [Arr/Arr/3, Arr/Arr/3]
 │                        └── [Arr/Arr/4, Arr/Arr/4]
 └── filters
      └── (j:4 @> '[[1, 2]]') OR (j:4 @> '[[3, 4]]') [outer=(4), immutable, constraints=(/4: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j @> '[{}]'
----
index-join b
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── [Arr/{}, Arr/{}]
      │         └── [Arr/, Arr//PrefixEnd)
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── [Arr/{}, Arr/{}]
                     └── [Arr/, Arr//PrefixEnd)

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j @> '{"a": {}}'
----
index-join b
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/{}, "a"/{}]
      │         └── ["a"/, Arr/)
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/{}, "a"/{}]
                     └── ["a"/, Arr/)

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j @> '{"a": []}'
----
index-join b
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/[], "a"/[]]
      │         └── ["a"/Arr/, "a"/Arr/]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/[], "a"/[]]
                     └── ["a"/Arr/, "a"/Arr/]

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j @> '{"a":[[{"b":{"c":[{"d":"e"}]}}]]}'
----
index-join b
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/Arr/Arr/"b"/"c"/Arr/"d"/"e", "a"/Arr/Arr/"b"/"c"/Arr/"d"/"e"]
      └── key: (1)

# Query using the contained by operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j <@ '1'
----
select
 ├── columns: k:1!null u:2 v:3 j:4
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── scan b@j_inv_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /7/1
 │         │    └── spans: [1, 1]
 │         └── key: (1)
 └── filters
      └── j:4 <@ '1' [outer=(4), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j <@ '{}'
----
select
 ├── columns: k:1!null u:2 v:3 j:4
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── scan b@j_inv_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /7/1
 │         │    └── spans: [{}, {}]
 │         └── key: (1)
 └── filters
      └── j:4 <@ '{}' [outer=(4), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j <@ '[]'
----
select
 ├── columns: k:1!null u:2 v:3 j:4
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── scan b@j_inv_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /7/1
 │         │    └── spans: [[], []]
 │         └── key: (1)
 └── filters
      └── j:4 <@ '[]' [outer=(4), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j <@ '{"a": []}'
----
select
 ├── columns: k:1!null u:2 v:3 j:4
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── [{}, {}]
 │         │         └── ["a"/[], "a"/[]]
 │         ├── key: (1)
 │         └── scan b@j_inv_idx,inverted
 │              ├── columns: k:1!null j_inverted_key:7!null
 │              └── inverted constraint: /7/1
 │                   └── spans
 │                        ├── [{}, {}]
 │                        └── ["a"/[], "a"/[]]
 └── filters
      └── j:4 <@ '{"a": []}' [outer=(4), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j <@ '[{"a": {"d": true}}, 1, "b"]'
----
select
 ├── columns: k:1!null u:2 v:3 j:4
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["b", "b"]
 │         │         ├── [1, 1]
 │         │         ├── [[], []]
 │         │         ├── [Arr/"b", Arr/"b"]
 │         │         ├── [Arr/1, Arr/1]
 │         │         ├── [Arr/{}, Arr/{}]
 │         │         ├── [Arr/"a"/{}, Arr/"a"/{}]
 │         │         └── [Arr/"a"/"d"/True, Arr/"a"/"d"/True]
 │         ├── key: (1)
 │         └── scan b@j_inv_idx,inverted
 │              ├── columns: k:1!null j_inverted_key:7!null
 │              └── inverted constraint: /7/1
 │                   └── spans
 │                        ├── ["b", "b"]
 │                        ├── [1, 1]
 │                        ├── [[], []]
 │                        ├── [Arr/"b", Arr/"b"]
 │                        ├── [Arr/1, Arr/1]
 │                        ├── [Arr/{}, Arr/{}]
 │                        ├── [Arr/"a"/{}, Arr/"a"/{}]
 │                        └── [Arr/"a"/"d"/True, Arr/"a"/"d"/True]
 └── filters
      └── j:4 <@ '[{"a": {"d": true}}, 1, "b"]' [outer=(4), immutable]

# Disjunction using the contained by operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j <@ '{"a": [3]}' OR j <@ '[1, 2, 3]'
----
select
 ├── columns: k:1!null u:2 v:3 j:4
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── [1, 1]
 │         │         ├── [2, 2]
 │         │         ├── [3, 3]
 │         │         ├── [[], {}/PrefixEnd)
 │         │         ├── [Arr/1, Arr/1]
 │         │         ├── [Arr/2, Arr/2]
 │         │         ├── [Arr/3, Arr/3]
 │         │         ├── ["a"/[], "a"/[]]
 │         │         └── ["a"/Arr/3, "a"/Arr/3]
 │         ├── key: (1)
 │         └── scan b@j_inv_idx,inverted
 │              ├── columns: k:1!null j_inverted_key:7!null
 │              └── inverted constraint: /7/1
 │                   └── spans
 │                        ├── [1, 1]
 │                        ├── [2, 2]
 │                        ├── [3, 3]
 │                        ├── [[], {}/PrefixEnd)
 │                        ├── [Arr/1, Arr/1]
 │                        ├── [Arr/2, Arr/2]
 │                        ├── [Arr/3, Arr/3]
 │                        ├── ["a"/[], "a"/[]]
 │                        └── ["a"/Arr/3, "a"/Arr/3]
 └── filters
      └── (j:4 <@ '{"a": [3]}') OR (j:4 <@ '[1, 2, 3]') [outer=(4), immutable]

# Conjunction using the contained by operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j <@ '{"a": [3]}' AND j <@ '[1, 2, 3]'
----
select
 ├── columns: k:1!null u:2 v:3 j:4
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    ├── union spans: empty
 │         │    └── INTERSECTION
 │         │         ├── span expression
 │         │         │    ├── tight: false, unique: false
 │         │         │    └── union spans
 │         │         │         ├── [{}, {}]
 │         │         │         ├── ["a"/[], "a"/[]]
 │         │         │         └── ["a"/Arr/3, "a"/Arr/3]
 │         │         └── span expression
 │         │              ├── tight: false, unique: false
 │         │              └── union spans
 │         │                   ├── [1, 1]
 │         │                   ├── [2, 2]
 │         │                   ├── [3, 3]
 │         │                   ├── [[], []]
 │         │                   ├── [Arr/1, Arr/1]
 │         │                   ├── [Arr/2, Arr/2]
 │         │                   └── [Arr/3, Arr/3]
 │         ├── key: (1)
 │         └── scan b@j_inv_idx,inverted
 │              ├── columns: k:1!null j_inverted_key:7!null
 │              └── inverted constraint: /7/1
 │                   └── spans
 │                        ├── [1, 1]
 │                        ├── [2, 2]
 │                        ├── [3, 3]
 │                        ├── [[], {}/PrefixEnd)
 │                        ├── [Arr/1, Arr/1]
 │                        ├── [Arr/2, Arr/2]
 │                        ├── [Arr/3, Arr/3]
 │                        ├── ["a"/[], "a"/[]]
 │                        └── ["a"/Arr/3, "a"/Arr/3]
 └── filters
      ├── j:4 <@ '{"a": [3]}' [outer=(4), immutable]
      └── j:4 <@ '[1, 2, 3]' [outer=(4), immutable]

# Testing the equality operator, without the fetch val operator,
# on an inverted index. In all such cases, the inverted expression
# generated is not tight resulting in the original filter to be
# applied again.

# Testing equality for a JSON null on an inverted index.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j = 'null'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         └── inverted constraint: /7/1
      │              └── spans
      │                   ├── [NULL, NULL]
      │                   └── [Arr/NULL, Arr/NULL]
      └── filters
           └── j:4 = 'null' [outer=(4), immutable, constraints=(/4: [/'null' - /'null']; tight), fd=()-->(4)]

# Testing equality for a JSON string on an inverted index.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j = '"b"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         └── inverted constraint: /7/1
      │              └── spans
      │                   ├── ["b", "b"]
      │                   └── [Arr/"b", Arr/"b"]
      └── filters
           └── j:4 = '"b"' [outer=(4), immutable, constraints=(/4: [/'"b"' - /'"b"']; tight), fd=()-->(4)]

# Testing equality for a JSON number on an inverted index.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j = '1'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         └── inverted constraint: /7/1
      │              └── spans
      │                   ├── [1, 1]
      │                   └── [Arr/1, Arr/1]
      └── filters
           └── j:4 = '1' [outer=(4), immutable, constraints=(/4: [/'1' - /'1']; tight), fd=()-->(4)]

# Testing equality for a JSON empty string on an inverted index.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j = '""'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         └── inverted constraint: /7/1
      │              └── spans
      │                   ├── ["", ""]
      │                   └── [Arr/"", Arr/""]
      └── filters
           └── j:4 = '""' [outer=(4), immutable, constraints=(/4: [/'""' - /'""']; tight), fd=()-->(4)]

# Testing equality for a JSON array on an inverted index.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j = '[1, 2, 3]'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup b)
      ├── columns: k:1!null j:4!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [7] = ['\x37000300012a0200']
      │    ├── right fixed columns: [7] = ['\x37000300012a0400']
      │    └── filters (true)
      └── filters
           └── j:4 = '[1, 2, 3]' [outer=(4), immutable, constraints=(/4: [/'[1, 2, 3]' - /'[1, 2, 3]']; tight), fd=()-->(4)]


# Testing equality for a JSON empty array on an inverted index.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j = '[]'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [[], []]
      │         │         └── [Arr/, Arr/]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [[], []]
      │                        └── [Arr/, Arr/]
      └── filters
           └── j:4 = '[]' [outer=(4), immutable, constraints=(/4: [/'[]' - /'[]']; tight), fd=()-->(4)]

# Testing equality for a JSON empty object on an inverted index.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j = '{}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [{}, {}]
      │         │         └── ["", [])
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [{}, {}]
      │                        └── ["", [])
      └── filters
           └── j:4 = '{}' [outer=(4), immutable, constraints=(/4: [/'{}' - /'{}']; tight), fd=()-->(4)]

# Testing equality for a JSON object on an inverted index.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j = '{"a": "b", "c": "d"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup b)
      ├── columns: k:1!null j:4!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [7] = ['\x3761000112620001']
      │    ├── right fixed columns: [7] = ['\x3763000112640001']
      │    └── filters (true)
      └── filters
           └── j:4 = '{"a": "b", "c": "d"}' [outer=(4), immutable, constraints=(/4: [/'{"a": "b", "c": "d"}' - /'{"a": "b", "c": "d"}']; tight), fd=()-->(4)]

# Query using the fetch val and equality operators.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' = '"b"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

opt expect=ConvertJSONSubscriptToFetchValue
SELECT k FROM b WHERE j['a'] = '"b"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

# Chained fetch val operators.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a'->'b' = '"c"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/"b"/"c", "a"/"b"/"c"]
      └── key: (1)

# Do not generate an inverted scan when the fetch val and equality operators are
# wrapped in a NOT operator. The -> operator returns NULL if the key does not
# exist in the JSON object, so (NOT j->'a' = '"b"') is not equivalent to the
# inverse of the existence of the key/value pair {"a": "b"} in the inverted
# index. See #49143 and #55316.
opt expect-not=GenerateInvertedIndexScans
SELECT k FROM b WHERE NOT j->'a' = '"b"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── scan b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    └── fd: (1)-->(4)
      └── filters
           └── (j:4->'a') != '"b"' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator is
# an integer and right side to the equality is a JSON string. Since
# the inverted expression is not tight, the original filter should be
# applied.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 = '"b"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         ├── inverted constraint: /7/1
      │         │    └── spans: [Arr/"b", Arr/"b"]
      │         └── key: (1)
      └── filters
           └── (j:4->0) = '"b"' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator is
# integer and the right side to the equality is a JSON object.
# Since the inverted expression is not tight, a filter should be applied.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 = '{"a": "b"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         ├── inverted constraint: /7/1
      │         │    └── spans: [Arr/"a"/"b", Arr/"a"/"b"]
      │         └── key: (1)
      └── filters
           └── (j:4->0) = '{"a": "b"}' [outer=(4), immutable]

# Generate an inverted scan when right side of the equality is an array.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' = '["b"]'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         ├── inverted constraint: /7/1
      │         │    └── spans: ["a"/Arr/"b", "a"/Arr/"b"]
      │         └── key: (1)
      └── filters
           └── (j:4->'a') = '["b"]' [outer=(4), immutable]

# Generate an inverted scan when right side of the equality is an object.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' = '{"b": "c"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         ├── inverted constraint: /7/1
      │         │    └── spans: ["a"/"b"/"c", "a"/"b"/"c"]
      │         └── key: (1)
      └── filters
           └── (j:4->'a') = '{"b": "c"}' [outer=(4), immutable]


# Generate an inverted scan when the index of the fetch val operator is
# an integer and the right side to the equality is a JSON
# array. Since the inverted expression is not tight, a
# filter should be applied.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 = '[1, 2]'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup b)
      ├── columns: k:1!null j:4
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [7] = ['\x370003000300012a0200']
      │    ├── right fixed columns: [7] = ['\x370003000300012a0400']
      │    └── filters (true)
      └── filters
           └── (j:4->0) = '[1, 2]' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator
# is an integer and there is a containment operator with a JSON object.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 @> '{"b": "c"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         ├── inverted constraint: /7/1
      │         │    └── spans: [Arr/"b"/"c", Arr/"b"/"c"]
      │         └── key: (1)
      └── filters
           └── (j:4->0) @> '{"b": "c"}' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator
# is an integer and there is a containment operator with a JSON object.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 <@ '{"b": "c"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [[], []]
      │         │         ├── [Arr/{}, Arr/{}]
      │         │         └── [Arr/"b"/"c", Arr/"b"/"c"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [[], []]
      │                        ├── [Arr/{}, Arr/{}]
      │                        └── [Arr/"b"/"c", Arr/"b"/"c"]
      └── filters
           └── (j:4->0) <@ '{"b": "c"}' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator
# is an integer and there is a containment operator with a JSON array.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 <@ '[1, 2]'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [1, 1]
      │         │         ├── [2, 2]
      │         │         ├── [[], []]
      │         │         ├── [Arr/1, Arr/1]
      │         │         ├── [Arr/2, Arr/2]
      │         │         ├── [Arr/[], Arr/[]]
      │         │         ├── [Arr/Arr/1, Arr/Arr/1]
      │         │         └── [Arr/Arr/2, Arr/Arr/2]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [1, 1]
      │                        ├── [2, 2]
      │                        ├── [[], []]
      │                        ├── [Arr/1, Arr/1]
      │                        ├── [Arr/2, Arr/2]
      │                        ├── [Arr/[], Arr/[]]
      │                        ├── [Arr/Arr/1, Arr/Arr/1]
      │                        └── [Arr/Arr/2, Arr/Arr/2]
      └── filters
           └── (j:4->0) <@ '[1, 2]' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator
# is an integer and there is a containment operator with a JSON array.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 @> '[1, 2]'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup b)
      ├── columns: k:1!null j:4
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [7] = ['\x370003000300012a0200']
      │    ├── right fixed columns: [7] = ['\x370003000300012a0400']
      │    └── filters (true)
      └── filters
           └── (j:4->0) @> '[1, 2]' [outer=(4), immutable]


# Generate an inverted scan when the index of the fetch val operator
# is an integer and there is a containment operator with a JSON string.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 @> '"a"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [Arr/"a", Arr/"a"]
      │         │         └── [Arr/Arr/"a", Arr/Arr/"a"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [Arr/"a", Arr/"a"]
      │                        └── [Arr/Arr/"a", Arr/Arr/"a"]
      └── filters
           └── (j:4->0) @> '"a"' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator
# is an integer and there is a containment operator with a JSON string.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 <@ '"a"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["a", "a"]
      │         │         ├── [[], []]
      │         │         └── [Arr/"a", Arr/"a"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["a", "a"]
      │                        ├── [[], []]
      │                        └── [Arr/"a", Arr/"a"]
      └── filters
           └── (j:4->0) <@ '"a"' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator
# is an integer and there is a containment operator with a JSON integer.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 @> '1'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [Arr/1, Arr/1]
      │         │         └── [Arr/Arr/1, Arr/Arr/1]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [Arr/1, Arr/1]
      │                        └── [Arr/Arr/1, Arr/Arr/1]
      └── filters
           └── (j:4->0) @> '1' [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator
# is an integer and there is a containment operator with a JSON integer.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 <@ '1'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [1, 1]
      │         │         ├── [[], []]
      │         │         └── [Arr/1, Arr/1]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [1, 1]
      │                        ├── [[], []]
      │                        └── [Arr/1, Arr/1]
      └── filters
           └── (j:4->0) <@ '1' [outer=(4), immutable]

# Query using the fetch val and equality operators in a conjunction.
opt expect=GenerateInvertedIndexScans disable=GenerateInvertedIndexZigzagJoins
SELECT k FROM b WHERE j->'a' = '"b"' AND j->'c' = '"d"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: true
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: true
      │         │    └── union spans: ["a"/"b", "a"/"b"]
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: ["c"/"d", "c"/"d"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"b", "a"/"b"]
                     └── ["c"/"d", "c"/"d"]

# Query using the fetch val and equality operators in a disjunction.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' = '"b"' OR j->'c' = '"d"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/"b", "a"/"b"]
      │         └── ["c"/"d", "c"/"d"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"b", "a"/"b"]
                     └── ["c"/"d", "c"/"d"]

# Generate an inverted scan when the index of the fetch val operator is
# a string along with the IN operator consisting of JSON strings in
# the tuple. Since the inverted expression is tight,
# the original filter should not be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' IN ('1', '2', '3')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/1, "a"/1]
      │         ├── ["a"/2, "a"/2]
      │         └── ["a"/3, "a"/3]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/1, "a"/1]
                     ├── ["a"/2, "a"/2]
                     └── ["a"/3, "a"/3]


# Testing the IN operator, without the fetch val operator,
# on an inverted index.

# Multiple JSON numbers contained within the IN operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j IN ('1', '2', '3')
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [1, 1]
      │         │         ├── [2, 2]
      │         │         ├── [3, 3]
      │         │         ├── [Arr/1, Arr/1]
      │         │         ├── [Arr/2, Arr/2]
      │         │         └── [Arr/3, Arr/3]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [1, 1]
      │                        ├── [2, 2]
      │                        ├── [3, 3]
      │                        ├── [Arr/1, Arr/1]
      │                        ├── [Arr/2, Arr/2]
      │                        └── [Arr/3, Arr/3]
      └── filters
           └── j:4 IN ('1', '2', '3') [outer=(4), constraints=(/4: [/'1' - /'1'] [/'2' - /'2'] [/'3' - /'3']; tight)]


# Multiple JSON strings contained within the IN operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j IN ('"a"', '"b"', '"c"')
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["a", "a"]
      │         │         ├── ["b", "b"]
      │         │         ├── ["c", "c"]
      │         │         ├── [Arr/"a", Arr/"a"]
      │         │         ├── [Arr/"b", Arr/"b"]
      │         │         └── [Arr/"c", Arr/"c"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["a", "a"]
      │                        ├── ["b", "b"]
      │                        ├── ["c", "c"]
      │                        ├── [Arr/"a", Arr/"a"]
      │                        ├── [Arr/"b", Arr/"b"]
      │                        └── [Arr/"c", Arr/"c"]
      └── filters
           └── j:4 IN ('"a"', '"b"', '"c"') [outer=(4), constraints=(/4: [/'"a"' - /'"a"'] [/'"b"' - /'"b"'] [/'"c"' - /'"c"']; tight)]


# Multiple JSON arrays contained within the IN operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j IN ('[1, 2, 3]', '[1, 2]', '[1]', '[]')
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    ├── union spans
      │         │    │    ├── [[], []]
      │         │    │    └── [Arr/, Arr/]
      │         │    └── UNION
      │         │         ├── span expression
      │         │         │    ├── tight: false, unique: false
      │         │         │    ├── union spans: empty
      │         │         │    └── INTERSECTION
      │         │         │         ├── span expression
      │         │         │         │    ├── tight: true, unique: true
      │         │         │         │    └── union spans: [Arr/1, Arr/1]
      │         │         │         └── span expression
      │         │         │              ├── tight: true, unique: true
      │         │         │              └── union spans: [Arr/2, Arr/2]
      │         │         └── span expression
      │         │              ├── tight: false, unique: true
      │         │              ├── union spans: empty
      │         │              └── INTERSECTION
      │         │                   ├── span expression
      │         │                   │    ├── tight: true, unique: true
      │         │                   │    ├── union spans: empty
      │         │                   │    └── INTERSECTION
      │         │                   │         ├── span expression
      │         │                   │         │    ├── tight: true, unique: true
      │         │                   │         │    └── union spans: [Arr/1, Arr/1]
      │         │                   │         └── span expression
      │         │                   │              ├── tight: true, unique: true
      │         │                   │              └── union spans: [Arr/2, Arr/2]
      │         │                   └── span expression
      │         │                        ├── tight: true, unique: true
      │         │                        └── union spans: [Arr/3, Arr/3]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [[], []]
      │                        └── [Arr/, Arr/]
      └── filters
           └── j:4 IN ('[]', '[1]', '[1, 2]', '[1, 2, 3]') [outer=(4), constraints=(/4: [/'[]' - /'[]'] [/'[1]' - /'[1]'] [/'[1, 2]' - /'[1, 2]'] [/'[1, 2, 3]' - /'[1, 2, 3]']; tight)]

# Multiple JSON objects contained within the IN operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j IN ('{"a": "b", "c": "d"}', '{}', '{"a": [1, 2, 3]}')
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    ├── union spans
      │         │    │    ├── [{}, {}]
      │         │    │    └── ["", [])
      │         │    └── UNION
      │         │         ├── span expression
      │         │         │    ├── tight: false, unique: false
      │         │         │    ├── union spans: empty
      │         │         │    └── INTERSECTION
      │         │         │         ├── span expression
      │         │         │         │    ├── tight: true, unique: true
      │         │         │         │    ├── union spans: empty
      │         │         │         │    └── INTERSECTION
      │         │         │         │         ├── span expression
      │         │         │         │         │    ├── tight: true, unique: true
      │         │         │         │         │    └── union spans: ["a"/Arr/1, "a"/Arr/1]
      │         │         │         │         └── span expression
      │         │         │         │              ├── tight: true, unique: true
      │         │         │         │              └── union spans: ["a"/Arr/2, "a"/Arr/2]
      │         │         │         └── span expression
      │         │         │              ├── tight: true, unique: true
      │         │         │              └── union spans: ["a"/Arr/3, "a"/Arr/3]
      │         │         └── span expression
      │         │              ├── tight: false, unique: true
      │         │              ├── union spans: empty
      │         │              └── INTERSECTION
      │         │                   ├── span expression
      │         │                   │    ├── tight: true, unique: true
      │         │                   │    └── union spans: ["a"/"b", "a"/"b"]
      │         │                   └── span expression
      │         │                        ├── tight: true, unique: true
      │         │                        └── union spans: ["c"/"d", "c"/"d"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [{}, {}]
      │                        └── ["", [])
      └── filters
           └── j:4 IN ('{}', '{"a": [1, 2, 3]}', '{"a": "b", "c": "d"}') [outer=(4), constraints=(/4: [/'{}' - /'{}'] [/'{"a": [1, 2, 3]}' - /'{"a": [1, 2, 3]}'] [/'{"a": "b", "c": "d"}' - /'{"a": "b", "c": "d"}']; tight)]

# Combination of JSON values contained within the IN operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j IN ('{"a": "b", "c": "d"}', '{}', 'null', '1', '"a"')
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    ├── union spans
      │         │    │    ├── [NULL, NULL]
      │         │    │    ├── ["a", "a"]
      │         │    │    ├── [1, 1]
      │         │    │    ├── [{}, {}]
      │         │    │    ├── [Arr/NULL, Arr/NULL]
      │         │    │    ├── [Arr/"a", Arr/"a"]
      │         │    │    ├── [Arr/1, Arr/1]
      │         │    │    └── ["", [])
      │         │    └── INTERSECTION
      │         │         ├── span expression
      │         │         │    ├── tight: true, unique: true
      │         │         │    └── union spans: ["a"/"b", "a"/"b"]
      │         │         └── span expression
      │         │              ├── tight: true, unique: true
      │         │              └── union spans: ["c"/"d", "c"/"d"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [NULL, NULL]
      │                        ├── ["a", "a"]
      │                        ├── [1, 1]
      │                        ├── [{}, {}]
      │                        ├── [Arr/NULL, Arr/NULL]
      │                        ├── [Arr/"a", Arr/"a"]
      │                        ├── [Arr/1, Arr/1]
      │                        └── ["", [])
      └── filters
           └── j:4 IN ('null', '"a"', '1', '{}', '{"a": "b", "c": "d"}') [outer=(4), constraints=(/4: [/'null' - /'null'] [/'"a"' - /'"a"'] [/'1' - /'1'] [/'{}' - /'{}'] [/'{"a": "b", "c": "d"}' - /'{"a": "b", "c": "d"}']; tight)]

# Generate an inverted scan when the index of the fetch val operator is
# a string along with the IN operator consisting of JSON strings in
# the tuple. Since the inverted expression is tight,
# the original filter should not be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' IN ('"a"', '"b"')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/"a", "a"/"a"]
      │         └── ["a"/"b", "a"/"b"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"a", "a"/"a"]
                     └── ["a"/"b", "a"/"b"]


# Generate an inverted scan when the index of the fetch val operator is
# a string along with the IN operator consisting of JSON objects in
# the tuple. Since the inverted expression is not tight,
# the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' IN ('{"a": "b"}', '{"c": "d"}')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["a"/"a"/"b", "a"/"a"/"b"]
      │         │         └── ["a"/"c"/"d", "a"/"c"/"d"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["a"/"a"/"b", "a"/"a"/"b"]
      │                        └── ["a"/"c"/"d", "a"/"c"/"d"]
      └── filters
           └── (j:4->'a') IN ('{"a": "b"}', '{"c": "d"}') [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator is
# a string along with the IN operator consisting of a combination of JSON objects,
# JSON strings and JSON integers inside the tuple. Since the inverted expression generated
# is not tight, the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' IN ('{"a": "b"}', '1', '"a"', 'null')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["a"/NULL, "a"/NULL]
      │         │         ├── ["a"/"a", "a"/"a"]
      │         │         ├── ["a"/1, "a"/1]
      │         │         └── ["a"/"a"/"b", "a"/"a"/"b"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["a"/NULL, "a"/NULL]
      │                        ├── ["a"/"a", "a"/"a"]
      │                        ├── ["a"/1, "a"/1]
      │                        └── ["a"/"a"/"b", "a"/"a"/"b"]
      └── filters
           └── (j:4->'a') IN ('null', '"a"', '1', '{"a": "b"}') [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator is
# a string along with the IN operator consisting of a combination of JSON objects,
# JSON strings,JSON integers and JSON arrays inside the tuple. Since the inverted expression
# generated is not tight, the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' IN ('{"a": "b"}', '1', '"a"', 'null', '[1,2,3]')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    ├── union spans
      │         │    │    ├── ["a"/NULL, "a"/NULL]
      │         │    │    ├── ["a"/"a", "a"/"a"]
      │         │    │    ├── ["a"/1, "a"/1]
      │         │    │    └── ["a"/"a"/"b", "a"/"a"/"b"]
      │         │    └── INTERSECTION
      │         │         ├── span expression
      │         │         │    ├── tight: true, unique: true
      │         │         │    ├── union spans: empty
      │         │         │    └── INTERSECTION
      │         │         │         ├── span expression
      │         │         │         │    ├── tight: true, unique: true
      │         │         │         │    └── union spans: ["a"/Arr/1, "a"/Arr/1]
      │         │         │         └── span expression
      │         │         │              ├── tight: true, unique: true
      │         │         │              └── union spans: ["a"/Arr/2, "a"/Arr/2]
      │         │         └── span expression
      │         │              ├── tight: true, unique: true
      │         │              └── union spans: ["a"/Arr/3, "a"/Arr/3]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["a"/NULL, "a"/NULL]
      │                        ├── ["a"/"a", "a"/"a"]
      │                        ├── ["a"/1, "a"/1]
      │                        ├── ["a"/Arr/1, "a"/Arr/1]
      │                        ├── ["a"/Arr/2, "a"/Arr/2]
      │                        ├── ["a"/Arr/3, "a"/Arr/3]
      │                        └── ["a"/"a"/"b", "a"/"a"/"b"]
      └── filters
           └── (j:4->'a') IN ('null', '"a"', '1', '[1, 2, 3]', '{"a": "b"}') [outer=(4), immutable]


# Generate an inverted scan when the index of the fetch val operator is
# a string along with the IN operator consisting of JSON arrays in
# the tuple. Since the inverted expression is not tight,
# the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' IN ('[1,2,3]', '[1, 2]', '[1]')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: true
      │         │    ├── union spans: ["a"/Arr/1, "a"/Arr/1]
      │         │    └── UNION
      │         │         ├── span expression
      │         │         │    ├── tight: false, unique: true
      │         │         │    ├── union spans: empty
      │         │         │    └── INTERSECTION
      │         │         │         ├── span expression
      │         │         │         │    ├── tight: true, unique: true
      │         │         │         │    └── union spans: ["a"/Arr/1, "a"/Arr/1]
      │         │         │         └── span expression
      │         │         │              ├── tight: true, unique: true
      │         │         │              └── union spans: ["a"/Arr/2, "a"/Arr/2]
      │         │         └── span expression
      │         │              ├── tight: false, unique: true
      │         │              ├── union spans: empty
      │         │              └── INTERSECTION
      │         │                   ├── span expression
      │         │                   │    ├── tight: true, unique: true
      │         │                   │    ├── union spans: empty
      │         │                   │    └── INTERSECTION
      │         │                   │         ├── span expression
      │         │                   │         │    ├── tight: true, unique: true
      │         │                   │         │    └── union spans: ["a"/Arr/1, "a"/Arr/1]
      │         │                   │         └── span expression
      │         │                   │              ├── tight: true, unique: true
      │         │                   │              └── union spans: ["a"/Arr/2, "a"/Arr/2]
      │         │                   └── span expression
      │         │                        ├── tight: true, unique: true
      │         │                        └── union spans: ["a"/Arr/3, "a"/Arr/3]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["a"/Arr/1, "a"/Arr/1]
      │                        ├── ["a"/Arr/2, "a"/Arr/2]
      │                        └── ["a"/Arr/3, "a"/Arr/3]
      └── filters
           └── (j:4->'a') IN ('[1]', '[1, 2]', '[1, 2, 3]') [outer=(4), immutable]


# Generate an inverted scan when the index of the fetch val operator is
# an integer along with the IN operator consisting of JSON integers in
# the tuple. Since the inverted expression is not tight,
# the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 in ('1', '2')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [Arr/1, Arr/1]
      │         │         └── [Arr/2, Arr/2]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [Arr/1, Arr/1]
      │                        └── [Arr/2, Arr/2]
      └── filters
           └── (j:4->0) IN ('1', '2') [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator is
# an integer along with the IN operator consisting of JSON strings in
# the tuple. Since the generated inverted expression is not tight,
# the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 IN ('"a"', '"b"')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [Arr/"a", Arr/"a"]
      │         │         └── [Arr/"b", Arr/"b"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [Arr/"a", Arr/"a"]
      │                        └── [Arr/"b", Arr/"b"]
      └── filters
           └── (j:4->0) IN ('"a"', '"b"') [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator is
# an integer along with the IN operator consisting of JSON objects in
# the tuple. Since the generated inverted expression is not tight,
# the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 IN ('{"a": "b"}', '{"c": "d"}')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [Arr/"a"/"b", Arr/"a"/"b"]
      │         │         └── [Arr/"c"/"d", Arr/"c"/"d"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [Arr/"a"/"b", Arr/"a"/"b"]
      │                        └── [Arr/"c"/"d", Arr/"c"/"d"]
      └── filters
           └── (j:4->0) IN ('{"a": "b"}', '{"c": "d"}') [outer=(4), immutable]

# Do not generate an inverted scan when one of the elements of the tuple is not a constant
# JSON expression.
opt expect-not=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 IN ('{"a": "b"}', j->0)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── scan b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    └── fd: (1)-->(4)
      └── filters
           └── (j:4->0) IN ('{"a": "b"}', j:4->0) [outer=(4), immutable]

# Do not generate inverted index scans when the IN operator is used with sub-queries. This is
# due to the fact that queries of the form, <scalar> IN (<subquery>), <scalar> NOT IN (<subquery>),
# <scalar> <cmp> {SOME|ANY}(<subquery>) and <scalar> <cmp> ALL(<subquery>) all map to the Any
# operator.
opt expect-not=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' IN ((SELECT j from b where v = 1 limit 1), (SELECT j FROM b WHERE v = 2 limit 1))
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── scan b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    └── fd: (1)-->(4)
      └── filters
           └── in [outer=(4), immutable, subquery]
                ├── j:4->'a'
                └── tuple
                     ├── subquery
                     │    └── project
                     │         ├── columns: j:11
                     │         ├── cardinality: [0 - 1]
                     │         ├── key: ()
                     │         ├── fd: ()-->(11)
                     │         └── limit
                     │              ├── columns: v:10!null j:11
                     │              ├── cardinality: [0 - 1]
                     │              ├── key: ()
                     │              ├── fd: ()-->(10,11)
                     │              ├── index-join b
                     │              │    ├── columns: v:10!null j:11
                     │              │    ├── lax-key: (11)
                     │              │    ├── fd: ()-->(10,11)
                     │              │    ├── limit hint: 1.00
                     │              │    └── scan b@v
                     │              │         ├── columns: k:8!null v:10!null
                     │              │         ├── constraint: /10: [/1 - /1]
                     │              │         ├── cardinality: [0 - 1]
                     │              │         ├── key: ()
                     │              │         ├── fd: ()-->(8,10)
                     │              │         └── limit hint: 1.00
                     │              └── 1
                     └── subquery
                          └── project
                               ├── columns: j:18
                               ├── cardinality: [0 - 1]
                               ├── key: ()
                               ├── fd: ()-->(18)
                               └── limit
                                    ├── columns: v:17!null j:18
                                    ├── cardinality: [0 - 1]
                                    ├── key: ()
                                    ├── fd: ()-->(17,18)
                                    ├── index-join b
                                    │    ├── columns: v:17!null j:18
                                    │    ├── lax-key: (18)
                                    │    ├── fd: ()-->(17,18)
                                    │    ├── limit hint: 1.00
                                    │    └── scan b@v
                                    │         ├── columns: k:15!null v:17!null
                                    │         ├── constraint: /17: [/2 - /2]
                                    │         ├── cardinality: [0 - 1]
                                    │         ├── key: ()
                                    │         ├── fd: ()-->(15,17)
                                    │         └── limit hint: 1.00
                                    └── 1

# Do not generate inverted index scans when the IN operator is used with sub-queries. This is
# due to the fact that queries of the form, <scalar> IN (<subquery>), <scalar> NOT IN (<subquery>),
# <scalar> <cmp> {SOME|ANY}(<subquery>) and <scalar> <cmp> ALL(<subquery>) all map to the Any
# operator.
opt expect-not=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' NOT IN ((SELECT j from b where v = 1 limit 1), (SELECT j FROM b WHERE v = 2 limit 1))
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── scan b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    └── fd: (1)-->(4)
      └── filters
           └── not-in [outer=(4), immutable, subquery]
                ├── j:4->'a'
                └── tuple
                     ├── subquery
                     │    └── project
                     │         ├── columns: j:11
                     │         ├── cardinality: [0 - 1]
                     │         ├── key: ()
                     │         ├── fd: ()-->(11)
                     │         └── limit
                     │              ├── columns: v:10!null j:11
                     │              ├── cardinality: [0 - 1]
                     │              ├── key: ()
                     │              ├── fd: ()-->(10,11)
                     │              ├── index-join b
                     │              │    ├── columns: v:10!null j:11
                     │              │    ├── lax-key: (11)
                     │              │    ├── fd: ()-->(10,11)
                     │              │    ├── limit hint: 1.00
                     │              │    └── scan b@v
                     │              │         ├── columns: k:8!null v:10!null
                     │              │         ├── constraint: /10: [/1 - /1]
                     │              │         ├── cardinality: [0 - 1]
                     │              │         ├── key: ()
                     │              │         ├── fd: ()-->(8,10)
                     │              │         └── limit hint: 1.00
                     │              └── 1
                     └── subquery
                          └── project
                               ├── columns: j:18
                               ├── cardinality: [0 - 1]
                               ├── key: ()
                               ├── fd: ()-->(18)
                               └── limit
                                    ├── columns: v:17!null j:18
                                    ├── cardinality: [0 - 1]
                                    ├── key: ()
                                    ├── fd: ()-->(17,18)
                                    ├── index-join b
                                    │    ├── columns: v:17!null j:18
                                    │    ├── lax-key: (18)
                                    │    ├── fd: ()-->(17,18)
                                    │    ├── limit hint: 1.00
                                    │    └── scan b@v
                                    │         ├── columns: k:15!null v:17!null
                                    │         ├── constraint: /17: [/2 - /2]
                                    │         ├── cardinality: [0 - 1]
                                    │         ├── key: ()
                                    │         ├── fd: ()-->(15,17)
                                    │         └── limit hint: 1.00
                                    └── 1

# Generate an inverted scan when the index of the fetch val operator is
# an integer along with the IN operator consisting of a combination of JSON objects,
# JSON strings and JSON integers inside the tuple. Since the inverted expression generated
# is not tight, the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 IN ('{"a": "b"}', '1', '"a"', 'null')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [Arr/NULL, Arr/NULL]
      │         │         ├── [Arr/"a", Arr/"a"]
      │         │         ├── [Arr/1, Arr/1]
      │         │         └── [Arr/"a"/"b", Arr/"a"/"b"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [Arr/NULL, Arr/NULL]
      │                        ├── [Arr/"a", Arr/"a"]
      │                        ├── [Arr/1, Arr/1]
      │                        └── [Arr/"a"/"b", Arr/"a"/"b"]
      └── filters
           └── (j:4->0) IN ('null', '"a"', '1', '{"a": "b"}') [outer=(4), immutable]


# Generate an inverted scan when the index of the fetch val operator is
# an integer along with the IN operator consisting of JSON arrays.
# Since the inverted expression generated is not tight,
# the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0 IN ('[1,2,3]', '[1]', '[1,2]')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: true
      │         │    ├── union spans: [Arr/Arr/1, Arr/Arr/1]
      │         │    └── UNION
      │         │         ├── span expression
      │         │         │    ├── tight: false, unique: true
      │         │         │    ├── union spans: empty
      │         │         │    └── INTERSECTION
      │         │         │         ├── span expression
      │         │         │         │    ├── tight: true, unique: true
      │         │         │         │    └── union spans: [Arr/Arr/1, Arr/Arr/1]
      │         │         │         └── span expression
      │         │         │              ├── tight: true, unique: true
      │         │         │              └── union spans: [Arr/Arr/2, Arr/Arr/2]
      │         │         └── span expression
      │         │              ├── tight: false, unique: true
      │         │              ├── union spans: empty
      │         │              └── INTERSECTION
      │         │                   ├── span expression
      │         │                   │    ├── tight: true, unique: true
      │         │                   │    ├── union spans: empty
      │         │                   │    └── INTERSECTION
      │         │                   │         ├── span expression
      │         │                   │         │    ├── tight: true, unique: true
      │         │                   │         │    └── union spans: [Arr/Arr/1, Arr/Arr/1]
      │         │                   │         └── span expression
      │         │                   │              ├── tight: true, unique: true
      │         │                   │              └── union spans: [Arr/Arr/2, Arr/Arr/2]
      │         │                   └── span expression
      │         │                        ├── tight: true, unique: true
      │         │                        └── union spans: [Arr/Arr/3, Arr/Arr/3]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [Arr/Arr/1, Arr/Arr/1]
      │                        ├── [Arr/Arr/2, Arr/Arr/2]
      │                        └── [Arr/Arr/3, Arr/Arr/3]
      └── filters
           └── (j:4->0) IN ('[1]', '[1, 2]', '[1, 2, 3]') [outer=(4), immutable]

# Generate an inverted scan when the index of the fetch val operator is
# a combination of an integer and a string along with the IN operator consisting of
# a combination of JSON arrays, JSON objects, JSON integers, JSON strings and null.
# Since the inverted expression generated is not tight,
# the original filter should be applied on top.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->0->'a' IN ('{"a": "b"}', '1', '"a"', 'null')
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [Arr/"a"/NULL, Arr/"a"/NULL]
      │         │         ├── [Arr/"a"/"a", Arr/"a"/"a"]
      │         │         ├── [Arr/"a"/1, Arr/"a"/1]
      │         │         └── [Arr/"a"/"a"/"b", Arr/"a"/"a"/"b"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [Arr/"a"/NULL, Arr/"a"/NULL]
      │                        ├── [Arr/"a"/"a", Arr/"a"/"a"]
      │                        ├── [Arr/"a"/1, Arr/"a"/1]
      │                        └── [Arr/"a"/"a"/"b", Arr/"a"/"a"/"b"]
      └── filters
           └── ((j:4->0)->'a') IN ('null', '"a"', '1', '{"a": "b"}') [outer=(4), immutable]


# Query using the fetch val and equality operators in a disjunction with a
# contains operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' = '"b"' OR j @> '{"c": "d"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/"b", "a"/"b"]
      │         └── ["c"/"d", "c"/"d"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"b", "a"/"b"]
                     └── ["c"/"d", "c"/"d"]

# Query using the fetch val and containment operators.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' @> '"b"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/"b", "a"/"b"]
      │         └── ["a"/Arr/"b", "a"/Arr/"b"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"b", "a"/"b"]
                     └── ["a"/Arr/"b", "a"/Arr/"b"]

opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' <@ '"b"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [{}, {}]
      │         │         └── ["a"/"b", "a"/"b"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [{}, {}]
      │                        └── ["a"/"b", "a"/"b"]
      └── filters
           └── (j:4->'a') <@ '"b"' [outer=(4), immutable]

# Chained fetch val operators and containment operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a'->'b' @> '"c"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/"b"/"c", "a"/"b"/"c"]
      │         └── ["a"/"b"/Arr/"c", "a"/"b"/Arr/"c"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"b"/"c", "a"/"b"/"c"]
                     └── ["a"/"b"/Arr/"c", "a"/"b"/Arr/"c"]

opt expect=ConvertJSONSubscriptToFetchValue
SELECT k FROM b WHERE j['a']['b'] @> '"c"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/"b"/"c", "a"/"b"/"c"]
      │         └── ["a"/"b"/Arr/"c", "a"/"b"/Arr/"c"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"b"/"c", "a"/"b"/"c"]
                     └── ["a"/"b"/Arr/"c", "a"/"b"/Arr/"c"]

opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a'->'b' <@ '"c"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [{}, {}]
      │         │         ├── ["a"/{}, "a"/{}]
      │         │         └── ["a"/"b"/"c", "a"/"b"/"c"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [{}, {}]
      │                        ├── ["a"/{}, "a"/{}]
      │                        └── ["a"/"b"/"c", "a"/"b"/"c"]
      └── filters
           └── ((j:4->'a')->'b') <@ '"c"' [outer=(4), immutable]

# Query using the fetch val and equality operators in a disjunction.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' @> '"b"' OR j->'c' @> '"d"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── ["a"/"b", "a"/"b"]
      │         ├── ["a"/Arr/"b", "a"/Arr/"b"]
      │         ├── ["c"/"d", "c"/"d"]
      │         └── ["c"/Arr/"d", "c"/Arr/"d"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"b", "a"/"b"]
                     ├── ["a"/Arr/"b", "a"/Arr/"b"]
                     ├── ["c"/"d", "c"/"d"]
                     └── ["c"/Arr/"d", "c"/Arr/"d"]

# Query using the fetch val and contains operators in a disjunction with a
# contained by operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' @> '["b"]' OR j <@ '{"c": "d"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── [{}, {}]
      │         │         ├── ["a"/Arr/"b", "a"/Arr/"b"]
      │         │         └── ["c"/"d", "c"/"d"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [{}, {}]
      │                        ├── ["a"/Arr/"b", "a"/Arr/"b"]
      │                        └── ["c"/"d", "c"/"d"]
      └── filters
           └── ((j:4->'a') @> '["b"]') OR (j:4 <@ '{"c": "d"}') [outer=(4), immutable]

# Query using the fetch val and equality operators in a conjunction.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' @> '"b"' AND j->'c' @> '"d"'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: false
      │         │    └── union spans
      │         │         ├── ["a"/"b", "a"/"b"]
      │         │         └── ["a"/Arr/"b", "a"/Arr/"b"]
      │         └── span expression
      │              ├── tight: true, unique: false
      │              └── union spans
      │                   ├── ["c"/"d", "c"/"d"]
      │                   └── ["c"/Arr/"d", "c"/Arr/"d"]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:7!null
           └── inverted constraint: /7/1
                └── spans
                     ├── ["a"/"b", "a"/"b"]
                     ├── ["a"/Arr/"b", "a"/Arr/"b"]
                     ├── ["c"/"d", "c"/"d"]
                     └── ["c"/Arr/"d", "c"/Arr/"d"]

# Query using the fetch val and contains operators in conjunction with a
# contained by operator.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j->'a' @> '["b"]' AND j <@ '{"c": "d"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    ├── union spans: empty
      │         │    └── INTERSECTION
      │         │         ├── span expression
      │         │         │    ├── tight: true, unique: true
      │         │         │    └── union spans: ["a"/Arr/"b", "a"/Arr/"b"]
      │         │         └── span expression
      │         │              ├── tight: false, unique: false
      │         │              └── union spans
      │         │                   ├── [{}, {}]
      │         │                   └── ["c"/"d", "c"/"d"]
      │         ├── key: (1)
      │         └── scan b@j_inv_idx,inverted
      │              ├── columns: k:1!null j_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── [{}, {}]
      │                        ├── ["a"/Arr/"b", "a"/Arr/"b"]
      │                        └── ["c"/"d", "c"/"d"]
      └── filters
           └── j:4 <@ '{"c": "d"}' [outer=(4), immutable]

# GenerateInvertedIndexScans propagates row-level locking information.
opt expect=GenerateInvertedIndexScans
SELECT k FROM b WHERE j @> '{"a": "b"}' FOR UPDATE
----
project
 ├── columns: k:1!null
 ├── volatile
 ├── key: (1)
 └── scan b@j_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/"b", "a"/"b"]
      ├── locking: for-update
      ├── volatile
      └── key: (1)

# Tests for array inverted indexes.
opt expect=GenerateInvertedIndexScans
SELECT k FROM c WHERE a @> ARRAY[1]
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan c@a_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /6/1
      │    └── spans: [1, 1]
      └── key: (1)

# Disjunction.
opt expect=GenerateInvertedIndexScans
SELECT k FROM c WHERE a @> ARRAY[1] OR a @> ARRAY[2]
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /6
      │    ├── tight: true, unique: false
      │    └── union spans: [1, 3)
      ├── key: (1)
      └── scan c@a_inv_idx,inverted
           ├── columns: k:1!null a_inverted_key:6!null
           └── inverted constraint: /6/1
                └── spans: [1, 3)

opt expect=GenerateInvertedIndexScans
SELECT k FROM c WHERE a @> ARRAY[]::INT[]
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /6
      │    ├── tight: true, unique: false
      │    └── union spans: [, NULL/NULL)
      ├── key: (1)
      └── scan c@a_inv_idx,inverted
           ├── columns: k:1!null a_inverted_key:6!null
           └── inverted constraint: /6/1
                └── spans: [, NULL/NULL)

opt expect-not=GenerateInvertedIndexScans
SELECT k FROM c WHERE a IS NULL
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null a:2
      ├── key: (1)
      ├── fd: ()-->(2)
      ├── scan c
      │    ├── columns: k:1!null a:2
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      └── filters
           └── a:2 IS NULL [outer=(2), constraints=(/2: [/NULL - /NULL]; tight), fd=()-->(2)]

opt expect=GenerateInvertedIndexScans
SELECT * FROM c WHERE a <@ ARRAY[1]
----
select
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /6
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── [[], []]
 │         │         └── [1, 1]
 │         ├── key: (1)
 │         └── scan c@a_inv_idx,inverted
 │              ├── columns: k:1!null a_inverted_key:6!null
 │              └── inverted constraint: /6/1
 │                   └── spans
 │                        ├── [[], []]
 │                        └── [1, 1]
 └── filters
      └── a:2 <@ ARRAY[1] [outer=(2), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM c WHERE a <@ ARRAY[]::INT[]
----
select
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan c@a_inv_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /6/1
 │         │    └── spans: [[], []]
 │         └── key: (1)
 └── filters
      └── a:2 <@ ARRAY[] [outer=(2), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM c WHERE a <@ ARRAY[NULL]::INT[]
----
select
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan c@a_inv_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /6/1
 │         │    └── spans: [[], []]
 │         └── key: (1)
 └── filters
      └── a:2 <@ ARRAY[NULL] [outer=(2), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM c WHERE a <@ ARRAY[1] OR a <@ ARRAY[2]
----
select
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /6
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── [[], []]
 │         │         └── [1, 3)
 │         ├── key: (1)
 │         └── scan c@a_inv_idx,inverted
 │              ├── columns: k:1!null a_inverted_key:6!null
 │              └── inverted constraint: /6/1
 │                   └── spans
 │                        ├── [[], []]
 │                        └── [1, 3)
 └── filters
      └── (a:2 <@ ARRAY[1]) OR (a:2 <@ ARRAY[2]) [outer=(2), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM c WHERE a <@ ARRAY[1] AND a <@ ARRAY[2]
----
select
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /6
 │         │    ├── tight: false, unique: false
 │         │    ├── union spans: [[], []]
 │         │    └── INTERSECTION
 │         │         ├── span expression
 │         │         │    ├── tight: false, unique: false
 │         │         │    └── union spans: [1, 1]
 │         │         └── span expression
 │         │              ├── tight: false, unique: false
 │         │              └── union spans: [2, 2]
 │         ├── key: (1)
 │         └── scan c@a_inv_idx,inverted
 │              ├── columns: k:1!null a_inverted_key:6!null
 │              └── inverted constraint: /6/1
 │                   └── spans
 │                        ├── [[], []]
 │                        └── [1, 3)
 └── filters
      ├── a:2 <@ ARRAY[1] [outer=(2), immutable]
      └── a:2 <@ ARRAY[2] [outer=(2), immutable]

opt expect=GenerateInvertedIndexScans
SELECT * FROM c@a_inv_idx WHERE a && ARRAY[1]
----
index-join c
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── scan c@a_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /6/1
      │    └── spans: [1, 1]
      ├── flags: force-index=a_inv_idx
      └── key: (1)

opt expect=GenerateInvertedIndexScans
SELECT * FROM c@a_inv_idx WHERE a && ARRAY[1] OR a && ARRAY[2]
----
index-join c
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /6
      │    ├── tight: true, unique: false
      │    └── union spans: [1, 3)
      ├── key: (1)
      └── scan c@a_inv_idx,inverted
           ├── columns: k:1!null a_inverted_key:6!null
           ├── inverted constraint: /6/1
           │    └── spans: [1, 3)
           └── flags: force-index=a_inv_idx

opt expect=GenerateInvertedIndexScans
SELECT * FROM c WHERE a && ARRAY[1] AND a && ARRAY[2]
----
inner-join (lookup c)
 ├── columns: k:1!null a:2 u:3
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── inner-join (zigzag c@a_inv_idx,inverted c@a_inv_idx,inverted)
 │    ├── columns: k:1!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [6] = ['\x89']
 │    ├── right fixed columns: [6] = ['\x8a']
 │    └── filters (true)
 └── filters (true)

# TODO: Add normalization rule to convert to no-op since predicate
# is a contradiction
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM c WHERE a && ARRAY[]::INT[]
----
select
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── scan c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 └── filters
      └── a:2 && ARRAY[] [outer=(2), immutable]

# TODO: Add normalization rule to convert to no-op since predicate
# is a contradiction
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM c WHERE a && ARRAY[NULL]::INT[]
----
select
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── scan c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 └── filters
      └── a:2 && ARRAY[NULL] [outer=(2), immutable]

# Tests for indexes with older descriptor versions.

exec-ddl
DROP INDEX j_inv_idx
----

exec-ddl
DROP INDEX a_inv_idx
----

exec-ddl index-version=1
CREATE INVERTED INDEX j_inv_idx ON b (j)
----

exec-ddl index-version=1
CREATE INVERTED INDEX a_inv_idx ON c (a)
----

# Verify that we do not plan an inverted index scan with an empty array if the
# index has version SecondaryIndexFamilyFormatVersion, since it does not contain
# keys for empty arrays.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM c WHERE a @> '{}'
----
select
 ├── columns: k:1!null a:2!null u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── scan c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 └── filters
      └── a:2 @> ARRAY[] [outer=(2), immutable, constraints=(/2: (/NULL - ])]

# We should still plan an inverted index scan with a non-empty array even
# if the index version is SecondaryIndexFamilyFormatVersion.
opt expect=GenerateInvertedIndexScans
SELECT * FROM c WHERE a @> '{1}'
----
index-join c
 ├── columns: k:1!null a:2!null u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── scan c@a_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: [1, 1]
      └── key: (1)

# We should still plan an inverted index scan with an empty JSON array even
# if the index version is SecondaryIndexFamilyFormatVersion.
opt expect=GenerateInvertedIndexScans
SELECT * FROM b WHERE j @> '[]'
----
index-join b
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /8
      │    ├── tight: true, unique: false
      │    └── union spans
      │         ├── [[], []]
      │         └── [Arr/, Arr/]
      ├── key: (1)
      └── scan b@j_inv_idx,inverted
           ├── columns: k:1!null j_inverted_key:8!null
           └── inverted constraint: /8/1
                └── spans
                     ├── [[], []]
                     └── [Arr/, Arr/]

# The inverted index will never be used for <@ expressions if the index version
# does not have empty arrays in the inverted index.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM c WHERE a <@ '{2}'
----
select
 ├── columns: k:1!null a:2 u:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── scan c
 │    ├── columns: k:1!null a:2 u:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 └── filters
      └── a:2 <@ ARRAY[2] [outer=(2), immutable]

exec-ddl
DROP INDEX j_inv_idx
----

exec-ddl
DROP INDEX a_inv_idx
----

exec-ddl
CREATE INVERTED INDEX j_inv_idx ON b (j)
----

exec-ddl
CREATE INVERTED INDEX a_inv_idx ON c (a)
----

# Tests for geospatial constrained scans.
opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_Intersects('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join g
      │    ├── columns: k:1!null geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │         │         └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_intersects('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4)
      │         ├── key: (1)
      │         └── scan g@geog_idx,inverted
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              └── inverted constraint: /8/1
      │                   └── spans
      │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │                        └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── st_intersects('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4) [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# Same test as above, but with commuted arguments.
opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_Intersects(geog, 'SRID=4326;POINT(-43.23456 72.4567772)'::geography)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join g
      │    ├── columns: k:1!null geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │         │         └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_intersects('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4)
      │         ├── key: (1)
      │         └── scan g@geog_idx,inverted
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              └── inverted constraint: /8/1
      │                   └── spans
      │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │                        └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── st_intersects(geog:4, '0101000020E61000009279E40F069E45C0BEE36FD63B1D5240') [outer=(4), immutable, constraints=(/4: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3) [outer=(3), immutable, constraints=(/3: (/NULL - ])]

# Same test as above, but with commuted arguments.
opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_CoveredBy(geom, 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         ├── pre-filterer expression
      │         │    └── st_covers('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      └── filters
           └── st_coveredby(geom:3, '0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000') [outer=(3), immutable, constraints=(/3: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_DWithin('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom, 2)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         ├── pre-filterer expression
      │         │    └── st_dwithin('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      └── filters
           └── st_dwithin('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0) [outer=(3), immutable, constraints=(/3: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_DFullyWithin('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom, 2)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         ├── pre-filterer expression
      │         │    └── st_dfullywithin('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      └── filters
           └── st_dfullywithin('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0) [outer=(3), immutable, constraints=(/3: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_DWithin('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog, 2000)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join g
      │    ├── columns: k:1!null geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x01", "B\xfdF\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x01", "B\xfdR\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdR\x00\x00\x00\x00\x00\x00\x01", "B\xfdT\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdT\x00\x00\x00\x00\x00\x00\x00", "B\xfdT\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdZ\x00\x00\x00\x00\x00\x00\x01", "B\xfd\\\x00\x00\x00\x00\x00\x00\x00")
      │         │         └── ["B\xfd\\\x00\x00\x00\x00\x00\x00\x00", "B\xfd\\\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_dwithin('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4, 2000.0)
      │         ├── key: (1)
      │         └── scan g@geog_idx,inverted
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              └── inverted constraint: /8/1
      │                   └── spans
      │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x01", "B\xfdF\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x01", "B\xfdR\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdR\x00\x00\x00\x00\x00\x00\x01", "B\xfdT\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdT\x00\x00\x00\x00\x00\x00\x00", "B\xfdT\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdZ\x00\x00\x00\x00\x00\x00\x01", "B\xfd\\\x00\x00\x00\x00\x00\x00\x00")
      │                        └── ["B\xfd\\\x00\x00\x00\x00\x00\x00\x00", "B\xfd\\\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── st_dwithin('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4, 2000.0) [outer=(4), immutable, constraints=(/4: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_DWithin('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog, 2000, false)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join g
      │    ├── columns: k:1!null geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x01", "B\xfdF\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x01", "B\xfdR\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdR\x00\x00\x00\x00\x00\x00\x01", "B\xfdT\x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── ["B\xfdT\x00\x00\x00\x00\x00\x00\x00", "B\xfdT\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdZ\x00\x00\x00\x00\x00\x00\x01", "B\xfd\\\x00\x00\x00\x00\x00\x00\x00")
      │         │         └── ["B\xfd\\\x00\x00\x00\x00\x00\x00\x00", "B\xfd\\\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_dwithin('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4, 2000.0, false)
      │         ├── key: (1)
      │         └── scan g@geog_idx,inverted
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              └── inverted constraint: /8/1
      │                   └── spans
      │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x01", "B\xfdF\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x01", "B\xfdR\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdR\x00\x00\x00\x00\x00\x00\x01", "B\xfdT\x00\x00\x00\x00\x00\x00\x00")
      │                        ├── ["B\xfdT\x00\x00\x00\x00\x00\x00\x00", "B\xfdT\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdZ\x00\x00\x00\x00\x00\x00\x01", "B\xfd\\\x00\x00\x00\x00\x00\x00\x00")
      │                        └── ["B\xfd\\\x00\x00\x00\x00\x00\x00\x00", "B\xfd\\\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── st_dwithin('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4, 2000.0, false) [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# Test for ST_DWithinExclusive
opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_DWithinExclusive('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom, 2)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         ├── pre-filterer expression
      │         │    └── st_dwithinexclusive('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      └── filters
           └── st_dwithinexclusive('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0) [outer=(3), immutable, constraints=(/3: (/NULL - ])]

# Commuted version of previous test.
opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_DWithinExclusive(geom, 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, 2)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         ├── pre-filterer expression
      │         │    └── st_dwithin('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      └── filters
           └── st_dwithinexclusive(geom:3, '0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', 2.0) [outer=(3), immutable, constraints=(/3: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE ST_DFullyWithinExclusive('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom, 2)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         ├── pre-filterer expression
      │         │    └── st_dfullywithinexclusive('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      └── filters
           └── st_dfullywithinexclusive('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:3, 2.0) [outer=(3), immutable, constraints=(/3: (/NULL - ])]

# Multiple geospatial functions.
# NB: the union spans for the following query are a subset of the spans. This is a
# consequence of how intersectSpanExpression does simplification.
opt expect=GenerateInvertedIndexScans
SELECT k FROM g
WHERE ST_Covers('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog)
AND ST_CoveredBy('SRID=4326;POINT(-40.23456 70.4567772)'::geography, geog)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join g
      │    ├── columns: k:1!null geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdO\x00\x00\x00\x00\x00\x00\x00", "B\xfdO\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4)
      │         ├── key: (1)
      │         └── scan g@geog_idx,inverted
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              └── inverted constraint: /8/1
      │                   └── spans
      │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdO\x00\x00\x00\x00\x00\x00\x00", "B\xfdO\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           ├── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4) [outer=(4), immutable, constraints=(/4: (/NULL - ])]
           └── st_coveredby('0101000020E61000009279E40F061E44C0BEE36FD63B9D5140', geog:4) [outer=(4), immutable, constraints=(/4: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g
WHERE ST_Covers('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog)
OR ST_CoveredBy('SRID=4326;POINT(-40.23456 70.4567772)'::geography, geog)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join g
      │    ├── columns: k:1!null geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x01")
      │         ├── key: (1)
      │         └── scan g@geog_idx,inverted
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              └── inverted constraint: /8/1
      │                   └── spans
      │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x01")
      └── filters
           └── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4) OR st_coveredby('0101000020E61000009279E40F061E44C0BEE36FD63B9D5140', geog:4) [outer=(4), immutable, constraints=(/4: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g
WHERE ST_Covers('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog)
OR (ST_CoveredBy('SRID=4326;LINESTRING(-118.4079 33.9434, 2.5559 49.0083)'::geography, geog)
AND ST_CoveredBy('SRID=4326;POINT(-44.23836 60.4567772)'::geography, geog))
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join g
      │    ├── columns: k:1!null geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    ├── union spans
      │         │    │    ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │         │    │    ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │         │    │    └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         │    └── INTERSECTION
      │         │         ├── span expression
      │         │         │    ├── tight: false, unique: false
      │         │         │    ├── union spans: empty
      │         │         │    └── INTERSECTION
      │         │         │         ├── span expression
      │         │         │         │    ├── tight: false, unique: false
      │         │         │         │    └── union spans
      │         │         │         │         ├── ["B\xfd\x81\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x81\x00\x00\x00\x00\x00\x00\x00"]
      │         │         │         │         ├── ["B\xfd\x84\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x84\x00\x00\x00\x00\x00\x00\x00"]
      │         │         │         │         └── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
      │         │         │         └── span expression
      │         │         │              ├── tight: false, unique: false
      │         │         │              └── union spans
      │         │         │                   ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
      │         │         │                   ├── ["B\xfdG\x00\x00\x00\x00\x00\x00\x00", "B\xfdG\x00\x00\x00\x00\x00\x00\x00"]
      │         │         │                   └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── span expression
      │         │              ├── tight: false, unique: false
      │         │              └── union spans
      │         │                   ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │         │                   ├── ["B\xfdO\x00\x00\x00\x00\x00\x00\x00", "B\xfdO\x00\x00\x00\x00\x00\x00\x00"]
      │         │                   └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── key: (1)
      │         └── scan g@geog_idx,inverted
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              └── inverted constraint: /8/1
      │                   └── spans
      │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdG\x00\x00\x00\x00\x00\x00\x00", "B\xfdG\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x01")
      │                        ├── ["B\xfd\x81\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x81\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfd\x84\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x84\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4) OR (st_coveredby('0102000020E61000000200000075029A081B9A5DC0F085C954C1F840406DC5FEB27B720440454772F90F814840', geog:4) AND st_coveredby('0101000020E610000058569A94821E46C07BC7DFAC773A4E40', geog:4)) [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# Multiple geospatial functions on different indexes.
opt expect=GenerateInvertedIndexScans
SELECT k FROM g
WHERE ST_Covers('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog)
AND ST_Overlaps('LINESTRING ( 0 0, 0 2 )'::geometry, geom)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3,4)
      ├── index-join g
      │    ├── columns: k:1!null geom:3 geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(3,4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         ├── pre-filterer expression
      │         │    └── st_overlaps('0102000000020000000000000000000000000000000000000000000000000000000000000000000040', geom:3)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      └── filters
           ├── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4) [outer=(4), immutable, constraints=(/4: (/NULL - ])]
           └── st_overlaps('0102000000020000000000000000000000000000000000000000000000000000000000000000000040', geom:3) [outer=(3), immutable, constraints=(/3: (/NULL - ])]

# We can use both indexes because of the SplitDisjunction rule.
opt
SELECT k FROM g
WHERE ST_Covers('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog)
OR ST_Touches('LINESTRING ( 0 0, 0 2 )'::geometry, geom)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null geom:3 geog:4
      ├── grouping columns: k:1!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3,4)
      ├── union-all
      │    ├── columns: k:1!null geom:3 geog:4
      │    ├── left columns: k:9 geom:11 geog:12
      │    ├── right columns: k:17 geom:19 geog:20
      │    ├── immutable
      │    ├── select
      │    │    ├── columns: k:9!null geom:11 geog:12!null
      │    │    ├── immutable
      │    │    ├── key: (9)
      │    │    ├── fd: (9)-->(11,12)
      │    │    ├── index-join g
      │    │    │    ├── columns: k:9!null geom:11 geog:12
      │    │    │    ├── key: (9)
      │    │    │    ├── fd: (9)-->(11,12)
      │    │    │    └── inverted-filter
      │    │    │         ├── columns: k:9!null
      │    │    │         ├── inverted expression: /16
      │    │    │         │    ├── tight: false, unique: false
      │    │    │         │    └── union spans
      │    │    │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │    │    │         │         ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │    │    │         │         └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │    │    │         ├── pre-filterer expression
      │    │    │         │    └── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:12)
      │    │    │         ├── key: (9)
      │    │    │         └── scan g@geog_idx,inverted
      │    │    │              ├── columns: k:9!null geog_inverted_key:16!null
      │    │    │              └── inverted constraint: /16/9
      │    │    │                   └── spans
      │    │    │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │    │    │                        ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │    │    │                        └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │    │    └── filters
      │    │         └── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:12) [outer=(12), immutable, constraints=(/12: (/NULL - ])]
      │    └── select
      │         ├── columns: k:17!null geom:19!null geog:20
      │         ├── immutable
      │         ├── key: (17)
      │         ├── fd: (17)-->(19,20)
      │         ├── index-join g
      │         │    ├── columns: k:17!null geom:19 geog:20
      │         │    ├── key: (17)
      │         │    ├── fd: (17)-->(19,20)
      │         │    └── inverted-filter
      │         │         ├── columns: k:17!null
      │         │         ├── inverted expression: /23
      │         │         │    ├── tight: false, unique: false
      │         │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         │         ├── pre-filterer expression
      │         │         │    └── st_touches('0102000000020000000000000000000000000000000000000000000000000000000000000000000040', geom:19)
      │         │         ├── key: (17)
      │         │         └── scan g@geom_idx,inverted
      │         │              ├── columns: k:17!null geom_inverted_key:23!null
      │         │              └── inverted constraint: /23/17
      │         │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
      │         └── filters
      │              └── st_touches('0102000000020000000000000000000000000000000000000000000000000000000000000000000040', geom:19) [outer=(19), immutable, constraints=(/19: (/NULL - ])]
      └── aggregations
           ├── const-agg [as=geom:3, outer=(3)]
           │    └── geom:3
           └── const-agg [as=geog:4, outer=(4)]
                └── geog:4

opt expect=GenerateInvertedIndexScans
SELECT k FROM g
WHERE ST_Covers('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog)
OR (ST_Intersects('SRID=4326;POINT(-40.23456 70.4567772)'::geography, geog)
AND ST_Crosses('LINESTRING ( 0 0, 0 2 )'::geometry, geom))
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3 geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3,4)
      ├── index-join g
      │    ├── columns: k:1!null geom:3 geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(3,4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x01")
      │         ├── key: (1)
      │         └── scan g@geog_idx,inverted
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              └── inverted constraint: /8/1
      │                   └── spans
      │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x01")
      └── filters
           └── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4) OR (st_intersects('0101000020E61000009279E40F061E44C0BEE36FD63B9D5140', geog:4) AND st_crosses('0102000000020000000000000000000000000000000000000000000000000000000000000000000040', geom:3)) [outer=(3,4), immutable, constraints=(/4: (/NULL - ])]

# Filters on other columns.
opt expect=GenerateInvertedIndexScans
SELECT k FROM g
WHERE ST_Covers('SRID=4326;POINT(-43.23456 72.4567772)'::geography, geog)
AND v = 3 AND k > 100
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null v:2!null geog:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(2), (1)-->(4)
      ├── index-join g
      │    ├── columns: k:1!null v:2 geog:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,4)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /8
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │         │         └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4)
      │         ├── key: (1)
      │         └── select
      │              ├── columns: k:1!null geog_inverted_key:8!null
      │              ├── scan g@geog_idx,inverted
      │              │    ├── columns: k:1!null geog_inverted_key:8!null
      │              │    └── inverted constraint: /8/1
      │              │         └── spans
      │              │              ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
      │              │              ├── ["B\xfdN\x00\x00\x00\x00\x00\x00\x01", "B\xfdP\x00\x00\x00\x00\x00\x00\x00")
      │              │              └── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
      │              └── filters
      │                   └── k:1 > 100 [outer=(1), constraints=(/1: [/101 - ]; tight)]
      └── filters
           ├── st_covers('0101000020E61000009279E40F069E45C0BEE36FD63B1D5240', geog:4) [outer=(4), immutable, constraints=(/4: (/NULL - ])]
           └── v:2 = 3 [outer=(2), constraints=(/2: [/3 - /3]; tight), fd=()-->(2)]

# Bounding box operations.
opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE 'BOX(1 2, 3 4)'::box2d ~ geom
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x18\x00\x00\x00\x00\x00\x00\x00")
      │         ├── pre-filterer expression
      │         │    └── st_covers('01030000000100000005000000000000000000F03F0000000000000040000000000000F03F00000000000010400000000000000840000000000000104000000000000008400000000000000040000000000000F03F0000000000000040', geom:3)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x18\x00\x00\x00\x00\x00\x00\x00")
      └── filters
           └── 'BOX(1 2,3 4)' ~ geom:3 [outer=(3), immutable]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE geom ~ 'BOX(1 2, 3 4)'::box2d
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_coveredby('01030000000100000005000000000000000000F03F0000000000000040000000000000F03F00000000000010400000000000000840000000000000104000000000000008400000000000000040000000000000F03F0000000000000040', geom:3)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── geom:3 ~ 'BOX(1 2,3 4)' [outer=(3), immutable, constraints=(/3: (/NULL - ])]

opt expect=GenerateInvertedIndexScans
SELECT k FROM g WHERE geom ~ 'MULTIPOINT((2.2 2.2), (3.0 3.0))'::geometry
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null geom:3!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── index-join g
      │    ├── columns: k:1!null geom:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /7
      │         │    ├── tight: false, unique: false
      │         │    ├── union spans
      │         │    │    ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │         │    │    └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
      │         │    └── INTERSECTION
      │         │         ├── span expression
      │         │         │    ├── tight: false, unique: false
      │         │         │    └── union spans: ["B\xfd\x15\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x15\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── span expression
      │         │              ├── tight: false, unique: false
      │         │              └── union spans: ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_coveredby('01040000000200000001010000009A999999999901409A99999999990140010100000000000000000008400000000000000840', geom:3)
      │         ├── key: (1)
      │         └── scan g@geom_idx,inverted
      │              ├── columns: k:1!null geom_inverted_key:7!null
      │              └── inverted constraint: /7/1
      │                   └── spans
      │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfd\x15\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x15\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── geom:3 ~ '01040000000200000001010000009A999999999901409A99999999990140010100000000000000000008400000000000000840' [outer=(3), immutable, constraints=(/3: (/NULL - ])]

# Tests for partial inverted indexes.

exec-ddl
CREATE TABLE pi (
    k INT PRIMARY KEY,
    s STRING,
    j JSON
)
----

# Partial inverted index scan.

exec-ddl
CREATE INVERTED INDEX idx ON pi (j) WHERE s = 'foo'
----

opt expect=GenerateInvertedIndexScans
SELECT k FROM pi WHERE j @> '{"a": "b"}' AND s = 'foo'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan pi@idx,inverted,partial
      ├── columns: k:1!null
      ├── inverted constraint: /6/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

exec-ddl
DROP INDEX idx
----

# Partial inverted index scan with indexed column in predicate.

exec-ddl
CREATE INVERTED INDEX idx ON pi (j) WHERE j @> '{"group": 1}'
----

opt expect=GenerateInvertedIndexScans
SELECT k FROM pi WHERE j @> '{"a": "b"}' AND j @> '{"group": 1}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan pi@idx,inverted,partial
      ├── columns: k:1!null
      ├── inverted constraint: /7/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

exec-ddl
DROP INDEX idx
----

# Partial inverted index scan with index-join.

exec-ddl
CREATE INVERTED INDEX idx ON pi (j) WHERE s = 'foo'
----

opt expect=GenerateInvertedIndexScans
SELECT * FROM pi WHERE j @> '{"a": "b"}' AND s = 'foo'
----
index-join pi
 ├── columns: k:1!null s:2!null j:3!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3)
 └── scan pi@idx,inverted,partial
      ├── columns: k:1!null
      ├── inverted constraint: /8/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

exec-ddl
DROP INDEX idx
----

# Partial inverted index scan with additional filters.

exec-ddl
CREATE INVERTED INDEX idx ON pi (j) WHERE s IN ('foo', 'bar')
----

opt expect=GenerateInvertedIndexScans
SELECT * FROM pi WHERE j @> '{"a": "b"}' AND j @> '{"group": 1}' AND s = 'foo'
----
select
 ├── columns: k:1!null s:2!null j:3!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3)
 ├── index-join pi
 │    ├── columns: k:1!null s:2 j:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /9
 │         │    ├── tight: false, unique: true
 │         │    ├── union spans: empty
 │         │    └── INTERSECTION
 │         │         ├── span expression
 │         │         │    ├── tight: true, unique: true
 │         │         │    └── union spans: ["a"/"b", "a"/"b"]
 │         │         └── span expression
 │         │              ├── tight: true, unique: true
 │         │              └── union spans: ["group"/1, "group"/1]
 │         ├── key: (1)
 │         └── scan pi@idx,inverted,partial
 │              ├── columns: k:1!null j_inverted_key:9!null
 │              ├── inverted constraint: /9/1
 │              │    └── spans
 │              │         ├── ["a"/"b", "a"/"b"]
 │              │         └── ["group"/1, "group"/1]
 │              └── key: (1,9)
 └── filters
      └── s:2 = 'foo' [outer=(2), constraints=(/2: [/'foo' - /'foo']; tight), fd=()-->(2)]

exec-ddl
DROP INDEX idx
----

# Generate multiple partial inverted index scans in the memo when the filter
# implies multiple partial index predicates.

exec-ddl
CREATE INVERTED INDEX idx ON pi (j) WHERE s IN ('foo', 'bar')
----

exec-ddl
CREATE INVERTED INDEX idx2 ON pi (j) WHERE s = 'bar'
----

memo expect=GenerateInvertedIndexScans
SELECT * FROM pi WHERE j @> '{"a": "b"}' AND s = 'bar'
----
memo (optimized, ~15KB, required=[presentation: k:1,s:2,j:3])
 ├── G1: (select G2 G3) (select G4 G5) (index-join G6 pi,cols=(1-3))
 │    └── [presentation: k:1,s:2,j:3]
 │         ├── best: (index-join G6 pi,cols=(1-3))
 │         └── cost: 25.89
 ├── G2: (scan pi,cols=(1-3))
 │    └── []
 │         ├── best: (scan pi,cols=(1-3))
 │         └── cost: 1088.62
 ├── G3: (filters G7 G8)
 ├── G4: (index-join G9 pi,cols=(1-3))
 │    └── []
 │         ├── best: (index-join G9 pi,cols=(1-3))
 │         └── cost: 33.80
 ├── G5: (filters G8)
 ├── G6: (scan pi@idx2,inverted,partial,cols=(1),constrained inverted)
 │    └── []
 │         ├── best: (scan pi@idx2,inverted,partial,cols=(1),constrained inverted)
 │         └── cost: 19.16
 ├── G7: (contains G10 G11)
 ├── G8: (eq G12 G13)
 ├── G9: (scan pi@idx,inverted,partial,cols=(1),constrained inverted)
 │    └── []
 │         ├── best: (scan pi@idx,inverted,partial,cols=(1),constrained inverted)
 │         └── cost: 20.31
 ├── G10: (variable j)
 ├── G11: (const '{"a": "b"}')
 ├── G12: (variable s)
 └── G13: (const 'bar')

exec-ddl
DROP INDEX idx
----

exec-ddl
DROP INDEX idx2
----

# Do not generate a partial inverted index scan when the predicate is not
# implied by the filter.

exec-ddl
CREATE INVERTED INDEX idx ON pi (j) WHERE s IN ('foo', 'bar')
----

memo expect-not=GenerateInvertedIndexScans
SELECT * FROM pi WHERE j @> '{"a": "b"}' AND s = 'baz'
----
memo (optimized, ~9KB, required=[presentation: k:1,s:2,j:3])
 ├── G1: (select G2 G3)
 │    └── [presentation: k:1,s:2,j:3]
 │         ├── best: (select G2 G3)
 │         └── cost: 1098.66
 ├── G2: (scan pi,cols=(1-3))
 │    └── []
 │         ├── best: (scan pi,cols=(1-3))
 │         └── cost: 1088.62
 ├── G3: (filters G4 G5)
 ├── G4: (contains G6 G7)
 ├── G5: (eq G8 G9)
 ├── G6: (variable j)
 ├── G7: (const '{"a": "b"}')
 ├── G8: (variable s)
 └── G9: (const 'baz')

exec-ddl
DROP INDEX idx
----

# Check that we can generate constraints by recognizing computed column
# expressions.
opt expect=GenerateInvertedIndexScans
SELECT k FROM computed WHERE (j->'foo') @> '{"a": "b"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan computed@field_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /9/1
      │    └── spans: ["a"/"b", "a"/"b"]
      └── key: (1)

opt expect=GenerateInvertedIndexScans
SELECT k FROM computed WHERE (j->'foo')->'a' = '1'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── scan computed@field_inv_idx,inverted
      ├── columns: k:1!null
      ├── inverted constraint: /9/1
      │    └── spans: ["a"/1, "a"/1]
      └── key: (1)

# Tests for multi-column inverted indexes.

exec-ddl
CREATE TABLE m (
  k INT PRIMARY KEY,
  a STRING,
  b STRING,
  i INT,
  geom GEOMETRY,
  INVERTED INDEX multicol (a, geom)
)
----

# Constrain a single prefix column to one value.
opt expect=GenerateInvertedIndexScans
SELECT * FROM m WHERE a = 'foo' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3 i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3-5)
 ├── index-join m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /8
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         ├── pre-filterer expression
 │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
 │         ├── key: (1)
 │         └── scan m@multicol,inverted
 │              ├── columns: k:1!null geom_inverted_key:8!null
 │              ├── constraint: /2: [/'foo' - /'foo']
 │              └── inverted constraint: /8/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Constrain a single prefix column to multiple point values.
opt expect=GenerateInvertedIndexScans
SELECT * FROM m WHERE a IN ('foo', 'bar') AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3 i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── index-join m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /8
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         ├── pre-filterer expression
 │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
 │         ├── key: (1)
 │         └── scan m@multicol,inverted
 │              ├── columns: k:1!null geom_inverted_key:8!null
 │              ├── constraint: /2
 │              │    ├── [/'bar' - /'bar']
 │              │    └── [/'foo' - /'foo']
 │              └── inverted constraint: /8/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Do not generate an inverted index scan when the first prefix column is not
# constrained.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM m WHERE ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2 b:3 i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Do not generate an inverted index scan when a single prefix column is
# constrained to a range.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM m WHERE a > 'x' AND a < 'y' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3 i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      ├── (a:2 > 'x') AND (a:2 < 'y') [outer=(2), constraints=(/2: [/e'x\x00' - /'y'); tight)]
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

exec-ddl
DROP INDEX multicol
----

exec-ddl
CREATE INVERTED INDEX multicol ON m (a, b, geom)
----

# Constrain multiple prefix columns to one value.
opt expect=GenerateInvertedIndexScans
SELECT * FROM m WHERE a = 'foo' AND b = 'bar' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3!null i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4,5)
 ├── index-join m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /9
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         ├── pre-filterer expression
 │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
 │         ├── key: (1)
 │         └── scan m@multicol,inverted
 │              ├── columns: k:1!null geom_inverted_key:9!null
 │              ├── constraint: /2/3: [/'foo'/'bar' - /'foo'/'bar']
 │              └── inverted constraint: /9/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Constrain multiple prefix columns to multiple point values.
opt expect=GenerateInvertedIndexScans
SELECT * FROM m WHERE a IN ('foo', 'bar') AND b IN ('baz', 'bob') AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3!null i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── index-join m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /9
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         ├── pre-filterer expression
 │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
 │         ├── key: (1)
 │         └── scan m@multicol,inverted
 │              ├── columns: k:1!null geom_inverted_key:9!null
 │              ├── constraint: /2/3
 │              │    ├── [/'bar'/'baz' - /'bar'/'baz']
 │              │    ├── [/'bar'/'bob' - /'bar'/'bob']
 │              │    ├── [/'foo'/'baz' - /'foo'/'baz']
 │              │    └── [/'foo'/'bob' - /'foo'/'bob']
 │              └── inverted constraint: /9/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Do not generate an inverted index scan when the first prefix column is not
# constrained.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM m WHERE b = 'foo' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2 b:3!null i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2,4,5)
 ├── scan m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      ├── b:3 = 'foo' [outer=(3), constraints=(/3: [/'foo' - /'foo']; tight), fd=()-->(3)]
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Do not generate an inverted index scan when the second prefix column is not
# constrained.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM m WHERE a = 'foo' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3 i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3-5)
 ├── scan m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      ├── a:2 = 'foo' [outer=(2), constraints=(/2: [/'foo' - /'foo']; tight), fd=()-->(2)]
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Do not generate an inverted index scan when the first prefix column is
# constrained to a range.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM m WHERE a > 'x' AND a < 'y' AND b = 'foo' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3!null i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2,4,5)
 ├── scan m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      ├── (a:2 > 'x') AND (a:2 < 'y') [outer=(2), constraints=(/2: [/e'x\x00' - /'y'); tight)]
      ├── b:3 = 'foo' [outer=(3), constraints=(/3: [/'foo' - /'foo']; tight), fd=()-->(3)]
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Do not generate an inverted index scan when the second prefix column is
# constrained to a range.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM m WHERE a = 'foo' AND b > 'x' AND b < 'y' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3!null i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3-5)
 ├── scan m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      ├── (b:3 > 'x') AND (b:3 < 'y') [outer=(3), constraints=(/3: [/e'x\x00' - /'y'); tight)]
      ├── a:2 = 'foo' [outer=(2), constraints=(/2: [/'foo' - /'foo']; tight), fd=()-->(2)]
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Do not generate an inverted index scan when neither prefix column is constrained.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM m WHERE ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2 b:3 i:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5)
 ├── scan m
 │    ├── columns: k:1!null a:2 b:3 i:4 geom:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

exec-ddl
DROP INDEX multicol
----

exec-ddl
CREATE INVERTED INDEX multicol ON m (i, geom)
----

# Generate an inverted index scan when the prefix column is constrained to a
# contiguous range.
opt expect=GenerateInvertedIndexScans
SELECT k FROM m WHERE i IN (1, 2, 3) AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null i:4!null geom:5!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4,5)
      ├── index-join m
      │    ├── columns: k:1!null i:4 geom:5
      │    ├── key: (1)
      │    ├── fd: (1)-->(4,5)
      │    └── inverted-filter
      │         ├── columns: k:1!null
      │         ├── inverted expression: /10
      │         │    ├── tight: false, unique: false
      │         │    └── union spans
      │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
      │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
      │         ├── pre-filterer expression
      │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
      │         ├── key: (1)
      │         └── scan m@multicol,inverted
      │              ├── columns: k:1!null geom_inverted_key:10!null
      │              ├── constraint: /4
      │              │    ├── [/1 - /1]
      │              │    ├── [/2 - /2]
      │              │    └── [/3 - /3]
      │              └── inverted constraint: /10/1
      │                   └── spans
      │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
      │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
      │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
      └── filters
           └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

exec-ddl
CREATE TABLE mc (
  k INT PRIMARY KEY,
  a STRING NOT NULL,
  b STRING NOT NULL,
  c STRING AS (upper(a)) STORED,
  geom GEOMETRY,
  CHECK (b IN ('foo', 'bar', 'baz'))
)
----

exec-ddl
CREATE INVERTED INDEX mc_idx ON mc (b, geom)
----

# Constrain the first prefix column with a CHECK constraint.
opt expect=GenerateInvertedIndexScans
SELECT * FROM mc WHERE ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3!null c:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5), (2)-->(4)
 ├── index-join mc
 │    ├── columns: k:1!null a:2!null b:3!null c:4 geom:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5), (2)-->(4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /8
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         ├── pre-filterer expression
 │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
 │         ├── key: (1)
 │         └── scan mc@mc_idx,inverted
 │              ├── columns: k:1!null geom_inverted_key:8!null
 │              ├── constraint: /3
 │              │    ├── [/'bar' - /'bar']
 │              │    ├── [/'baz' - /'baz']
 │              │    └── [/'foo' - /'foo']
 │              └── inverted constraint: /8/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

exec-ddl
DROP INDEX mc_idx
----

exec-ddl
CREATE INVERTED INDEX mc_idx ON mc (a, b, geom)
----

# Constrain the second prefix column with a CHECK constraint.
opt expect=GenerateInvertedIndexScans
SELECT * FROM mc WHERE a = 'x' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3!null c:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(2,4), (1)-->(3,5)
 ├── index-join mc
 │    ├── columns: k:1!null a:2!null b:3!null c:4 geom:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5), (2)-->(4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /9
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         ├── pre-filterer expression
 │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
 │         ├── key: (1)
 │         └── scan mc@mc_idx,inverted
 │              ├── columns: k:1!null geom_inverted_key:9!null
 │              ├── constraint: /2/3
 │              │    ├── [/'x'/'bar' - /'x'/'bar']
 │              │    ├── [/'x'/'baz' - /'x'/'baz']
 │              │    └── [/'x'/'foo' - /'x'/'foo']
 │              └── inverted constraint: /9/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

exec-ddl
DROP INDEX mc_idx
----

exec-ddl
CREATE INVERTED INDEX mc_idx ON mc (c, geom)
----

# Constrain a prefix column that is computed from a column with a constant
# filter.
opt expect=GenerateInvertedIndexScans
SELECT * FROM mc WHERE a = 'foo' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3!null c:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: ()-->(2,4), (1)-->(3,5)
 ├── index-join mc
 │    ├── columns: k:1!null a:2!null b:3!null c:4 geom:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5), (2)-->(4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /10
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         ├── pre-filterer expression
 │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
 │         ├── key: (1)
 │         └── scan mc@mc_idx,inverted
 │              ├── columns: k:1!null geom_inverted_key:10!null
 │              ├── constraint: /4: [/'FOO' - /'FOO']
 │              └── inverted constraint: /10/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 └── filters
      ├── a:2 = 'foo' [outer=(2), constraints=(/2: [/'foo' - /'foo']; tight), fd=()-->(2)]
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

# Constrain a prefix computed column when the computed expression matches a
# filter expression.
opt expect=GenerateInvertedIndexScans
SELECT * FROM mc WHERE upper(a) = 'FOO' AND ST_CoveredBy('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'::geometry, geom)
----
select
 ├── columns: k:1!null a:2!null b:3!null c:4 geom:5!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-5), (2)-->(4)
 ├── index-join mc
 │    ├── columns: k:1!null a:2!null b:3!null c:4 geom:5
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-5), (2)-->(4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /10
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 │         ├── pre-filterer expression
 │         │    └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5)
 │         ├── key: (1)
 │         └── scan mc@mc_idx,inverted
 │              ├── columns: k:1!null geom_inverted_key:10!null
 │              ├── constraint: /4: [/'FOO' - /'FOO']
 │              └── inverted constraint: /10/1
 │                   └── spans
 │                        ├── ["B\xfd\x10\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x10\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x11\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x11\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\x14\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x14\x00\x00\x00\x00\x00\x00\x00"]
 └── filters
      └── st_coveredby('0103000000010000000500000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000', geom:5) [outer=(5), immutable, constraints=(/5: (/NULL - ])]

exec-ddl
DROP INDEX mc_idx
----

# Regression test for #59702. Do not panic when empty index spans are generated
# from a filter on an inverted column.
exec-ddl
CREATE TABLE t59702 (
  k INT PRIMARY KEY,
  g GEOGRAPHY,
  INVERTED INDEX (g)
)
----

opt
SELECT * FROM t59702 WHERE st_intersects(g, st_buffer(st_makepoint(1, 1)::GEOGRAPHY, 0))
----
select
 ├── columns: k:1!null g:2!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── scan t59702
 │    ├── columns: k:1!null g:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters
      └── st_intersects(g:2, '0103000020E610000000000000') [outer=(2), immutable, constraints=(/2: (/NULL - ])]

# Regression test for #60527. Do not panic when a geospatial function has a NULL
# GEOMETRY/GEOGRAPHY argument.
exec-ddl
CREATE TABLE t60527 (
  g GEOGRAPHY,
  INVERTED INDEX (g)
)
----

# FoldFunctionWithNullArg is disabled to prevent the expression from being
# normalized to an empty Values expression and avoiding
# GenerateInvertedIndexScans altogether.
opt disable=FoldFunctionWithNullArg
SELECT * FROM t60527 WHERE (ST_Covers(NULL::GEOGRAPHY, g) AND ST_DWithin(NULL::GEOGRAPHY, g, 1))
----
select
 ├── columns: g:1!null
 ├── immutable
 ├── scan t60527
 │    └── columns: g:1
 └── filters
      ├── st_covers(CAST(NULL AS GEOGRAPHY), g:1) [outer=(1), immutable, constraints=(/1: (/NULL - ])]
      └── st_dwithin(CAST(NULL AS GEOGRAPHY), g:1, 1.0) [outer=(1), immutable, constraints=(/1: (/NULL - ])]

# Regression test for #62686. Do not panic when a geospatial function has a NULL
# additional argument.
exec-ddl
CREATE TABLE t62686 (
  c GEOMETRY NULL,
  INVERTED INDEX (c ASC)
)
----

# FoldFunctionWithNullArg is disabled to prevent the expression from being
# normalized to an empty Values expression and avoiding
# GenerateInvertedIndexScans altogether.
opt disable=FoldFunctionWithNullArg
SELECT * FROM t62686 WHERE ST_DFullyWithin(c, ST_GeomFromText('POINT(1 1)'), NULL::FLOAT8)
----
select
 ├── columns: c:1!null
 ├── immutable
 ├── scan t62686
 │    └── columns: c:1
 └── filters
      └── st_dfullywithin(c:1, '0101000000000000000000F03F000000000000F03F', CAST(NULL AS FLOAT8)) [outer=(1), immutable, constraints=(/1: (/NULL - ])]

# Regression test for #63180. Ensure that we only scan spans needed by the
# inverted filter.
exec-ddl
CREATE TABLE t63180 (
  j JSON,
  INVERTED INDEX j_idx (j)
)
----

opt expect=GenerateInvertedIndexScans
SELECT j FROM t63180@j_idx WHERE j @> '{"a": "c"}' AND (j @> '{"a": "b"}' OR j @> '{"a": "c"}');
----
index-join t63180
 ├── columns: j:1!null
 ├── immutable
 └── inverted-filter
      ├── columns: rowid:2!null
      ├── inverted expression: /5
      │    ├── tight: true, unique: false
      │    └── union spans: ["a"/"c", "a"/"c"]
      ├── key: (2)
      └── scan t63180@j_idx,inverted
           ├── columns: rowid:2!null j_inverted_key:5!null
           ├── inverted constraint: /5/2
           │    └── spans: ["a"/"c", "a"/"c"]
           ├── flags: force-index=j_idx
           ├── key: (2)
           └── fd: (2)-->(5)

exec-ddl
CREATE TABLE geom_geog (
  geom GEOMETRY,
  geog GEOGRAPHY,
  val FLOAT,
  INVERTED INDEX(geom),
  INVERTED INDEX(geog)
)
----

# Tests for pre-existing normalization rules FoldEqZeroSTDistance,
# FoldCmpSTDistanceLeft, FoldCmpSTDistanceRight, FoldCmpSTMaxDistanceLeft and
# FoldCmpSTMaxDistanceRight have been moved here, and converted into tests
# detecting whether GenerateInvertedIndexScans was fired due to derivation
# of a new filter condition, used only internally when determining the
# inverted index scan spans to use.


# Tests related to old normalization rule FoldEqZeroSTDistance

# Geometry case.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geom, 'POINT(0.0 0.0)') = 0
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         ├── pre-filterer expression
 │         │    └── st_intersects('010100000000000000000000000000000000000000', geom:1)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geom_idx,inverted
 │              ├── columns: rowid:4!null geom_inverted_key:7!null
 │              └── inverted constraint: /7/4
 │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 └── filters
      └── st_distance(geom:1, '010100000000000000000000000000000000000000') = 0.0 [outer=(1), immutable]

# Geography case with use_spheroid=false.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)', false) = 0
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /8
 │         │    ├── tight: false, unique: false
 │         │    └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         ├── pre-filterer expression
 │         │    └── st_intersects('0101000020E610000000000000000000000000000000000000', geog:2)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geog_idx,inverted
 │              ├── columns: rowid:4!null geog_inverted_key:8!null
 │              └── inverted constraint: /8/4
 │                   └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 └── filters
      └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', false) = 0.0 [outer=(2), immutable]

# No-op case because the constant is nonzero.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geom, 'POINT(0.0 0.0)') = 1
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── st_distance(geom:1, '010100000000000000000000000000000000000000') = 1.0 [outer=(1), immutable]

# No-op case because use_spheroid=true implicitly.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)') = 0
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000') = 0.0 [outer=(2), immutable]

# No-op case because use_spheroid=true.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)', true) = 0
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', true) = 0.0 [outer=(2), immutable]

# Tests related to old normalization rule FoldCmpSTDistanceLeft

# Geometry case with '<=' operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') <= 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         │         └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 │         ├── pre-filterer expression
 │         │    └── st_dwithin('010100000000000000000000000000000000000000', geom:1, 5.0)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geom_idx,inverted
 │              ├── columns: rowid:4!null geom_inverted_key:7!null
 │              └── inverted constraint: /7/4
 │                   └── spans
 │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │                        └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 └── filters
      └── st_distance(geom:1, '010100000000000000000000000000000000000000') <= 5.0 [outer=(1), immutable]

# Geometry case with '<' operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance('point(0.0 0.0)', geom) < 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         │         └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 │         ├── pre-filterer expression
 │         │    └── st_dwithinexclusive('010100000000000000000000000000000000000000', geom:1, 5.0)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geom_idx,inverted
 │              ├── columns: rowid:4!null geom_inverted_key:7!null
 │              └── inverted constraint: /7/4
 │                   └── spans
 │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │                        └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 └── filters
      └── st_distance('010100000000000000000000000000000000000000', geom:1) < 5.0 [outer=(1), immutable]

# Geometry case with '>=' operator.
# Inverted index scan not expected because derived expression is
# NOT st_dwithinexclusive.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') >= 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── st_distance(geom:1, '010100000000000000000000000000000000000000') >= 5.0 [outer=(1), immutable]

# Geometry case with '>' operator.
# Inverted index scan not expected because derived expression is NOT st_dwithin.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') > 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── st_distance(geom:1, '010100000000000000000000000000000000000000') > 5.0 [outer=(1), immutable]

# Geography case with '<=' operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') <= 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /8
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00")
 │         ├── pre-filterer expression
 │         │    └── st_dwithin('0101000020E610000000000000000000000000000000000000', geog:2, 5.0)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geog_idx,inverted
 │              ├── columns: rowid:4!null geog_inverted_key:8!null
 │              └── inverted constraint: /8/4
 │                   └── spans
 │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00")
 └── filters
      └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000') <= 5.0 [outer=(2), immutable]

# Geography case with '<' operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /8
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00")
 │         ├── pre-filterer expression
 │         │    └── st_dwithin('0101000020E610000000000000000000000000000000000000', geog:2, 5.0)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geog_idx,inverted
 │              ├── columns: rowid:4!null geog_inverted_key:8!null
 │              └── inverted constraint: /8/4
 │                   └── spans
 │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00")
 └── filters
      └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000') < 5.0 [outer=(2), immutable]

# Regression test for #54326. Ensure the distance param is cast to a float.
opt expect=GenerateInvertedIndexScans format=(show-scalars,show-types)
SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < 5::int
----
select
 ├── columns: geom:1(geometry) geog:2(geography) val:3(float)
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1(geometry) geog:2(geography) val:3(float)
 │    └── inverted-filter
 │         ├── columns: rowid:4(int!null)
 │         ├── inverted expression: /8
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00")
 │         ├── pre-filterer expression
 │         │    └── function: st_dwithin [type=bool]
 │         │         ├── const: '0101000020E610000000000000000000000000000000000000' [type=geography]
 │         │         ├── variable: geog:2 [type=geography]
 │         │         └── const: 5.0 [type=float]
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geog_idx,inverted
 │              ├── columns: rowid:4(int!null) geog_inverted_key:8(encodedkey!null)
 │              └── inverted constraint: /8/4
 │                   └── spans
 │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00")
 └── filters
      └── lt [type=bool, outer=(2), immutable]
           ├── function: st_distance [type=float]
           │    ├── variable: geog:2 [type=geography]
           │    └── const: '0101000020E610000000000000000000000000000000000000' [type=geography]
           └── const: 5 [type=int]

# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans format=(show-scalars,show-types)
SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < val::int
----
select
 ├── columns: geom:1(geometry) geog:2(geography) val:3(float)
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1(geometry) geog:2(geography) val:3(float)
 └── filters
      └── lt [type=bool, outer=(2,3), immutable]
           ├── function: st_distance [type=float]
           │    ├── variable: geog:2 [type=geography]
           │    └── const: '0101000020E610000000000000000000000000000000000000' [type=geography]
           └── cast: INT8 [type=int]
                └── variable: val:3 [type=float]

# Regression test for #55675. Handle use_spheroid param.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)', true) <= 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /8
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"]
 │         │         ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00")
 │         │         ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"]
 │         │         └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00")
 │         ├── pre-filterer expression
 │         │    └── st_dwithin('0101000020E610000000000000000000000000000000000000', geog:2, 5.0, true)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geog_idx,inverted
 │              ├── columns: rowid:4!null geog_inverted_key:8!null
 │              └── inverted constraint: /8/4
 │                   └── spans
 │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"]
 │                        ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00")
 │                        ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"]
 │                        └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00")
 └── filters
      └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', true) <= 5.0 [outer=(2), immutable]

# Tests related to old normalization rule FoldCmpSTDistanceRight

# Case with '<=' operator.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val <= st_distance(geom, 'point(0.0 0.0)')
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 <= st_distance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])]

# Case with '<' operator.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val < st_distance(geom, 'point(0.0 0.0)')
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 < st_distance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])]

# Case with '>=' operator.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val >= st_distance(geom, 'point(0.0 0.0)')
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 >= st_distance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])]

# Case with '>' operator.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val > st_distance(geom, 'point(0.0 0.0)')
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 > st_distance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])]

# Regression test for #55675. Handle use_spheroid param.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val > st_distance(geog, 'point(0.0 0.0)', false)
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 > st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', false) [outer=(2,3), immutable, constraints=(/3: (/NULL - ])]

# Tests related to old normalization rule FoldCmpSTMaxDistanceLeft

# Case with '<=' operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') <= 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         │         └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 │         ├── pre-filterer expression
 │         │    └── st_dfullywithin('010100000000000000000000000000000000000000', geom:1, 5.0)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geom_idx,inverted
 │              ├── columns: rowid:4!null geom_inverted_key:7!null
 │              └── inverted constraint: /7/4
 │                   └── spans
 │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │                        └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 └── filters
      └── st_maxdistance(geom:1, '010100000000000000000000000000000000000000') <= 5.0 [outer=(1), immutable]

# Case with '<' operator.
opt expect=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_maxdistance('point(0.0 0.0)', geom) < 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── index-join geom_geog
 │    ├── columns: geom:1 geog:2 val:3
 │    └── inverted-filter
 │         ├── columns: rowid:4!null
 │         ├── inverted expression: /7
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │         │         └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 │         ├── pre-filterer expression
 │         │    └── st_dfullywithinexclusive('010100000000000000000000000000000000000000', geom:1, 5.0)
 │         ├── key: (4)
 │         └── scan geom_geog@geom_geog_geom_idx,inverted
 │              ├── columns: rowid:4!null geom_inverted_key:7!null
 │              └── inverted constraint: /7/4
 │                   └── spans
 │                        ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00")
 │                        └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"]
 └── filters
      └── st_maxdistance('010100000000000000000000000000000000000000', geom:1) < 5.0 [outer=(1), immutable]

# Case with '>=' operator.
# Inverted index scan not expected because derived expression is
# NOT st_dwithinexclusive.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') >= 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── st_maxdistance(geom:1, '010100000000000000000000000000000000000000') >= 5.0 [outer=(1), immutable]

# Case with '>' operator.
# Inverted index scan not expected because derived expression is NOT st_within.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') > 5
----
select
 ├── columns: geom:1 geog:2 val:3
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── st_maxdistance(geom:1, '010100000000000000000000000000000000000000') > 5.0 [outer=(1), immutable]

# Tests related to old normalization rule FoldCmpSTMaxDistanceRight

# Case with '<=' operator.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val <= st_maxdistance(geom, 'point(0.0 0.0)')
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 <= st_maxdistance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])]

# Case with '<' operator.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val < st_maxdistance(geom, 'point(0.0 0.0)')
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 < st_maxdistance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])]

# Case with '>=' operator.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val >= st_maxdistance(geom, 'point(0.0 0.0)')
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 >= st_maxdistance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])]

# Case with '>' operator.
# Inverted index scan not expected because `st_distance` result compared
# with column.
opt expect-not=GenerateInvertedIndexScans
SELECT * FROM geom_geog WHERE val > st_maxdistance(geom, 'point(0.0 0.0)')
----
select
 ├── columns: geom:1 geog:2 val:3!null
 ├── immutable
 ├── scan geom_geog
 │    └── columns: geom:1 geog:2 val:3
 └── filters
      └── val:3 > st_maxdistance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])]

opt expect=GenerateInvertedIndexScans set=(optimizer_use_trigram_similarity_optimization=off)
SELECT * FROM trgm WHERE s % 'foo'
----
select
 ├── columns: k:1!null s:2
 ├── stable
 ├── key: (1)
 ├── fd: (1)-->(2)
 ├── index-join trgm
 │    ├── columns: k:1!null s:2
 │    ├── key: (1)
 │    ├── fd: (1)-->(2)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /5
 │         │    ├── tight: false, unique: false
 │         │    └── union spans
 │         │         ├── ["  f", "  f"]
 │         │         ├── [" fo", " fo"]
 │         │         ├── ["foo", "foo"]
 │         │         └── ["oo ", "oo "]
 │         ├── key: (1)
 │         └── scan trgm@s_idx,inverted
 │              ├── columns: k:1!null s_inverted_key:5!null
 │              └── inverted constraint: /5/1
 │                   └── spans
 │                        ├── ["  f", "  f"]
 │                        ├── [" fo", " fo"]
 │                        ├── ["foo", "foo"]
 │                        └── ["oo ", "oo "]
 └── filters
      └── s:2 % 'foo' [outer=(2), stable]

opt expect-not=GenerateInvertedIndexScans format=hide-all
SELECT * FROM trgm WHERE s % 'foo'
----
select
 ├── index-join trgm
 │    └── distinct-on
 │         └── scan trgm@s_idx,inverted
 │              └── constraint: /5
 │                   ├── [/" fo" - /" fo"]
 │                   ├── [/"foo" - /"foo"]
 │                   └── [/"oo " - /"oo "]
 └── filters
      └── s % 'foo'

# Regression test for #111963. Do not plan inverted index scans on columns that
# are not filtered in the query.
exec-ddl
CREATE TABLE t111963 (
  j1 JSON,
  j2 JSON
)
----

exec-ddl
CREATE INVERTED INDEX idx111963 ON t111963 (j1)
----

opt expect-not=GenerateInvertedIndexScans
SELECT * FROM t111963 WHERE j2 = '1'
----
select
 ├── columns: j1:1 j2:2!null
 ├── immutable
 ├── fd: ()-->(2)
 ├── scan t111963
 │    └── columns: j1:1 j2:2
 └── filters
      └── j2:2 = '1' [outer=(2), immutable, constraints=(/2: [/'1' - /'1']; tight), fd=()-->(2)]

opt expect-not=GenerateInvertedIndexScans
SELECT * FROM t111963 WHERE j2 IN ('1', '10', '100')
----
select
 ├── columns: j1:1 j2:2!null
 ├── scan t111963
 │    └── columns: j1:1 j2:2
 └── filters
      └── j2:2 IN ('1', '10', '100') [outer=(2), constraints=(/2: [/'1' - /'1'] [/'10' - /'10'] [/'100' - /'100']; tight)]

exec-ddl
DROP INDEX idx111963
----

exec-ddl
CREATE INVERTED INDEX idx111963 ON t111963 ((j1->'foo'))
----

opt expect-not=GenerateInvertedIndexScans
SELECT * FROM t111963 WHERE j1 = '1'
----
select
 ├── columns: j1:1!null j2:2
 ├── immutable
 ├── fd: ()-->(1)
 ├── scan t111963
 │    ├── columns: j1:1 j2:2
 │    └── computed column expressions
 │         └── crdb_internal_idx_expr:7
 │              └── j1:1->'foo'
 └── filters
      └── j1:1 = '1' [outer=(1), immutable, constraints=(/1: [/'1' - /'1']; tight), fd=()-->(1)]

opt expect-not=GenerateInvertedIndexScans
SELECT * FROM t111963 WHERE j1 IN ('1', '10', '100')
----
select
 ├── columns: j1:1!null j2:2
 ├── scan t111963
 │    ├── columns: j1:1 j2:2
 │    └── computed column expressions
 │         └── crdb_internal_idx_expr:7
 │              └── j1:1->'foo'
 └── filters
      └── j1:1 IN ('1', '10', '100') [outer=(1), constraints=(/1: [/'1' - /'1'] [/'10' - /'10'] [/'100' - /'100']; tight)]

# Regression test for #122733. Do not construct an unnecessary index-join above
# the inverted index scan.
exec-ddl
CREATE TABLE t122733 (
  a STRING,
  b STRING,
  c STRING AS (b) VIRTUAL,
  PRIMARY KEY (a, b),
  INVERTED INDEX i122733 (a, c gin_trgm_ops)
)
----

opt expect=GenerateInvertedIndexScans
SELECT 1
FROM (SELECT 'foo' FROM t122733) AS tmp (f)
JOIN t122733@i122733 ON a = tmp.f AND c = tmp.f
----
project
 ├── columns: "?column?":14!null
 ├── fd: ()-->(14)
 ├── inner-join (cross)
 │    ├── columns: a:8!null b:9!null
 │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
 │    ├── fd: ()-->(8,9)
 │    ├── scan t122733
 │    │    └── computed column expressions
 │    │         └── c:3
 │    │              └── b:2
 │    ├── select
 │    │    ├── columns: a:8!null b:9!null
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(8,9)
 │    │    ├── scan t122733@i122733,inverted
 │    │    │    ├── columns: a:8!null b:9!null
 │    │    │    ├── constraint: /8: [/'foo' - /'foo']
 │    │    │    ├── inverted constraint: /13/9
 │    │    │    │    └── spans: ["foo", "foo"]
 │    │    │    ├── flags: force-index=i122733
 │    │    │    ├── key: (9)
 │    │    │    └── fd: ()-->(8)
 │    │    └── filters
 │    │         └── b:9 = 'foo' [outer=(9), constraints=(/9: [/'foo' - /'foo']; tight), fd=()-->(9)]
 │    └── filters (true)
 └── projections
      └── 1 [as="?column?":14]

opt expect=GenerateInvertedIndexScans
SELECT 1
FROM (SELECT 'foo', 'bar' FROM t122733) AS tmp (f, b)
JOIN t122733@i122733 ON a = tmp.f AND (c = tmp.f OR c = tmp.b)
----
project
 ├── columns: "?column?":15!null
 ├── fd: ()-->(15)
 ├── inner-join (cross)
 │    ├── columns: a:9!null b:10!null
 │    ├── fd: ()-->(9)
 │    ├── scan t122733
 │    │    └── computed column expressions
 │    │         └── c:3
 │    │              └── b:2
 │    ├── inverted-filter
 │    │    ├── columns: a:9!null b:10!null
 │    │    ├── inverted expression: /14
 │    │    │    ├── tight: false, unique: false
 │    │    │    └── union spans
 │    │    │         ├── ["bar", "bar"]
 │    │    │         └── ["foo", "foo"]
 │    │    ├── cardinality: [0 - 2]
 │    │    ├── key: (10)
 │    │    ├── fd: ()-->(9)
 │    │    └── select
 │    │         ├── columns: a:9!null b:10!null c_inverted_key:14!null
 │    │         ├── fd: ()-->(9)
 │    │         ├── scan t122733@i122733,inverted
 │    │         │    ├── columns: a:9!null b:10!null c_inverted_key:14!null
 │    │         │    ├── constraint: /9: [/'foo' - /'foo']
 │    │         │    ├── inverted constraint: /14/10
 │    │         │    │    └── spans
 │    │         │    │         ├── ["bar", "bar"]
 │    │         │    │         └── ["foo", "foo"]
 │    │         │    ├── flags: force-index=i122733
 │    │         │    └── fd: ()-->(9)
 │    │         └── filters
 │    │              └── (b:10 = 'foo') OR (b:10 = 'bar') [outer=(10), constraints=(/10: [/'bar' - /'bar'] [/'foo' - /'foo']; tight)]
 │    └── filters (true)
 └── projections
      └── 1 [as="?column?":15]

opt expect=GenerateInvertedIndexScans
SELECT 1
FROM (SELECT 'foo', 'bar' FROM t122733) AS tmp (f, b)
JOIN t122733@i122733 ON a = tmp.f AND (c = tmp.f OR c = tmp.b) AND c::INT > 5
----
project
 ├── columns: "?column?":15!null
 ├── immutable
 ├── fd: ()-->(15)
 ├── inner-join (cross)
 │    ├── columns: a:9!null b:10!null
 │    ├── immutable
 │    ├── fd: ()-->(9)
 │    ├── scan t122733
 │    │    └── computed column expressions
 │    │         └── c:3
 │    │              └── b:2
 │    ├── inverted-filter
 │    │    ├── columns: a:9!null b:10!null
 │    │    ├── inverted expression: /14
 │    │    │    ├── tight: false, unique: false
 │    │    │    └── union spans
 │    │    │         ├── ["bar", "bar"]
 │    │    │         └── ["foo", "foo"]
 │    │    ├── cardinality: [0 - 2]
 │    │    ├── immutable
 │    │    ├── key: (10)
 │    │    ├── fd: ()-->(9)
 │    │    └── select
 │    │         ├── columns: a:9!null b:10!null c_inverted_key:14!null
 │    │         ├── immutable
 │    │         ├── fd: ()-->(9)
 │    │         ├── scan t122733@i122733,inverted
 │    │         │    ├── columns: a:9!null b:10!null c_inverted_key:14!null
 │    │         │    ├── constraint: /9: [/'foo' - /'foo']
 │    │         │    ├── inverted constraint: /14/10
 │    │         │    │    └── spans
 │    │         │    │         ├── ["bar", "bar"]
 │    │         │    │         └── ["foo", "foo"]
 │    │         │    ├── flags: force-index=i122733
 │    │         │    └── fd: ()-->(9)
 │    │         └── filters
 │    │              ├── (b:10 = 'foo') OR (b:10 = 'bar') [outer=(10), constraints=(/10: [/'bar' - /'bar'] [/'foo' - /'foo']; tight)]
 │    │              └── b:10::INT8 > 5 [outer=(10), immutable]
 │    └── filters (true)
 └── projections
      └── 1 [as="?column?":15]


# --------------------------------------------------
# GenerateMinimalInvertedIndexScans
# --------------------------------------------------

exec-ddl
CREATE TABLE min (
  k INT PRIMARY KEY,
  j JSON,
  a INT[],
  INVERTED INDEX j_idx (j),
  INVERTED INDEX a_idx (a)
)
----

# Histogram boundaries are for arrays with values 1, 2, 3, and 4, including some
# empty arrays. The row_count is lower than the sum of the histogram buckets
# num_eq's because some rows can have multiple inverted index entries, for
# example `{1, 2}`. There are:
#
#   - 2000 rows total
#   - 10 empty arrays
#   - 1990 arrays encoded into 2020 index entries
#
# Histogram boundaries are for JSON values `[]`, `{}`, `[1]`, `[2]`, `[3]`,
# `{"a": "b"}`, `{"c": "d"}`, and `{"e": "f"}`. The row_count is lower than the
# sum of the histogram buckets num_eq's because some rows can have multiple
# inverted index entries, for example `{"a": "b", "c": "d"}`. There are:
#
#   - 2000 rows total
#   - 10 empty arrays
#   - 990 arrays encoded into 1110 index entries
#   - 10 empty objects
#   - 990 objects encoded into 1110 index entries
#
exec-ddl
ALTER TABLE min INJECT STATISTICS '[
  {
    "columns": ["a"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 3,
    "null_count": 0,
    "histo_col_type": "BYTES",
    "histo_buckets": [
      {
        "distinct_range": 0,
        "num_eq": 10,
        "num_range": 0,
        "upper_bound": "\\x43"
      },
      {
        "distinct_range": 0,
        "num_eq": 1990,
        "num_range": 0,
        "upper_bound": "\\x89"
      },
      {
        "distinct_range": 0,
        "num_eq": 10,
        "num_range": 0,
        "upper_bound": "\\x8a"
      },
      {
        "distinct_range": 0,
        "num_eq": 10,
        "num_range": 0,
        "upper_bound": "\\x8b"
      },
      {
        "distinct_range": 0,
        "num_eq": 10,
        "num_range": 0,
        "upper_bound": "\\x8c"
      }
    ]
  },
  {
    "columns": ["j"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 10,
    "null_count": 0,
    "histo_col_type": "BYTES",
    "histo_buckets": [
      {
        "distinct_range": 0,
        "num_eq": 10,
        "num_range": 0,
        "upper_bound": "\\x37000138"
      },
      {
        "distinct_range": 0,
        "num_eq": 10,
        "num_range": 0,
        "upper_bound": "\\x37000139"
      },
      {
        "distinct_range": 0,
        "num_eq": 990,
        "num_range": 0,
        "upper_bound": "\\x37000300012a0200"
      },
      {
        "distinct_range": 0,
        "num_eq": 100,
        "num_range": 0,
        "upper_bound": "\\x37000300012a0400"
      },
      {
        "distinct_range": 0,
        "num_eq": 10,
        "num_range": 0,
        "upper_bound": "\\x37000300012a0600"
      },
      {
        "distinct_range": 0,
        "num_eq": 990,
        "num_range": 0,
        "upper_bound": "\\x3761000112620001"
      },
      {
        "distinct_range": 0,
        "num_eq": 100,
        "num_range": 0,
        "upper_bound": "\\x3763000112640001"
      },
      {
        "distinct_range": 0,
        "num_eq": 10,
        "num_range": 0,
        "upper_bound": "\\x3765000112660001"
      }
    ]
  }
]'
----

# Scan over 3 since there are fewer rows containing 3 than 1.
# TODO(mgartner): The remaining filters could be reduced.
opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min WHERE a @> '{1}' AND a @> '{3}'
----
select
 ├── columns: k:1!null j:2 a:3!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan min@a_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /7/1
 │         │    └── spans: [3, 3]
 │         └── key: (1)
 └── filters
      ├── a:3 @> ARRAY[1] [outer=(3), immutable, constraints=(/3: (/NULL - ])]
      └── a:3 @> ARRAY[3] [outer=(3), immutable, constraints=(/3: (/NULL - ])]

# Scan over 2 since there are fewer rows containing 2 than 1.
opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min WHERE a @> '{1, 2}'
----
select
 ├── columns: k:1!null j:2 a:3!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan min@a_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /7/1
 │         │    └── spans: [2, 2]
 │         └── key: (1)
 └── filters
      └── a:3 @> ARRAY[1,2] [outer=(3), immutable, constraints=(/3: (/NULL - ])]

opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min@a_idx WHERE a @> '{2}' AND (a @> '{1}' OR a @> '{3}')
----
select
 ├── columns: k:1!null j:2 a:3!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan min@a_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /7/1
 │         │    └── spans: [2, 2]
 │         ├── flags: force-index=a_idx
 │         └── key: (1)
 └── filters
      ├── a:3 @> ARRAY[2] [outer=(3), immutable, constraints=(/3: (/NULL - ])]
      └── (a:3 @> ARRAY[1]) OR (a:3 @> ARRAY[3]) [outer=(3), immutable, constraints=(/3: (/NULL - ])]

opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min@a_idx WHERE (a @> '{2}' OR a @> '{4}') AND a @> '{1}'
----
select
 ├── columns: k:1!null j:2 a:3!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /7
 │         │    ├── tight: true, unique: false
 │         │    └── union spans
 │         │         ├── [2, 2]
 │         │         └── [4, 4]
 │         ├── key: (1)
 │         └── scan min@a_idx,inverted
 │              ├── columns: k:1!null a_inverted_key:7!null
 │              ├── inverted constraint: /7/1
 │              │    └── spans
 │              │         ├── [2, 2]
 │              │         └── [4, 4]
 │              └── flags: force-index=a_idx
 └── filters
      ├── (a:3 @> ARRAY[2]) OR (a:3 @> ARRAY[4]) [outer=(3), immutable, constraints=(/3: (/NULL - ])]
      └── a:3 @> ARRAY[1] [outer=(3), immutable, constraints=(/3: (/NULL - ])]

# TODO(mgartner): Scanning [2 - 3] would be better, but the current
# implementation can only estimate the row count for single-value spans.
opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min@a_idx WHERE (a @> '{2}' OR a @> '{3}') AND a @> '{1}'
----
index-join min
 ├── columns: k:1!null j:2 a:3!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: false
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: false
      │         │    └── union spans: [2, 4)
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: [1, 1]
      ├── key: (1)
      └── scan min@a_idx,inverted
           ├── columns: k:1!null a_inverted_key:7!null
           ├── inverted constraint: /7/1
           │    └── spans: [1, 4)
           └── flags: force-index=a_idx

opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min@a_idx WHERE a @> '{2, 3}' AND a @> '{1}'
----
select
 ├── columns: k:1!null j:2 a:3!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan min@a_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /7/1
 │         │    └── spans: [2, 2]
 │         ├── flags: force-index=a_idx
 │         └── key: (1)
 └── filters
      ├── a:3 @> ARRAY[2,3] [outer=(3), immutable, constraints=(/3: (/NULL - ])]
      └── a:3 @> ARRAY[1] [outer=(3), immutable, constraints=(/3: (/NULL - ])]

# The rule only applies when there are multiple spans to reduce.
opt expect-not=GenerateMinimalInvertedIndexScans format=hide-all
SELECT * FROM min WHERE a @> '{1}'
----
select
 ├── scan min
 └── filters
      └── a @> ARRAY[1]

opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min@j_idx WHERE j @> '[1]' AND j @> '[3]'
----
select
 ├── columns: k:1!null j:2!null a:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan min@j_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /6/1
 │         │    └── spans: [Arr/3, Arr/3]
 │         ├── flags: force-index=j_idx
 │         └── key: (1)
 └── filters
      ├── j:2 @> '[1]' [outer=(2), immutable, constraints=(/2: (/NULL - ])]
      └── j:2 @> '[3]' [outer=(2), immutable, constraints=(/2: (/NULL - ])]

opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min@j_idx WHERE j @> '[2]' AND j @> '[3]'
----
select
 ├── columns: k:1!null j:2!null a:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan min@j_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /6/1
 │         │    └── spans: [Arr/3, Arr/3]
 │         ├── flags: force-index=j_idx
 │         └── key: (1)
 └── filters
      ├── j:2 @> '[2]' [outer=(2), immutable, constraints=(/2: (/NULL - ])]
      └── j:2 @> '[3]' [outer=(2), immutable, constraints=(/2: (/NULL - ])]

# The rule only applies when there are multiple spans to reduce.
opt expect-not=GenerateMinimalInvertedIndexScans format=hide-all
SELECT * FROM min WHERE j @> '[3]'
----
index-join min
 └── scan min@j_idx,inverted
      └── inverted constraint: /6/1
           └── spans: [Arr/3, Arr/3]

# The rule does not apply when for a disjunction of spans.
opt expect-not=GenerateMinimalInvertedIndexScans format=hide-all
SELECT * FROM b WHERE j @> '[3]' OR j @> '[[1, 2]]'
----
select
 ├── index-join b
 │    └── inverted-filter
 │         ├── inverted expression: /9
 │         │    ├── tight: false, unique: true
 │         │    ├── union spans: [Arr/3, Arr/3]
 │         │    └── INTERSECTION
 │         │         ├── span expression
 │         │         │    ├── tight: true, unique: true
 │         │         │    └── union spans: [Arr/Arr/1, Arr/Arr/1]
 │         │         └── span expression
 │         │              ├── tight: true, unique: true
 │         │              └── union spans: [Arr/Arr/2, Arr/Arr/2]
 │         └── scan b@j_inv_idx,inverted
 │              └── inverted constraint: /9/1
 │                   └── spans
 │                        ├── [Arr/3, Arr/3]
 │                        ├── [Arr/Arr/1, Arr/Arr/1]
 │                        └── [Arr/Arr/2, Arr/Arr/2]
 └── filters
      └── (j @> '[3]') OR (j @> '[[1, 2]]')

opt expect=GenerateMinimalInvertedIndexScans disable=GenerateInvertedIndexZigzagJoins
SELECT * FROM min@j_idx WHERE j->'a' = '"b"' AND j->'c' = '"d"'
----
select
 ├── columns: k:1!null j:2 a:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan min@j_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /6/1
 │         │    └── spans: ["c"/"d", "c"/"d"]
 │         ├── flags: force-index=j_idx
 │         └── key: (1)
 └── filters
      ├── (j:2->'a') = '"b"' [outer=(2), immutable]
      └── (j:2->'c') = '"d"' [outer=(2), immutable]

opt expect=GenerateMinimalInvertedIndexScans
SELECT * FROM min@j_idx WHERE j->'a' = '"b"' AND j->'c' = '"d"' AND j->'e' = '"f"'
----
select
 ├── columns: k:1!null j:2 a:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join min
 │    ├── columns: k:1!null j:2 a:3
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan min@j_idx,inverted
 │         ├── columns: k:1!null
 │         ├── inverted constraint: /6/1
 │         │    └── spans: ["e"/"f", "e"/"f"]
 │         ├── flags: force-index=j_idx
 │         └── key: (1)
 └── filters
      ├── (j:2->'a') = '"b"' [outer=(2), immutable]
      ├── (j:2->'c') = '"d"' [outer=(2), immutable]
      └── (j:2->'e') = '"f"' [outer=(2), immutable]


# --------------------------------------------------
# GenerateZigzagJoins
# --------------------------------------------------

exec-ddl
CREATE TABLE pqr
(
    p INT PRIMARY KEY,
    q INT,
    r INT,
    s STRING,
    t STRING,
    u STRING NOT NULL,
    v STRING,
    INDEX q (q),
    INDEX r (r),
    INDEX s (s) STORING (r),
    INDEX rs (r,s),
    INDEX ts (t,s)
)
----

exec-ddl
ALTER TABLE pqr INJECT STATISTICS '[
  {
    "columns": ["r"],
    "distinct_count": 100,
    "null_count": 0,
    "row_count": 10000,
    "created_at": "2018-01-01 1:00:00.00000+00:00"
  },
  {
    "columns": ["s"],
    "distinct_count": 10,
    "null_count": 0,
    "row_count": 10000,
    "created_at": "2018-01-01 1:00:00.00000+00:00"
  }
]'
----

exec-ddl
CREATE TABLE zz (
    a INT8 PRIMARY KEY,
    b INT8 NULL,
    c INT8 NULL,
    INDEX idx_b (b ASC),
    CONSTRAINT idx_c UNIQUE (c)
)
----

exec-ddl
CREATE TABLE zz_redundant (
    a INT8 PRIMARY KEY,
    b INT8 NULL,
    c INT8 NULL,
    INDEX idx_u (b ASC, c ASC),
    INDEX idx_v (b ASC, c ASC)
)
----

# Simple zigzag case - where all requested columns are in the indexes being
# joined.
opt expect=GenerateZigzagJoins
SELECT q,r FROM pqr WHERE q = 1 AND r = 2
----
inner-join (zigzag pqr@q pqr@r)
 ├── columns: q:2!null r:3!null
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [3] = [2]
 ├── fd: ()-->(2,3)
 └── filters
      ├── q:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── r:3 = 2 [outer=(3), constraints=(/3: [/2 - /2]; tight), fd=()-->(3)]

opt expect=GenerateZigzagJoins
SELECT q,r FROM pqr WHERE q = 1 AND r IS NULL
----
inner-join (zigzag pqr@q pqr@r)
 ├── columns: q:2!null r:3
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [3] = [NULL]
 ├── fd: ()-->(2,3)
 └── filters
      ├── q:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── r:3 IS NULL [outer=(3), constraints=(/3: [/NULL - /NULL]; tight), fd=()-->(3)]

memo expect=GenerateZigzagJoins
SELECT q,r FROM pqr WHERE q = 1 AND r = 2
----
memo (optimized, ~18KB, required=[presentation: q:2,r:3])
 ├── G1: (select G2 G3) (select G4 G5) (select G6 G7) (select G8 G7) (zigzag-join G3 pqr@q pqr@r)
 │    └── [presentation: q:2,r:3]
 │         ├── best: (zigzag-join G3 pqr@q pqr@r)
 │         └── cost: 19.24
 ├── G2: (scan pqr,cols=(2,3))
 │    └── []
 │         ├── best: (scan pqr,cols=(2,3))
 │         └── cost: 10928.92
 ├── G3: (filters G9 G10)
 ├── G4: (index-join G11 pqr,cols=(2,3))
 │    └── []
 │         ├── best: (index-join G11 pqr,cols=(2,3))
 │         └── cost: 88.70
 ├── G5: (filters G10)
 ├── G6: (index-join G12 pqr,cols=(2,3))
 │    └── []
 │         ├── best: (index-join G12 pqr,cols=(2,3))
 │         └── cost: 731.04
 ├── G7: (filters G9)
 ├── G8: (index-join G13 pqr,cols=(2,3))
 │    └── []
 │         ├── best: (index-join G13 pqr,cols=(2,3))
 │         └── cost: 732.04
 ├── G9: (eq G14 G15)
 ├── G10: (eq G16 G17)
 ├── G11: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 28.33
 ├── G12: (scan pqr@r,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@r,cols=(1,3),constrained)
 │         └── cost: 122.02
 ├── G13: (scan pqr@rs,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@rs,cols=(1,3),constrained)
 │         └── cost: 123.02
 ├── G14: (variable q)
 ├── G15: (const 1)
 ├── G16: (variable r)
 └── G17: (const 2)

# Case where the fixed columns are extracted from a complicated expression.
opt expect=GenerateZigzagJoins
SELECT q,r FROM pqr WHERE q = 1 AND ((r < 1 AND r > 1) OR (r >= 2 AND r <= 2))
----
inner-join (zigzag pqr@q pqr@r)
 ├── columns: q:2!null r:3!null
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [3] = [2]
 ├── fd: ()-->(2,3)
 └── filters
      ├── q:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── ((r:3 < 1) AND (r:3 > 1)) OR ((r:3 >= 2) AND (r:3 <= 2)) [outer=(3), constraints=(/3: [/2 - /2]; tight), fd=()-->(3)]

# Nested zigzag case - zigzag join needs to be wrapped in a lookup join to
# satisfy required columns.
opt expect=GenerateZigzagJoins
SELECT q,r,s FROM pqr WHERE q = 1 AND r = 2
----
inner-join (lookup pqr)
 ├── columns: q:2!null r:3!null s:4
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── fd: ()-->(2,3)
 ├── inner-join (zigzag pqr@q pqr@r)
 │    ├── columns: p:1!null q:2!null r:3!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [2] = [1]
 │    ├── right fixed columns: [3] = [2]
 │    ├── fd: ()-->(2,3)
 │    └── filters
 │         ├── q:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
 │         └── r:3 = 2 [outer=(3), constraints=(/3: [/2 - /2]; tight), fd=()-->(3)]
 └── filters (true)

memo expect=GenerateZigzagJoins
SELECT q,r,s FROM pqr WHERE q = 1 AND r = 2
----
memo (optimized, ~21KB, required=[presentation: q:2,r:3,s:4])
 ├── G1: (select G2 G3) (select G4 G5) (select G6 G7) (select G8 G7) (lookup-join G9 G10 pqr,keyCols=[1],outCols=(2-4))
 │    └── [presentation: q:2,r:3,s:4]
 │         ├── best: (lookup-join G9 G10 pqr,keyCols=[1],outCols=(2-4))
 │         └── cost: 23.86
 ├── G2: (scan pqr,cols=(2-4))
 │    └── []
 │         ├── best: (scan pqr,cols=(2-4))
 │         └── cost: 11029.02
 ├── G3: (filters G11 G12)
 ├── G4: (index-join G13 pqr,cols=(2-4))
 │    └── []
 │         ├── best: (index-join G13 pqr,cols=(2-4))
 │         └── cost: 88.80
 ├── G5: (filters G12)
 ├── G6: (index-join G14 pqr,cols=(2-4))
 │    └── []
 │         ├── best: (index-join G14 pqr,cols=(2-4))
 │         └── cost: 732.04
 ├── G7: (filters G11)
 ├── G8: (index-join G15 pqr,cols=(2-4))
 │    └── []
 │         ├── best: (index-join G15 pqr,cols=(2-4))
 │         └── cost: 733.04
 ├── G9: (zigzag-join G3 pqr@q pqr@r)
 │    └── []
 │         ├── best: (zigzag-join G3 pqr@q pqr@r)
 │         └── cost: 19.24
 ├── G10: (filters)
 ├── G11: (eq G16 G17)
 ├── G12: (eq G18 G19)
 ├── G13: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 28.33
 ├── G14: (scan pqr@r,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@r,cols=(1,3),constrained)
 │         └── cost: 122.02
 ├── G15: (scan pqr@rs,cols=(1,3,4),constrained)
 │    └── []
 │         ├── best: (scan pqr@rs,cols=(1,3,4),constrained)
 │         └── cost: 124.02
 ├── G16: (variable q)
 ├── G17: (const 1)
 ├── G18: (variable r)
 └── G19: (const 2)

# Zigzag with fixed columns of different types.
opt expect=GenerateZigzagJoins
SELECT q,s FROM pqr WHERE q = 1 AND s = 'foo'
----
inner-join (zigzag pqr@q pqr@s)
 ├── columns: q:2!null s:4!null
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [4] = ['foo']
 ├── fd: ()-->(2,4)
 └── filters
      ├── q:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

memo expect=GenerateZigzagJoins
SELECT q,s FROM pqr WHERE q = 1 AND s = 'foo'
----
memo (optimized, ~15KB, required=[presentation: q:2,s:4])
 ├── G1: (select G2 G3) (select G4 G5) (select G6 G7) (zigzag-join G3 pqr@q pqr@s)
 │    └── [presentation: q:2,s:4]
 │         ├── best: (zigzag-join G3 pqr@q pqr@s)
 │         └── cost: 30.03
 ├── G2: (scan pqr,cols=(2,4))
 │    └── []
 │         ├── best: (scan pqr,cols=(2,4))
 │         └── cost: 10928.92
 ├── G3: (filters G8 G9)
 ├── G4: (index-join G10 pqr,cols=(2,4))
 │    └── []
 │         ├── best: (index-join G10 pqr,cols=(2,4))
 │         └── cost: 88.70
 ├── G5: (filters G9)
 ├── G6: (index-join G11 pqr,cols=(2,4))
 │    └── []
 │         ├── best: (index-join G11 pqr,cols=(2,4))
 │         └── cost: 7158.04
 ├── G7: (filters G8)
 ├── G8: (eq G12 G13)
 ├── G9: (eq G14 G15)
 ├── G10: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 28.33
 ├── G11: (scan pqr@s,cols=(1,4),constrained)
 │    └── []
 │         ├── best: (scan pqr@s,cols=(1,4),constrained)
 │         └── cost: 1068.02
 ├── G12: (variable q)
 ├── G13: (const 1)
 ├── G14: (variable s)
 └── G15: (const 'foo')

# A zig-zag join cannot be planned on rs and ts because s is nullable, so it
# cannot be an implicit equality column.
opt expect-not=GenerateZigzagJoins
SELECT r,t FROM pqr WHERE r = 1 AND t = 'foo'
----
select
 ├── columns: r:3!null t:5!null
 ├── fd: ()-->(3,5)
 ├── index-join pqr
 │    ├── columns: r:3 t:5
 │    ├── fd: ()-->(5)
 │    └── scan pqr@ts
 │         ├── columns: p:1!null t:5!null
 │         ├── constraint: /5/4/1: [/'foo' - /'foo']
 │         ├── key: (1)
 │         └── fd: ()-->(5)
 └── filters
      └── r:3 = 1 [outer=(3), constraints=(/3: [/1 - /1]; tight), fd=()-->(3)]

exec-ddl
CREATE INDEX ru ON pqr (r, u)
----

exec-ddl
CREATE INDEX tu ON pqr (t, u)
----

# A zig-zag join can be planned on ru and tu because u is non-nullable, so it
# is an implicit equality column in addition to the primary key.
opt expect=GenerateZigzagJoins
SELECT r,t FROM pqr WHERE r = 1 AND t = 'foo'
----
inner-join (zigzag pqr@ru pqr@tu)
 ├── columns: r:3!null t:5!null
 ├── eq columns: [6 1] = [6 1]
 ├── left fixed columns: [3] = [1]
 ├── right fixed columns: [5] = ['foo']
 ├── fd: ()-->(3,5)
 └── filters
      ├── r:3 = 1 [outer=(3), constraints=(/3: [/1 - /1]; tight), fd=()-->(3)]
      └── t:5 = 'foo' [outer=(5), constraints=(/5: [/'foo' - /'foo']; tight), fd=()-->(5)]

exec-ddl
DROP INDEX ru
----

exec-ddl
DROP INDEX tu
----

exec-ddl
CREATE INDEX tv ON pqr (t, v)
----

# A zig-zag join can be planned on rs and tv even though s and v are nullable
# because they are explicit equality columns. The query filters out rows where s
# IS NULL or v IS NULL.
opt expect=GenerateZigzagJoins
SELECT r,t FROM pqr WHERE r = 1 AND t = 'foo' AND s = v
----
project
 ├── columns: r:3!null t:5!null
 ├── fd: ()-->(3,5)
 └── inner-join (zigzag pqr@rs pqr@tv)
      ├── columns: r:3!null s:4!null t:5!null v:7!null
      ├── eq columns: [4 1] = [7 1]
      ├── left fixed columns: [3] = [1]
      ├── right fixed columns: [5] = ['foo']
      ├── fd: ()-->(3,5), (4)==(7), (7)==(4)
      └── filters
           ├── r:3 = 1 [outer=(3), constraints=(/3: [/1 - /1]; tight), fd=()-->(3)]
           └── t:5 = 'foo' [outer=(5), constraints=(/5: [/'foo' - /'foo']; tight), fd=()-->(5)]

exec-ddl
DROP INDEX tv
----

# Zigzag with choice between indexes for multiple equality predicates.
opt expect=GenerateZigzagJoins
SELECT p,q,r,s FROM pqr WHERE q = 1 AND r = 1 AND s = 'foo'
----
inner-join (zigzag pqr@q pqr@s)
 ├── columns: p:1!null q:2!null r:3!null s:4!null
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [4] = ['foo']
 ├── key: (1)
 ├── fd: ()-->(2-4)
 └── filters
      ├── q:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      ├── r:3 = 1 [outer=(3), constraints=(/3: [/1 - /1]; tight), fd=()-->(3)]
      └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

# Regression test for #97090. We should not plan a zigzag join when the
# direction of the equality columns doesn't match.
exec-ddl
CREATE TABLE t97090 (
  c INT NOT NULL,
  l INT NOT NULL,
  r INT NOT NULL,
  INDEX (l ASC, c DESC),
  INDEX (r ASC, c ASC)
)
----

opt expect-not=GenerateZigzagJoins
SELECT * FROM t97090 WHERE l = 1 AND r = -1
----
select
 ├── columns: c:1!null l:2!null r:3!null
 ├── fd: ()-->(2,3)
 ├── index-join t97090
 │    ├── columns: c:1!null l:2!null r:3!null
 │    ├── fd: ()-->(2)
 │    └── scan t97090@t97090_l_c_idx
 │         ├── columns: c:1!null l:2!null rowid:4!null
 │         ├── constraint: /2/-1/4: [/1 - /1]
 │         ├── key: (4)
 │         └── fd: ()-->(2), (4)-->(1)
 └── filters
      └── r:3 = -1 [outer=(3), constraints=(/3: [/-1 - /-1]; tight), fd=()-->(3)]

# Zigzag join should not be produced when there is a NO_ZIGZAG_JOIN hint.
opt expect-not=GenerateZigzagJoins
SELECT q,r FROM pqr@{NO_ZIGZAG_JOIN} WHERE q = 1 AND r = 2
----
select
 ├── columns: q:2!null r:3!null
 ├── fd: ()-->(2,3)
 ├── index-join pqr
 │    ├── columns: q:2 r:3
 │    ├── fd: ()-->(2)
 │    └── scan pqr@q
 │         ├── columns: p:1!null q:2!null
 │         ├── constraint: /2/1: [/1 - /1]
 │         ├── flags: no-zigzag-join
 │         ├── key: (1)
 │         └── fd: ()-->(2)
 └── filters
      └── r:3 = 2 [outer=(3), constraints=(/3: [/2 - /2]; tight), fd=()-->(3)]

# Tests for zigzag joins over partial indexes.

exec-ddl
CREATE TABLE zz_partial (
    k INT PRIMARY KEY,
    i INT,
    j INT,
    b1 BOOL,
    b2 BOOL,
    s STRING
)
----

exec-ddl
CREATE INDEX i ON zz_partial (i) WHERE b1
----

exec-ddl
CREATE INDEX j ON zz_partial (j) WHERE b2
----

# Generate a zigzag join over two partial indexes.
opt expect=GenerateZigzagJoins
SELECT k FROM zz_partial WHERE i = 10 AND b1 AND j = 20 AND b2
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── inner-join (lookup zz_partial)
      ├── columns: k:1!null i:2!null j:3!null b1:4!null b2:5!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── key: (1)
      ├── fd: ()-->(2-5)
      ├── inner-join (zigzag zz_partial@i,partial zz_partial@j,partial)
      │    ├── columns: k:1!null i:2!null j:3!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [2] = [10]
      │    ├── right fixed columns: [3] = [20]
      │    ├── fd: ()-->(2,3)
      │    └── filters
      │         ├── i:2 = 10 [outer=(2), constraints=(/2: [/10 - /10]; tight), fd=()-->(2)]
      │         └── j:3 = 20 [outer=(3), constraints=(/3: [/20 - /20]; tight), fd=()-->(3)]
      └── filters (true)

# Don't generate a zigzag join when the first index predicate is not implied.
opt expect-not=GenerateZigzagJoins format=hide-all
SELECT k FROM zz_partial WHERE i = 10 AND j = 20 AND b2
----
project
 └── select
      ├── index-join zz_partial
      │    └── scan zz_partial@j,partial
      │         └── constraint: /3/1: [/20 - /20]
      └── filters
           └── i = 10

# Don't generate a zigzag join when the second index predicate is not implied.
opt expect-not=GenerateZigzagJoins format=hide-all
SELECT k FROM zz_partial WHERE i = 10 AND b1 AND j = 20
----
project
 └── select
      ├── index-join zz_partial
      │    └── scan zz_partial@i,partial
      │         └── constraint: /2/1: [/10 - /10]
      └── filters
           └── j = 20

exec-ddl
DROP INDEX j
----

exec-ddl
CREATE INDEX j ON zz_partial (j)
----

# Generate a zigzag join over one partial and one non-partial index.
opt expect=GenerateZigzagJoins
SELECT k FROM zz_partial WHERE i = 10 AND b1 AND j = 20
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── inner-join (lookup zz_partial)
      ├── columns: k:1!null i:2!null j:3!null b1:4!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── key: (1)
      ├── fd: ()-->(2-4)
      ├── inner-join (zigzag zz_partial@i,partial zz_partial@j)
      │    ├── columns: k:1!null i:2!null j:3!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [2] = [10]
      │    ├── right fixed columns: [3] = [20]
      │    ├── fd: ()-->(2,3)
      │    └── filters
      │         ├── i:2 = 10 [outer=(2), constraints=(/2: [/10 - /10]; tight), fd=()-->(2)]
      │         └── j:3 = 20 [outer=(3), constraints=(/3: [/20 - /20]; tight), fd=()-->(3)]
      └── filters (true)

# Don't generate a zigzag join when the partial index predicate is not implied.
opt expect-not=GenerateZigzagJoins format=hide-all
SELECT k FROM zz_partial WHERE i = 10 AND j = 20
----
project
 └── select
      ├── index-join zz_partial
      │    └── scan zz_partial@j
      │         └── constraint: /3/1: [/20 - /20]
      └── filters
           └── i = 10

exec-ddl
DROP INDEX i
----

exec-ddl
DROP INDEX j
----

exec-ddl
CREATE INDEX i ON zz_partial (i) WHERE i = 10
----

exec-ddl
CREATE INDEX j ON zz_partial (j)
----

# Don't generate a zigzag join when the expression that fixes the left columns
# is removed during partial index implication of the left index.
opt expect-not=GenerateZigzagJoins format=hide-all
SELECT k FROM zz_partial WHERE i = 10 AND j = 20
----
project
 └── select
      ├── index-join zz_partial
      │    └── scan zz_partial@i,partial
      └── filters
           └── j = 20

exec-ddl
DROP INDEX i
----

exec-ddl
DROP INDEX j
----

exec-ddl
CREATE INDEX i ON zz_partial (i)
----

exec-ddl
CREATE INDEX j ON zz_partial (j) WHERE j = 20
----

# Don't generate a zigzag join when the expression that fixes the right columns
# is removed during partial index implication of the right index.
opt expect-not=GenerateZigzagJoins format=hide-all
SELECT k FROM zz_partial WHERE i = 10 AND j = 20
----
project
 └── select
      ├── index-join zz_partial
      │    └── scan zz_partial@j,partial
      └── filters
           └── i = 10

exec-ddl
DROP INDEX i
----

exec-ddl
DROP INDEX j
----

exec-ddl
CREATE INDEX zz_partial_s ON zz_partial (s)
----

exec-ddl
CREATE INDEX j ON zz_partial (j) WHERE s = 'foo'
----

# Don't generate a zigzag join when the expression that fixes the left columns
# is removed during partial index implication of the right index.
# TODO(mgartner): Once again, we find ourselves with a suboptimal query plan
# with an unnecessary Project that projects s='foo' which is not needed by the
# parent expression. This is the result of GenerateConstrainedScans replacing
# the matched Select with a Project that is required to produce all the columns
# that the Select produced. One way to fix this would be to add a new
# exploration rule that can remove these unnecessary Projects, similar to
# EliminateIndexJoinInsideProject.
opt expect-not=GenerateZigzagJoins format=hide-all
SELECT k FROM zz_partial WHERE s = 'foo' AND j = 20
----
project
 └── project
      ├── scan zz_partial@j,partial
      │    └── constraint: /3/1: [/20 - /20]
      └── projections
           └── 'foo'

exec-ddl
DROP INDEX zz_partial_s
----

exec-ddl
DROP INDEX j
----

exec-ddl
CREATE INDEX i ON zz_partial (i) WHERE s = 'foo'
----

exec-ddl
CREATE INDEX zz_partial_s ON zz_partial (s)
----

# Don't generate a zigzag join when the expression that fixes the right columns
# is removed during partial index implication of the left index.
opt expect-not=GenerateZigzagJoins format=hide-all
SELECT k FROM zz_partial WHERE i = 10 AND s = 'foo'
----
project
 └── project
      ├── scan zz_partial@i,partial
      │    └── constraint: /2/1: [/10 - /10]
      └── projections
           └── 'foo'

exec-ddl
DROP INDEX i
----

exec-ddl
DROP INDEX zz_partial_s
----

exec-ddl
CREATE INDEX i ON zz_partial (i)
----

exec-ddl
CREATE INDEX b1 ON zz_partial (b1) WHERE s = 'foo'
----

exec-ddl
CREATE INDEX j ON zz_partial (j)
----

# The filters should be reset during each iteration over the left and right
# indexes if they are reduced while proving partial index implication. In this
# test, (s = 'foo') must be applied after the zigzag join.
opt expect=GenerateZigzagJoins
SELECT k FROM zz_partial WHERE i = 10 AND j = 20 AND b1 AND s = 'foo'
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── inner-join (lookup zz_partial)
      ├── columns: k:1!null i:2!null j:3!null b1:4!null s:6!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── key: (1)
      ├── fd: ()-->(2-4,6)
      ├── inner-join (zigzag zz_partial@i zz_partial@j)
      │    ├── columns: k:1!null i:2!null j:3!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [2] = [10]
      │    ├── right fixed columns: [3] = [20]
      │    ├── fd: ()-->(2,3)
      │    └── filters
      │         ├── i:2 = 10 [outer=(2), constraints=(/2: [/10 - /10]; tight), fd=()-->(2)]
      │         └── j:3 = 20 [outer=(3), constraints=(/3: [/20 - /20]; tight), fd=()-->(3)]
      └── filters
           ├── b1:4 [outer=(4), constraints=(/4: [/true - /true]; tight), fd=()-->(4)]
           └── s:6 = 'foo' [outer=(6), constraints=(/6: [/'foo' - /'foo']; tight), fd=()-->(6)]

exec-ddl
DROP INDEX i
----

exec-ddl
DROP INDEX b1
----

exec-ddl
DROP INDEX j
----

exec-ddl
CREATE INDEX i ON zz_partial (i) WHERE b1
----

exec-ddl
CREATE INDEX j ON zz_partial (j) WHERE b1
----

# Generate a zigzag join on two partial indexes with the same predicate.
opt
SELECT k FROM zz_partial WHERE i = 10 AND j = 20 AND b1
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── inner-join (lookup zz_partial)
      ├── columns: k:1!null i:2!null j:3!null b1:4!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── key: (1)
      ├── fd: ()-->(2-4)
      ├── inner-join (zigzag zz_partial@i,partial zz_partial@j,partial)
      │    ├── columns: k:1!null i:2!null j:3!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [2] = [10]
      │    ├── right fixed columns: [3] = [20]
      │    ├── fd: ()-->(2,3)
      │    └── filters
      │         ├── i:2 = 10 [outer=(2), constraints=(/2: [/10 - /10]; tight), fd=()-->(2)]
      │         └── j:3 = 20 [outer=(3), constraints=(/3: [/20 - /20]; tight), fd=()-->(3)]
      └── filters (true)

# Don't generate a zigzag which has the PK as its equality columns against
# nullable unique indexes where the primary key is not part of the indexed
# columns.

# Regression test for #36051: prior to fixing this, we would try to use the PK
# as the equality column here, but it's not actually part of the key so we
# can't zigzag on it.
opt expect-not=GenerateZigzagJoins
SELECT * FROM zz WHERE b IS NULL AND c = 2
----
select
 ├── columns: a:1!null b:2 c:3!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1-3)
 ├── index-join zz
 │    ├── columns: a:1!null b:2 c:3
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(1-3)
 │    └── scan zz@idx_c
 │         ├── columns: a:1!null c:3!null
 │         ├── constraint: /3: [/2 - /2]
 │         ├── cardinality: [0 - 1]
 │         ├── key: ()
 │         └── fd: ()-->(1,3)
 └── filters
      └── b:2 IS NULL [outer=(2), constraints=(/2: [/NULL - /NULL]; tight), fd=()-->(2)]

memo
SELECT p,q,r,s FROM pqr WHERE q = 1 AND r = 1 AND s = 'foo'
----
memo (optimized, ~42KB, required=[presentation: p:1,q:2,r:3,s:4])
 ├── G1: (select G2 G3) (select G4 G5) (select G6 G7) (select G8 G9) (select G10 G9) (lookup-join G11 G12 pqr,keyCols=[1],outCols=(1-4)) (zigzag-join G3 pqr@q pqr@s) (zigzag-join G3 pqr@q pqr@rs) (lookup-join G13 G9 pqr,keyCols=[1],outCols=(1-4))
 │    └── [presentation: p:1,q:2,r:3,s:4]
 │         ├── best: (zigzag-join G3 pqr@q pqr@s)
 │         └── cost: 18.17
 ├── G2: (scan pqr,cols=(1-4))
 │    └── []
 │         ├── best: (scan pqr,cols=(1-4))
 │         └── cost: 11129.12
 ├── G3: (filters G14 G15 G16)
 ├── G4: (index-join G17 pqr,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G17 pqr,cols=(1-4))
 │         └── cost: 88.80
 ├── G5: (filters G15 G16)
 ├── G6: (index-join G18 pqr,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G18 pqr,cols=(1-4))
 │         └── cost: 732.04
 ├── G7: (filters G14 G16)
 ├── G8: (index-join G19 pqr,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G19 pqr,cols=(1-4))
 │         └── cost: 1148.97
 ├── G9: (filters G14)
 ├── G10: (index-join G20 pqr,cols=(1-4))
 │    └── []
 │         ├── best: (index-join G20 pqr,cols=(1-4))
 │         └── cost: 89.54
 ├── G11: (zigzag-join G21 pqr@q pqr@r)
 │    └── []
 │         ├── best: (zigzag-join G21 pqr@q pqr@r)
 │         └── cost: 19.24
 ├── G12: (filters G16)
 ├── G13: (zigzag-join G5 pqr@r pqr@s)
 │    └── []
 │         ├── best: (zigzag-join G5 pqr@r pqr@s)
 │         └── cost: 139.14
 ├── G14: (eq G22 G23)
 ├── G15: (eq G24 G23)
 ├── G16: (eq G25 G26)
 ├── G17: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 28.33
 ├── G18: (scan pqr@r,cols=(1,3),constrained)
 │    └── []
 │         ├── best: (scan pqr@r,cols=(1,3),constrained)
 │         └── cost: 122.02
 ├── G19: (select G27 G28)
 │    └── []
 │         ├── best: (select G27 G28)
 │         └── cost: 1088.05
 ├── G20: (scan pqr@rs,cols=(1,3,4),constrained)
 │    └── []
 │         ├── best: (scan pqr@rs,cols=(1,3,4),constrained)
 │         └── cost: 28.62
 ├── G21: (filters G14 G15)
 ├── G22: (variable q)
 ├── G23: (const 1)
 ├── G24: (variable r)
 ├── G25: (variable s)
 ├── G26: (const 'foo')
 ├── G27: (scan pqr@s,cols=(1,3,4),constrained)
 │    └── []
 │         ├── best: (scan pqr@s,cols=(1,3,4),constrained)
 │         └── cost: 1078.02
 └── G28: (filters G15)

# Zigzag joins cannot be planned for indexes where equality columns do not
# immediately follow fixed columns. Here, the only index on t is (t,s,p) and
# s is not a fixed or equal column, so a zigzag join shouldn't be planned.
opt
SELECT q,t FROM pqr WHERE q = 1 AND t = 'foo'
----
select
 ├── columns: q:2!null t:5!null
 ├── fd: ()-->(2,5)
 ├── index-join pqr
 │    ├── columns: q:2 t:5
 │    ├── fd: ()-->(2)
 │    └── scan pqr@q
 │         ├── columns: p:1!null q:2!null
 │         ├── constraint: /2/1: [/1 - /1]
 │         ├── key: (1)
 │         └── fd: ()-->(2)
 └── filters
      └── t:5 = 'foo' [outer=(5), constraints=(/5: [/'foo' - /'foo']; tight), fd=()-->(5)]

memo
SELECT q,t FROM pqr WHERE q = 1 AND t = 'foo'
----
memo (optimized, ~12KB, required=[presentation: q:2,t:5])
 ├── G1: (select G2 G3) (select G4 G5) (select G6 G7)
 │    └── [presentation: q:2,t:5]
 │         ├── best: (select G4 G5)
 │         └── cost: 88.83
 ├── G2: (scan pqr,cols=(2,5))
 │    └── []
 │         ├── best: (scan pqr,cols=(2,5))
 │         └── cost: 10928.92
 ├── G3: (filters G8 G9)
 ├── G4: (index-join G10 pqr,cols=(2,5))
 │    └── []
 │         ├── best: (index-join G10 pqr,cols=(2,5))
 │         └── cost: 88.70
 ├── G5: (filters G9)
 ├── G6: (index-join G11 pqr,cols=(2,5))
 │    └── []
 │         ├── best: (index-join G11 pqr,cols=(2,5))
 │         └── cost: 88.80
 ├── G7: (filters G8)
 ├── G8: (eq G12 G13)
 ├── G9: (eq G14 G15)
 ├── G10: (scan pqr@q,cols=(1,2),constrained)
 │    └── []
 │         ├── best: (scan pqr@q,cols=(1,2),constrained)
 │         └── cost: 28.33
 ├── G11: (scan pqr@ts,cols=(1,5),constrained)
 │    └── []
 │         ├── best: (scan pqr@ts,cols=(1,5),constrained)
 │         └── cost: 28.43
 ├── G12: (variable q)
 ├── G13: (const 1)
 ├── G14: (variable t)
 └── G15: (const 'foo')

# Don't zigzag on two identical indexes.
memo
SELECT c FROM zz_redundant WHERE b = 1
----
memo (optimized, ~9KB, required=[presentation: c:3])
 ├── G1: (project G2 G3 c)
 │    └── [presentation: c:3]
 │         ├── best: (project G2 G3 c)
 │         └── cost: 28.64
 ├── G2: (select G4 G5) (scan zz_redundant@idx_u,cols=(2,3),constrained) (scan zz_redundant@idx_v,cols=(2,3),constrained)
 │    └── []
 │         ├── best: (scan zz_redundant@idx_u,cols=(2,3),constrained)
 │         └── cost: 28.52
 ├── G3: (projections)
 ├── G4: (scan zz_redundant,cols=(2,3)) (scan zz_redundant@idx_u,cols=(2,3)) (scan zz_redundant@idx_v,cols=(2,3))
 │    └── []
 │         ├── best: (scan zz_redundant,cols=(2,3))
 │         └── cost: 1078.52
 ├── G5: (filters G6)
 ├── G6: (eq G7 G8)
 ├── G7: (variable b)
 └── G8: (const 1)

# GenerateZigzagJoins propagates row-level locking information.
opt expect=GenerateZigzagJoins
SELECT q,r FROM pqr WHERE q = 1 AND r = 2 FOR UPDATE
----
inner-join (zigzag pqr@q pqr@r)
 ├── columns: q:2!null r:3!null
 ├── eq columns: [1] = [1]
 ├── left fixed columns: [2] = [1]
 ├── right fixed columns: [3] = [2]
 ├── left locking: for-update
 ├── right locking: for-update
 ├── volatile
 ├── fd: ()-->(2,3)
 └── filters
      ├── q:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
      └── r:3 = 2 [outer=(3), constraints=(/3: [/2 - /2]; tight), fd=()-->(3)]

# Zigzag join hinting tests
exec-ddl
CREATE TABLE zigzag
(
    n INT PRIMARY KEY,
    a INT,
    b INT,
    c STRING,
    INDEX a_idx(a),
    INDEX b_idx(b),
    UNIQUE INDEX c_idx(b,c),
    INDEX bc_idx(b,c)
)
----

# Verify naked hint.
opt
SELECT * FROM zigzag@{FORCE_ZIGZAG} WHERE a = 3 AND b = 7;
----
inner-join (lookup zigzag)
 ├── columns: n:1!null a:2!null b:3!null c:4
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4), (3,4)~~>(1)
 ├── inner-join (zigzag zigzag@a_idx zigzag@b_idx)
 │    ├── columns: n:1!null a:2!null b:3!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [2] = [3]
 │    ├── right fixed columns: [3] = [7]
 │    ├── fd: ()-->(2,3)
 │    └── filters
 │         ├── a:2 = 3 [outer=(2), constraints=(/2: [/3 - /3]; tight), fd=()-->(2)]
 │         └── b:3 = 7 [outer=(3), constraints=(/3: [/7 - /7]; tight), fd=()-->(3)]
 └── filters (true)

# Verify plan with one index.
opt
SELECT * FROM zigzag@{FORCE_ZIGZAG=a_idx} WHERE a = 3 AND b = 7;
----
inner-join (lookup zigzag)
 ├── columns: n:1!null a:2!null b:3!null c:4
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4), (3,4)~~>(1)
 ├── inner-join (zigzag zigzag@a_idx zigzag@b_idx)
 │    ├── columns: n:1!null a:2!null b:3!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [2] = [3]
 │    ├── right fixed columns: [3] = [7]
 │    ├── fd: ()-->(2,3)
 │    └── filters
 │         ├── a:2 = 3 [outer=(2), constraints=(/2: [/3 - /3]; tight), fd=()-->(2)]
 │         └── b:3 = 7 [outer=(3), constraints=(/3: [/7 - /7]; tight), fd=()-->(3)]
 └── filters (true)

# Verify plan with other index.
opt
SELECT * FROM zigzag@{FORCE_ZIGZAG=b_idx} WHERE a = 3 AND b = 7;
----
inner-join (lookup zigzag)
 ├── columns: n:1!null a:2!null b:3!null c:4
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4), (3,4)~~>(1)
 ├── inner-join (zigzag zigzag@a_idx zigzag@b_idx)
 │    ├── columns: n:1!null a:2!null b:3!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [2] = [3]
 │    ├── right fixed columns: [3] = [7]
 │    ├── fd: ()-->(2,3)
 │    └── filters
 │         ├── a:2 = 3 [outer=(2), constraints=(/2: [/3 - /3]; tight), fd=()-->(2)]
 │         └── b:3 = 7 [outer=(3), constraints=(/3: [/7 - /7]; tight), fd=()-->(3)]
 └── filters (true)

# Verify plan with both indexes hinted.
opt
SELECT * FROM zigzag@{FORCE_ZIGZAG=a_idx,FORCE_ZIGZAG=b_idx} WHERE a = 3 AND b = 7;
----
inner-join (lookup zigzag)
 ├── columns: n:1!null a:2!null b:3!null c:4
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4), (3,4)~~>(1)
 ├── inner-join (zigzag zigzag@a_idx zigzag@b_idx)
 │    ├── columns: n:1!null a:2!null b:3!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [2] = [3]
 │    ├── right fixed columns: [3] = [7]
 │    ├── fd: ()-->(2,3)
 │    └── filters
 │         ├── a:2 = 3 [outer=(2), constraints=(/2: [/3 - /3]; tight), fd=()-->(2)]
 │         └── b:3 = 7 [outer=(3), constraints=(/3: [/7 - /7]; tight), fd=()-->(3)]
 └── filters (true)

# Verify force selection when there's multiple valid choices.
opt
SELECT * FROM zigzag@{FORCE_ZIGZAG=a_idx,FORCE_ZIGZAG=bc_idx} WHERE a = 3 AND b = 7;
----
select
 ├── columns: n:1!null a:2!null b:3!null c:4
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4), (3,4)~~>(1)
 ├── scan zigzag
 │    ├── columns: n:1!null a:2 b:3 c:4
 │    ├── flags: force-zigzag=a_idx,bc_idx
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 └── filters
      ├── a:2 = 3 [outer=(2), constraints=(/2: [/3 - /3]; tight), fd=()-->(2)]
      └── b:3 = 7 [outer=(3), constraints=(/3: [/7 - /7]; tight), fd=()-->(3)]

# --------------------------------------------------
# GenerateInvertedIndexZigzagJoins
# --------------------------------------------------

# Stats on table b created via:
# insert into b select g,g,g,'{"a": "b"}'
#                      from generate_series(10,100000) g(g);
# insert into b select g,g,g,'{"f": "g"}'
#                      from generate_series(100001,200000) g(g);
# insert into b select g,g,g,'{"a":[{"b":"c"}, 5]}'
#                      from generate_series(200001,300000) g(g);
# insert into b select g,g,g,'{"a":1}'
#                      from generate_series(300001,400000) g(g);
# insert into b select g,g,g,'{"b":2}'
#                      from generate_series(400001,500000) g(g);
# analyze b;
# Only histogram buckets on column j are preserved.
exec-ddl
ALTER TABLE b INJECT STATISTICS '[
    {
        "avg_size": 4,
        "columns": [
            "k"
        ],
        "created_at": "2022-10-03 22:22:47.76982",
        "distinct_count": 496512,
        "null_count": 0,
        "row_count": 499991
    },
    {
        "avg_size": 4,
        "columns": [
            "u"
        ],
        "created_at": "2022-10-03 22:22:47.76982",
        "distinct_count": 496512,
        "null_count": 0,
        "row_count": 499991
    },
    {
        "avg_size": 4,
        "columns": [
            "v"
        ],
        "created_at": "2022-10-03 22:22:47.76982",
        "distinct_count": 496512,
        "null_count": 0,
        "row_count": 499991
    },
    {
        "avg_size": 25,
        "columns": [
            "j"
        ],
        "created_at": "2022-10-03 22:22:47.76982",
        "distinct_count": 5,
        "histo_buckets": [
            {
                "distinct_range": 0,
                "num_eq": 103378,
                "num_range": 0,
                "upper_bound": "\\x3761000112620001"
            },
            {
                "distinct_range": 0,
                "num_eq": 102478,
                "num_range": 0,
                "upper_bound": "\\x376100012a0200"
            },
            {
                "distinct_range": 0,
                "num_eq": 101998,
                "num_range": 0,
                "upper_bound": "\\x37610002000300012a0a00"
            },
            {
                "distinct_range": 0,
                "num_eq": 96659,
                "num_range": 0,
                "upper_bound": "\\x37610002000362000112630001"
            },
            {
                "distinct_range": 0,
                "num_eq": 96119,
                "num_range": 0,
                "upper_bound": "\\x376200012a0400"
            },
            {
                "distinct_range": 0,
                "num_eq": 99359,
                "num_range": 0,
                "upper_bound": "\\x3766000112670001"
            }
        ],
        "histo_col_type": "BYTES",
        "histo_version": 2,
        "null_count": 0,
        "row_count": 499991
    }
]';
----

# Query only the primary key with a remaining filter. 2+ paths in containment
# query should favor zigzag joins.
opt expect=GenerateInvertedIndexZigzagJoins disable=GenerateMinimalInvertedIndexScans
SELECT k FROM b WHERE j @> '{"a": "b", "c": "d"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup b)
      ├── columns: k:1!null j:4!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [9] = ['\x3761000112620001']
      │    ├── right fixed columns: [9] = ['\x3763000112640001']
      │    └── filters (true)
      └── filters (true)

# Query requiring a zigzag join with a remaining filter.
opt expect=GenerateInvertedIndexZigzagJoins disable=GenerateMinimalInvertedIndexScans
SELECT j, k FROM b WHERE j @> '{"a": "b", "c": "d"}'
----
inner-join (lookup b)
 ├── columns: j:4!null k:1!null
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(4)
 ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
 │    ├── columns: k:1!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [9] = ['\x3761000112620001']
 │    ├── right fixed columns: [9] = ['\x3763000112640001']
 │    └── filters (true)
 └── filters (true)

opt expect=GenerateInvertedIndexZigzagJoins disable=GenerateMinimalInvertedIndexScans
SELECT * FROM b WHERE j @> '{"a": {"b": "c", "d": "e"}, "f": "g"}'
----
inner-join (lookup b)
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
 │    ├── columns: k:1!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [9] = ['\x3761000262000112630001']
 │    ├── right fixed columns: [9] = ['\x3761000264000112650001']
 │    └── filters (true)
 └── filters
      └── j:4 @> '{"a": {"b": "c", "d": "e"}, "f": "g"}' [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# Three or more paths. Should generate zigzag joins.
opt expect=GenerateInvertedIndexZigzagJoins disable=GenerateMinimalInvertedIndexScans
SELECT * FROM b WHERE j @> '{"a":[{"b":"c", "d":3}, 5]}'
----
inner-join (lookup b)
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
 │    ├── columns: k:1!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [9] = ['\x37610002000362000112630001']
 │    ├── right fixed columns: [9] = ['\x3761000200036400012a0600']
 │    └── filters (true)
 └── filters
      └── j:4 @> '{"a": [{"b": "c", "d": 3}, 5]}' [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# Stats on table b created via:
# insert into c select g, ARRAY[1,3], g
#                      from generate_series(1,100000) g(g);
# analyze c;
# Only histogram buckets on column a are preserved.
exec-ddl
ALTER TABLE c INJECT STATISTICS '[
    {
        "avg_size": 4,
        "columns": [
            "k"
        ],
        "created_at": "2022-10-03 22:41:56.480168",
        "distinct_count": 99658,
        "null_count": 0,
        "row_count": 100000
    },
    {
        "avg_size": 7,
        "columns": [
            "a"
        ],
        "created_at": "2022-10-03 22:41:56.480168",
        "distinct_count": 1,
        "histo_buckets": [
            {
                "distinct_range": 0,
                "num_eq": 101040,
                "num_range": 0,
                "upper_bound": "\\x89"
            },
            {
                "distinct_range": 0,
                "num_eq": 98960,
                "num_range": 0,
                "upper_bound": "\\x8b"
            }
        ],
        "histo_col_type": "BYTES",
        "histo_version": 2,
        "null_count": 0,
        "row_count": 100000
    },
    {
        "avg_size": 4,
        "columns": [
            "u"
        ],
        "created_at": "2022-10-03 22:41:56.480168",
        "distinct_count": 99658,
        "null_count": 0,
        "row_count": 100000
    }
]';
----

# We need a remaining filter since only two of the three values
# are covered by the zigzag join.
opt expect=GenerateInvertedIndexZigzagJoins disable=GenerateMinimalInvertedIndexScans
SELECT k FROM c WHERE a @> ARRAY[1,3,1,5]
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup c)
      ├── columns: k:1!null a:2!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── inner-join (zigzag c@a_inv_idx,inverted c@a_inv_idx,inverted)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [8] = ['\x89']
      │    ├── right fixed columns: [8] = ['\x8b']
      │    └── filters (true)
      └── filters
           └── a:2 @> ARRAY[1,3,1,5] [outer=(2), immutable, constraints=(/2: (/NULL - ])]

# Regression test for #95270. We should not need any remaining filter.
opt expect=GenerateInvertedIndexZigzagJoins disable=GenerateMinimalInvertedIndexScans
SELECT k FROM c WHERE a @> ARRAY[1,2]
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup c)
      ├── columns: k:1!null a:2!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── inner-join (zigzag c@a_inv_idx,inverted c@a_inv_idx,inverted)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [8] = ['\x89']
      │    ├── right fixed columns: [8] = ['\x8a']
      │    └── filters (true)
      └── filters (true)

# The first path can't be used for a zigzag join, but the second two can.
opt expect=GenerateInvertedIndexZigzagJoins disable=GenerateMinimalInvertedIndexScans
SELECT * FROM b WHERE j @> '{"a":{}, "b":2, "c":3}'
----
inner-join (lookup b)
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
 │    ├── columns: k:1!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [9] = ['\x376200012a0400']
 │    ├── right fixed columns: [9] = ['\x376300012a0600']
 │    └── filters (true)
 └── filters
      └── j:4 @> '{"a": {}, "b": 2, "c": 3}' [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# We can't build a zigzag join over a disjunction.
opt expect-not=GenerateInvertedIndexZigzagJoins
SELECT * FROM b WHERE j @> '[3]' OR j @> '[[1, 2]]'
----
select
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── index-join b
 │    ├── columns: k:1!null u:2 v:3 j:4
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 │    └── inverted-filter
 │         ├── columns: k:1!null
 │         ├── inverted expression: /9
 │         │    ├── tight: false, unique: true
 │         │    ├── union spans: [Arr/3, Arr/3]
 │         │    └── INTERSECTION
 │         │         ├── span expression
 │         │         │    ├── tight: true, unique: true
 │         │         │    └── union spans: [Arr/Arr/1, Arr/Arr/1]
 │         │         └── span expression
 │         │              ├── tight: true, unique: true
 │         │              └── union spans: [Arr/Arr/2, Arr/Arr/2]
 │         ├── key: (1)
 │         └── scan b@j_inv_idx,inverted
 │              ├── columns: k:1!null j_inverted_key:9!null
 │              └── inverted constraint: /9/1
 │                   └── spans
 │                        ├── [Arr/3, Arr/3]
 │                        ├── [Arr/Arr/1, Arr/Arr/1]
 │                        └── [Arr/Arr/2, Arr/Arr/2]
 └── filters
      └── (j:4 @> '[3]') OR (j:4 @> '[[1, 2]]') [outer=(4), immutable, constraints=(/4: (/NULL - ])]

# GenerateInvertedIndexZigzagJoins propagates row-level locking information.
opt expect=GenerateInvertedIndexZigzagJoins disable=GenerateMinimalInvertedIndexScans
SELECT * FROM b WHERE j @> '{"a":1, "c":2}' FOR UPDATE
----
inner-join (lookup b)
 ├── columns: k:1!null u:2 v:3 j:4!null
 ├── key columns: [1] = [1]
 ├── lookup columns are key
 ├── locking: for-update
 ├── volatile
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)~~>(1,2,4)
 ├── inner-join (zigzag b@j_inv_idx,inverted b@j_inv_idx,inverted)
 │    ├── columns: k:1!null
 │    ├── eq columns: [1] = [1]
 │    ├── left fixed columns: [9] = ['\x376100012a0200']
 │    ├── right fixed columns: [9] = ['\x376300012a0400']
 │    ├── left locking: for-update
 │    ├── right locking: for-update
 │    └── filters (true)
 └── filters (true)

# Zigzag join should not be produced when there is a NO_ZIGZAG_JOIN hint.
opt expect-not=GenerateInvertedIndexZigzagJoins
SELECT k FROM b@{NO_ZIGZAG_JOIN} WHERE j @> '{"a": "b", "c": "d"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null j:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── index-join b
      │    ├── columns: k:1!null j:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── scan b@j_inv_idx,inverted
      │         ├── columns: k:1!null
      │         ├── inverted constraint: /9/1
      │         │    └── spans: ["c"/"d", "c"/"d"]
      │         ├── flags: no-zigzag-join
      │         └── key: (1)
      └── filters
           └── j:4 @> '{"a": "b", "c": "d"}' [outer=(4), immutable, constraints=(/4: (/NULL - ])]

exec-ddl
CREATE TABLE inv_zz_partial (
    k INT PRIMARY KEY,
    j JSON,
    b BOOL,
    s STRING
)
----

exec-ddl
CREATE INVERTED INDEX zz_idx ON inv_zz_partial (j) WHERE b
----

# Generate a zigzag join on a single index with no remaining filter.
opt expect=GenerateInvertedIndexZigzagJoins
SELECT k FROM inv_zz_partial WHERE j @> '{"a": 1, "b": 2}' AND b
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inverted-filter
      ├── columns: k:1!null
      ├── inverted expression: /7
      │    ├── tight: true, unique: true
      │    ├── union spans: empty
      │    └── INTERSECTION
      │         ├── span expression
      │         │    ├── tight: true, unique: true
      │         │    └── union spans: ["a"/1, "a"/1]
      │         └── span expression
      │              ├── tight: true, unique: true
      │              └── union spans: ["b"/2, "b"/2]
      ├── key: (1)
      └── scan inv_zz_partial@zz_idx,inverted,partial
           ├── columns: k:1!null j_inverted_key:7!null
           ├── inverted constraint: /7/1
           │    └── spans
           │         ├── ["a"/1, "a"/1]
           │         └── ["b"/2, "b"/2]
           └── key: (1,7)

# Generate a zigzag join on a single index with a remaining filter.
opt expect=GenerateInvertedIndexZigzagJoins
SELECT k FROM inv_zz_partial WHERE j @> '{"a": 1, "b": 2}' AND b AND s = 'foo'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup inv_zz_partial)
      ├── columns: k:1!null j:2!null b:3!null s:4!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(3,4), (1)-->(2)
      ├── inner-join (zigzag inv_zz_partial@zz_idx,inverted,partial inv_zz_partial@zz_idx,inverted,partial)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [7] = ['\x376100012a0200']
      │    ├── right fixed columns: [7] = ['\x376200012a0400']
      │    └── filters (true)
      └── filters
           └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

# Don't generate a zigzag join when the predicate is not implied.
opt expect-not=GenerateInvertedIndexZigzagJoins format=hide-all
SELECT k FROM inv_zz_partial WHERE j @> '{"a": 1, "b": 2}'
----
project
 └── select
      ├── scan inv_zz_partial
      │    └── partial index predicates
      │         └── zz_idx: filters
      │              └── b
      └── filters
           └── j @> '{"a": 1, "b": 2}'

# Generate a zigzag join with a hint.
opt expect=GenerateInvertedIndexZigzagJoins
SELECT k FROM inv_zz_partial@{FORCE_ZIGZAG} WHERE j @> '{"a": 1, "b": 2}' AND b AND s = 'foo'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup inv_zz_partial)
      ├── columns: k:1!null j:2!null b:3!null s:4!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(3,4), (1)-->(2)
      ├── inner-join (zigzag inv_zz_partial@zz_idx,inverted,partial inv_zz_partial@zz_idx,inverted,partial)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [7] = ['\x376100012a0200']
      │    ├── right fixed columns: [7] = ['\x376200012a0400']
      │    └── filters (true)
      └── filters
           └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

# Generate an inverted zigzag join with a hint.
opt expect=GenerateInvertedIndexZigzagJoins
SELECT k FROM inv_zz_partial@{FORCE_ZIGZAG=zz_idx} WHERE j @> '{"a": 1, "b": 2}' AND b AND s = 'foo'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── inner-join (lookup inv_zz_partial)
      ├── columns: k:1!null j:2!null b:3!null s:4!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(3,4), (1)-->(2)
      ├── inner-join (zigzag inv_zz_partial@zz_idx,inverted,partial inv_zz_partial@zz_idx,inverted,partial)
      │    ├── columns: k:1!null
      │    ├── eq columns: [1] = [1]
      │    ├── left fixed columns: [7] = ['\x376100012a0200']
      │    ├── right fixed columns: [7] = ['\x376200012a0400']
      │    └── filters (true)
      └── filters
           └── s:4 = 'foo' [outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

exec-ddl
DROP INDEX zz_idx
----

exec-ddl
CREATE INVERTED INDEX zz_idx ON inv_zz_partial (j) WHERE j->'a' = '1'
----

# Don't generate a zigzag join when one of the the expressions that fixes the
# columns is removed while proving partial index implication.
opt expect-not=GenerateInvertedIndexZigzagJoins format=hide-all
SELECT k FROM inv_zz_partial WHERE j->'a' = '1' AND j->'b' = '2'
----
project
 └── scan inv_zz_partial@zz_idx,inverted,partial
      └── inverted constraint: /8/1
           └── spans: ["b"/2, "b"/2]

# --------------------------------------------------
# SplitDisjunction
# --------------------------------------------------

# TODO(mgartner): PruneAggCols should be pruning columns from the DistinctOn
# and further down the expression tree, ultimately eliminating the index-joins.
# PruneAggCols does not run in this case because normalization rules do not run
# at the root tree generated by an exploration rule.
opt expect=SplitDisjunction
SELECT k FROM d WHERE u = 1 OR v = 1
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── ordering: +1
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8), (7)-->(9)
      │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7: [/1 - /1]
      │    │         ├── key: (7)
      │    │         ├── fd: ()-->(8)
      │    │         └── ordering: +7 opt(8) [actual: +7]
      │    └── index-join d
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: ()-->(15), (13)-->(14)
      │         ├── ordering: +13 opt(15) [actual: +13]
      │         └── scan d@v
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/13: [/1 - /1]
      │              ├── key: (13)
      │              ├── fd: ()-->(15)
      │              └── ordering: +13 opt(15) [actual: +13]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

opt expect=SplitDisjunction
SELECT * FROM d WHERE w = 1 AND (u = 1 OR v = 1)
----
distinct-on
 ├── columns: k:1!null u:2 v:3 w:4!null
 ├── grouping columns: k:1!null
 ├── internal-ordering: +1
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3)
 ├── union-all
 │    ├── columns: k:1!null u:2 v:3 w:4!null
 │    ├── left columns: k:7 u:8 v:9 w:10
 │    ├── right columns: k:13 u:14 v:15 w:16
 │    ├── ordering: +1
 │    ├── select
 │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
 │    │    ├── key: (7)
 │    │    ├── fd: ()-->(8,10), (7)-->(9)
 │    │    ├── ordering: +7 opt(8,10) [actual: +7]
 │    │    ├── index-join d
 │    │    │    ├── columns: k:7!null u:8 v:9 w:10
 │    │    │    ├── key: (7)
 │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
 │    │    │    ├── ordering: +7 opt(8) [actual: +7]
 │    │    │    └── scan d@u
 │    │    │         ├── columns: k:7!null u:8!null
 │    │    │         ├── constraint: /8/7: [/1 - /1]
 │    │    │         ├── key: (7)
 │    │    │         ├── fd: ()-->(8)
 │    │    │         └── ordering: +7 opt(8) [actual: +7]
 │    │    └── filters
 │    │         └── w:10 = 1 [outer=(10), constraints=(/10: [/1 - /1]; tight), fd=()-->(10)]
 │    └── select
 │         ├── columns: k:13!null u:14 v:15!null w:16!null
 │         ├── key: (13)
 │         ├── fd: ()-->(15,16), (13)-->(14)
 │         ├── ordering: +13 opt(15,16) [actual: +13]
 │         ├── index-join d
 │         │    ├── columns: k:13!null u:14 v:15 w:16
 │         │    ├── key: (13)
 │         │    ├── fd: ()-->(15), (13)-->(14,16)
 │         │    ├── ordering: +13 opt(15) [actual: +13]
 │         │    └── scan d@v
 │         │         ├── columns: k:13!null v:15!null
 │         │         ├── constraint: /15/13: [/1 - /1]
 │         │         ├── key: (13)
 │         │         ├── fd: ()-->(15)
 │         │         └── ordering: +13 opt(15) [actual: +13]
 │         └── filters
 │              └── w:16 = 1 [outer=(16), constraints=(/16: [/1 - /1]; tight), fd=()-->(16)]
 └── aggregations
      ├── const-agg [as=u:2, outer=(2)]
      │    └── u:2
      ├── const-agg [as=v:3, outer=(3)]
      │    └── v:3
      └── const-agg [as=w:4, outer=(4)]
           └── w:4

opt expect=SplitDisjunction
SELECT k FROM d WHERE (u = 1 OR v = 2) AND (u = 10 OR v = 20)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2!null v:3!null
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── inner-join (zigzag d@u d@v)
      │    │    ├── columns: k:7!null u:8!null v:9!null
      │    │    ├── eq columns: [7] = [7]
      │    │    ├── left fixed columns: [8] = [1]
      │    │    ├── right fixed columns: [9] = [20]
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8,9)
      │    │    └── filters
      │    │         ├── u:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)]
      │    │         └── v:9 = 20 [outer=(9), constraints=(/9: [/20 - /20]; tight), fd=()-->(9)]
      │    └── inner-join (zigzag d@u d@v)
      │         ├── columns: k:13!null u:14!null v:15!null
      │         ├── eq columns: [13] = [13]
      │         ├── left fixed columns: [14] = [10]
      │         ├── right fixed columns: [15] = [2]
      │         ├── key: (13)
      │         ├── fd: ()-->(14,15)
      │         └── filters
      │              ├── v:15 = 2 [outer=(15), constraints=(/15: [/2 - /2]; tight), fd=()-->(15)]
      │              └── u:14 = 10 [outer=(14), constraints=(/14: [/10 - /10]; tight), fd=()-->(14)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

opt expect=SplitDisjunction
SELECT sum(k) FROM d WHERE u = 1 OR v = 1
----
scalar-group-by
 ├── columns: sum:7
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(7)
 ├── distinct-on
 │    ├── columns: k:1!null u:2 v:3
 │    ├── grouping columns: k:1!null
 │    ├── internal-ordering: +1
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── union-all
 │    │    ├── columns: k:1!null u:2 v:3
 │    │    ├── left columns: k:8 u:9 v:10
 │    │    ├── right columns: k:14 u:15 v:16
 │    │    ├── ordering: +1
 │    │    ├── index-join d
 │    │    │    ├── columns: k:8!null u:9!null v:10
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: ()-->(9), (8)-->(10)
 │    │    │    ├── ordering: +8 opt(9) [actual: +8]
 │    │    │    └── scan d@u
 │    │    │         ├── columns: k:8!null u:9!null
 │    │    │         ├── constraint: /9/8: [/1 - /1]
 │    │    │         ├── key: (8)
 │    │    │         ├── fd: ()-->(9)
 │    │    │         └── ordering: +8 opt(9) [actual: +8]
 │    │    └── index-join d
 │    │         ├── columns: k:14!null u:15 v:16!null
 │    │         ├── key: (14)
 │    │         ├── fd: ()-->(16), (14)-->(15)
 │    │         ├── ordering: +14 opt(16) [actual: +14]
 │    │         └── scan d@v
 │    │              ├── columns: k:14!null v:16!null
 │    │              ├── constraint: /16/14: [/1 - /1]
 │    │              ├── key: (14)
 │    │              ├── fd: ()-->(16)
 │    │              └── ordering: +14 opt(16) [actual: +14]
 │    └── aggregations
 │         ├── const-agg [as=u:2, outer=(2)]
 │         │    └── u:2
 │         └── const-agg [as=v:3, outer=(3)]
 │              └── v:3
 └── aggregations
      └── sum [as=sum:7, outer=(1)]
           └── k:1

# Multi-column primary key.
opt expect=SplitDisjunction
SELECT k, j FROM f WHERE u = 1 OR v = 2
----
project
 ├── columns: k:1!null j:2!null
 ├── key: (1,2)
 └── distinct-on
      ├── columns: k:1!null j:2!null u:3 v:4
      ├── grouping columns: k:1!null j:2!null
      ├── internal-ordering: +1,+2
      ├── key: (1,2)
      ├── fd: (1,2)-->(3,4)
      ├── union-all
      │    ├── columns: k:1!null j:2!null u:3 v:4
      │    ├── left columns: k:7 j:8 u:9 v:10
      │    ├── right columns: k:13 j:14 u:15 v:16
      │    ├── ordering: +1,+2
      │    ├── index-join f
      │    │    ├── columns: k:7!null j:8!null u:9!null v:10
      │    │    ├── key: (7,8)
      │    │    ├── fd: ()-->(9), (7,8)-->(10)
      │    │    ├── ordering: +7,+8 opt(9) [actual: +7,+8]
      │    │    └── scan f@u
      │    │         ├── columns: k:7!null j:8!null u:9!null
      │    │         ├── constraint: /9/7/8: [/1 - /1]
      │    │         ├── key: (7,8)
      │    │         ├── fd: ()-->(9)
      │    │         └── ordering: +7,+8 opt(9) [actual: +7,+8]
      │    └── index-join f
      │         ├── columns: k:13!null j:14!null u:15 v:16!null
      │         ├── key: (13,14)
      │         ├── fd: ()-->(16), (13,14)-->(15)
      │         ├── ordering: +13,+14 opt(16) [actual: +13,+14]
      │         └── scan f@v
      │              ├── columns: k:13!null j:14!null v:16!null
      │              ├── constraint: /16/13/14: [/2 - /2]
      │              ├── key: (13,14)
      │              ├── fd: ()-->(16)
      │              └── ordering: +13,+14 opt(16) [actual: +13,+14]
      └── aggregations
           ├── const-agg [as=u:3, outer=(3)]
           │    └── u:3
           └── const-agg [as=v:4, outer=(4)]
                └── v:4

# Don't expand INs to many ORs.
opt expect=SplitDisjunction
SELECT k FROM d WHERE u IN (1, 2, 3, 4) OR v IN (5, 6, 7, 8)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: (7)-->(8,9)
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7: [/1 - /4]
      │    │         ├── key: (7)
      │    │         └── fd: (7)-->(8)
      │    └── index-join d
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: (13)-->(14,15)
      │         └── scan d@v
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/13: [/5 - /8]
      │              ├── key: (13)
      │              └── fd: (13)-->(15)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Split and constrain with an inverted index on JSONB on one side.
opt expect=SplitDisjunction
SELECT k FROM b WHERE k = 1 OR j @> '{"foo": "bar"}'
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null j:4
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(4)
      ├── union-all
      │    ├── columns: k:1!null j:4
      │    ├── left columns: k:10 j:13
      │    ├── right columns: k:19 j:22
      │    ├── immutable
      │    ├── ordering: +1
      │    ├── scan b
      │    │    ├── columns: k:10!null j:13
      │    │    ├── constraint: /10: [/1 - /1]
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    └── fd: ()-->(10,13)
      │    └── index-join b
      │         ├── columns: k:19!null j:22!null
      │         ├── immutable
      │         ├── key: (19)
      │         ├── fd: (19)-->(22)
      │         ├── ordering: +19
      │         └── sort
      │              ├── columns: k:19!null
      │              ├── key: (19)
      │              ├── ordering: +19
      │              └── scan b@j_inv_idx,inverted
      │                   ├── columns: k:19!null
      │                   ├── inverted constraint: /27/19
      │                   │    └── spans: ["foo"/"bar", "foo"/"bar"]
      │                   └── key: (19)
      └── aggregations
           └── const-agg [as=j:4, outer=(4)]
                └── j:4

# Split and constrain with an inverted index on an array on one side.
opt expect=SplitDisjunction
SELECT k FROM c WHERE k = 1 OR a @> ARRAY[2]
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null a:2
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── union-all
      │    ├── columns: k:1!null a:2
      │    ├── left columns: k:9 a:10
      │    ├── right columns: k:17 a:18
      │    ├── immutable
      │    ├── ordering: +1
      │    ├── scan c
      │    │    ├── columns: k:9!null a:10
      │    │    ├── constraint: /9: [/1 - /1]
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    └── fd: ()-->(9,10)
      │    └── index-join c
      │         ├── columns: k:17!null a:18!null
      │         ├── immutable
      │         ├── key: (17)
      │         ├── fd: (17)-->(18)
      │         ├── ordering: +17
      │         └── sort
      │              ├── columns: k:17!null
      │              ├── key: (17)
      │              ├── ordering: +17
      │              └── scan c@a_inv_idx,inverted
      │                   ├── columns: k:17!null
      │                   ├── inverted constraint: /24/17
      │                   │    └── spans: [2, 2]
      │                   └── key: (17)
      └── aggregations
           └── const-agg [as=a:2, outer=(2)]
                └── a:2

# Uncorrelated subquery.
opt expect=SplitDisjunction
SELECT k FROM d WHERE (u = 1 OR v = 1) AND EXISTS (SELECT u, v FROM a)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: d.k:1!null d.u:2 d.v:3
      ├── grouping columns: d.k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: d.k:1!null d.u:2 d.v:3
      │    ├── left columns: d.k:14 d.u:15 d.v:16
      │    ├── right columns: d.k:20 d.u:21 d.v:22
      │    ├── ordering: +1
      │    ├── index-join d
      │    │    ├── columns: d.k:14!null d.u:15!null d.v:16
      │    │    ├── key: (14)
      │    │    ├── fd: ()-->(15), (14)-->(16)
      │    │    ├── ordering: +14 opt(15) [actual: +14]
      │    │    └── select
      │    │         ├── columns: d.k:14!null d.u:15!null
      │    │         ├── key: (14)
      │    │         ├── fd: ()-->(15)
      │    │         ├── ordering: +14 opt(15) [actual: +14]
      │    │         ├── scan d@u
      │    │         │    ├── columns: d.k:14!null d.u:15!null
      │    │         │    ├── constraint: /15/14: [/1 - /1]
      │    │         │    ├── key: (14)
      │    │         │    ├── fd: ()-->(15)
      │    │         │    └── ordering: +14 opt(15) [actual: +14]
      │    │         └── filters
      │    │              └── coalesce [subquery]
      │    │                   ├── subquery
      │    │                   │    └── project
      │    │                   │         ├── columns: column13:13!null
      │    │                   │         ├── cardinality: [0 - 1]
      │    │                   │         ├── key: ()
      │    │                   │         ├── fd: ()-->(13)
      │    │                   │         ├── scan a
      │    │                   │         │    ├── limit: 1
      │    │                   │         │    └── key: ()
      │    │                   │         └── projections
      │    │                   │              └── true [as=column13:13]
      │    │                   └── false
      │    └── index-join d
      │         ├── columns: d.k:20!null d.u:21 d.v:22!null
      │         ├── key: (20)
      │         ├── fd: ()-->(22), (20)-->(21)
      │         ├── ordering: +20 opt(22) [actual: +20]
      │         └── select
      │              ├── columns: d.k:20!null d.v:22!null
      │              ├── key: (20)
      │              ├── fd: ()-->(22)
      │              ├── ordering: +20 opt(22) [actual: +20]
      │              ├── scan d@v
      │              │    ├── columns: d.k:20!null d.v:22!null
      │              │    ├── constraint: /22/20: [/1 - /1]
      │              │    ├── key: (20)
      │              │    ├── fd: ()-->(22)
      │              │    └── ordering: +20 opt(22) [actual: +20]
      │              └── filters
      │                   └── coalesce [subquery]
      │                        ├── subquery
      │                        │    └── project
      │                        │         ├── columns: column13:13!null
      │                        │         ├── cardinality: [0 - 1]
      │                        │         ├── key: ()
      │                        │         ├── fd: ()-->(13)
      │                        │         ├── scan a
      │                        │         │    ├── limit: 1
      │                        │         │    └── key: ()
      │                        │         └── projections
      │                        │              └── true [as=column13:13]
      │                        └── false
      └── aggregations
           ├── const-agg [as=d.u:2, outer=(2)]
           │    └── d.u:2
           └── const-agg [as=d.v:3, outer=(3)]
                └── d.v:3

# Correlated subquery.
opt expect=SplitDisjunction
SELECT k FROM d WHERE (u = 1 OR v = 1) AND EXISTS (SELECT * FROM a WHERE a.u = d.u)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── semi-join (lookup a@u)
      ├── columns: d.k:1!null d.u:2 d.v:3
      ├── key columns: [2] = [8]
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── distinct-on
      │    ├── columns: d.k:1!null d.u:2 d.v:3
      │    ├── grouping columns: d.k:1!null
      │    ├── internal-ordering: +1
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    ├── union-all
      │    │    ├── columns: d.k:1!null d.u:2 d.v:3
      │    │    ├── left columns: d.k:13 d.u:14 d.v:15
      │    │    ├── right columns: d.k:19 d.u:20 d.v:21
      │    │    ├── ordering: +1
      │    │    ├── index-join d
      │    │    │    ├── columns: d.k:13!null d.u:14!null d.v:15
      │    │    │    ├── key: (13)
      │    │    │    ├── fd: ()-->(14), (13)-->(15)
      │    │    │    ├── ordering: +13 opt(14) [actual: +13]
      │    │    │    └── scan d@u
      │    │    │         ├── columns: d.k:13!null d.u:14!null
      │    │    │         ├── constraint: /14/13: [/1 - /1]
      │    │    │         ├── key: (13)
      │    │    │         ├── fd: ()-->(14)
      │    │    │         └── ordering: +13 opt(14) [actual: +13]
      │    │    └── index-join d
      │    │         ├── columns: d.k:19!null d.u:20 d.v:21!null
      │    │         ├── key: (19)
      │    │         ├── fd: ()-->(21), (19)-->(20)
      │    │         ├── ordering: +19 opt(21) [actual: +19]
      │    │         └── scan d@v
      │    │              ├── columns: d.k:19!null d.v:21!null
      │    │              ├── constraint: /21/19: [/1 - /1]
      │    │              ├── key: (19)
      │    │              ├── fd: ()-->(21)
      │    │              └── ordering: +19 opt(21) [actual: +19]
      │    └── aggregations
      │         ├── const-agg [as=d.u:2, outer=(2)]
      │         │    └── d.u:2
      │         └── const-agg [as=d.v:3, outer=(3)]
      │              └── d.v:3
      └── filters (true)

# Correlated subquery with references to outer columns not in the scan columns.
opt expect=SplitDisjunction
SELECT k FROM d WHERE (u = 1 OR v = 1) AND EXISTS (SELECT * FROM a WHERE a.u = d.w)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── semi-join (lookup a@u)
      ├── columns: d.k:1!null d.u:2 d.v:3 w:4
      ├── key columns: [4] = [8]
      ├── key: (1)
      ├── fd: (1)-->(2-4)
      ├── distinct-on
      │    ├── columns: d.k:1!null d.u:2 d.v:3 w:4
      │    ├── grouping columns: d.k:1!null
      │    ├── internal-ordering: +1
      │    ├── key: (1)
      │    ├── fd: (1)-->(2-4)
      │    ├── union-all
      │    │    ├── columns: d.k:1!null d.u:2 d.v:3 w:4
      │    │    ├── left columns: d.k:13 d.u:14 d.v:15 w:16
      │    │    ├── right columns: d.k:19 d.u:20 d.v:21 w:22
      │    │    ├── ordering: +1
      │    │    ├── index-join d
      │    │    │    ├── columns: d.k:13!null d.u:14!null d.v:15 w:16
      │    │    │    ├── key: (13)
      │    │    │    ├── fd: ()-->(14), (13)-->(15,16)
      │    │    │    ├── ordering: +13 opt(14) [actual: +13]
      │    │    │    └── scan d@u
      │    │    │         ├── columns: d.k:13!null d.u:14!null
      │    │    │         ├── constraint: /14/13: [/1 - /1]
      │    │    │         ├── key: (13)
      │    │    │         ├── fd: ()-->(14)
      │    │    │         └── ordering: +13 opt(14) [actual: +13]
      │    │    └── index-join d
      │    │         ├── columns: d.k:19!null d.u:20 d.v:21!null w:22
      │    │         ├── key: (19)
      │    │         ├── fd: ()-->(21), (19)-->(20,22)
      │    │         ├── ordering: +19 opt(21) [actual: +19]
      │    │         └── scan d@v
      │    │              ├── columns: d.k:19!null d.v:21!null
      │    │              ├── constraint: /21/19: [/1 - /1]
      │    │              ├── key: (19)
      │    │              ├── fd: ()-->(21)
      │    │              └── ordering: +19 opt(21) [actual: +19]
      │    └── aggregations
      │         ├── const-agg [as=d.u:2, outer=(2)]
      │         │    └── d.u:2
      │         ├── const-agg [as=d.v:3, outer=(3)]
      │         │    └── d.v:3
      │         └── const-agg [as=w:4, outer=(4)]
      │              └── w:4
      └── filters (true)

# Apply when outer columns of both sides of OR are a subset of index columns.
opt expect=SplitDisjunction
SELECT k, u, v FROM e WHERE u = 1 OR v = 1
----
distinct-on
 ├── columns: k:1!null u:2 v:3
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── union-all
 │    ├── columns: k:1!null u:2 v:3
 │    ├── left columns: k:7 u:8 v:9
 │    ├── right columns: k:13 u:14 v:15
 │    ├── index-join e
 │    │    ├── columns: k:7!null u:8!null v:9
 │    │    ├── key: (7)
 │    │    ├── fd: ()-->(8), (7)-->(9)
 │    │    └── scan e@uw
 │    │         ├── columns: k:7!null u:8!null
 │    │         ├── constraint: /8/10/7: [/1 - /1]
 │    │         ├── key: (7)
 │    │         └── fd: ()-->(8)
 │    └── index-join e
 │         ├── columns: k:13!null u:14 v:15!null
 │         ├── key: (13)
 │         ├── fd: ()-->(15), (13)-->(14)
 │         └── scan e@vw
 │              ├── columns: k:13!null v:15!null
 │              ├── constraint: /15/16/13: [/1 - /1]
 │              ├── key: (13)
 │              └── fd: ()-->(15)
 └── aggregations
      ├── const-agg [as=u:2, outer=(2)]
      │    └── u:2
      └── const-agg [as=v:3, outer=(3)]
           └── v:3

# Apply when outer columns of both sides of OR are a superset of index columns.
opt expect=SplitDisjunction
SELECT k, u, v FROM d WHERE (u = 1 AND w = 2) OR (v = 1 AND w = 3)
----
project
 ├── columns: k:1!null u:2 v:3
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3 w:4!null
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2-4)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3 w:4!null
      │    ├── left columns: k:7 u:8 v:9 w:10
      │    ├── right columns: k:13 u:14 v:15 w:16
      │    ├── ordering: +1
      │    ├── select
      │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8,10), (7)-->(9)
      │    │    ├── ordering: +7 opt(8,10) [actual: +7]
      │    │    ├── index-join d
      │    │    │    ├── columns: k:7!null u:8 v:9 w:10
      │    │    │    ├── key: (7)
      │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
      │    │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    │    └── scan d@u
      │    │    │         ├── columns: k:7!null u:8!null
      │    │    │         ├── constraint: /8/7: [/1 - /1]
      │    │    │         ├── key: (7)
      │    │    │         ├── fd: ()-->(8)
      │    │    │         └── ordering: +7 opt(8) [actual: +7]
      │    │    └── filters
      │    │         └── w:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)]
      │    └── select
      │         ├── columns: k:13!null u:14 v:15!null w:16!null
      │         ├── key: (13)
      │         ├── fd: ()-->(15,16), (13)-->(14)
      │         ├── ordering: +13 opt(15,16) [actual: +13]
      │         ├── index-join d
      │         │    ├── columns: k:13!null u:14 v:15 w:16
      │         │    ├── key: (13)
      │         │    ├── fd: ()-->(15), (13)-->(14,16)
      │         │    ├── ordering: +13 opt(15) [actual: +13]
      │         │    └── scan d@v
      │         │         ├── columns: k:13!null v:15!null
      │         │         ├── constraint: /15/13: [/1 - /1]
      │         │         ├── key: (13)
      │         │         ├── fd: ()-->(15)
      │         │         └── ordering: +13 opt(15) [actual: +13]
      │         └── filters
      │              └── w:16 = 3 [outer=(16), constraints=(/16: [/3 - /3]; tight), fd=()-->(16)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           ├── const-agg [as=v:3, outer=(3)]
           │    └── v:3
           └── const-agg [as=w:4, outer=(4)]
                └── w:4

# Apply when outer columns of both sides of OR are a superset of index columns.
opt expect=SplitDisjunction
SELECT k, u, v FROM d WHERE (u = 1 AND w = 2) OR (v = 1 AND w = 3)
----
project
 ├── columns: k:1!null u:2 v:3
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3 w:4!null
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2-4)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3 w:4!null
      │    ├── left columns: k:7 u:8 v:9 w:10
      │    ├── right columns: k:13 u:14 v:15 w:16
      │    ├── ordering: +1
      │    ├── select
      │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8,10), (7)-->(9)
      │    │    ├── ordering: +7 opt(8,10) [actual: +7]
      │    │    ├── index-join d
      │    │    │    ├── columns: k:7!null u:8 v:9 w:10
      │    │    │    ├── key: (7)
      │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
      │    │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    │    └── scan d@u
      │    │    │         ├── columns: k:7!null u:8!null
      │    │    │         ├── constraint: /8/7: [/1 - /1]
      │    │    │         ├── key: (7)
      │    │    │         ├── fd: ()-->(8)
      │    │    │         └── ordering: +7 opt(8) [actual: +7]
      │    │    └── filters
      │    │         └── w:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)]
      │    └── select
      │         ├── columns: k:13!null u:14 v:15!null w:16!null
      │         ├── key: (13)
      │         ├── fd: ()-->(15,16), (13)-->(14)
      │         ├── ordering: +13 opt(15,16) [actual: +13]
      │         ├── index-join d
      │         │    ├── columns: k:13!null u:14 v:15 w:16
      │         │    ├── key: (13)
      │         │    ├── fd: ()-->(15), (13)-->(14,16)
      │         │    ├── ordering: +13 opt(15) [actual: +13]
      │         │    └── scan d@v
      │         │         ├── columns: k:13!null v:15!null
      │         │         ├── constraint: /15/13: [/1 - /1]
      │         │         ├── key: (13)
      │         │         ├── fd: ()-->(15)
      │         │         └── ordering: +13 opt(15) [actual: +13]
      │         └── filters
      │              └── w:16 = 3 [outer=(16), constraints=(/16: [/3 - /3]; tight), fd=()-->(16)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           ├── const-agg [as=v:3, outer=(3)]
           │    └── v:3
           └── const-agg [as=w:4, outer=(4)]
                └── w:4

# Group sub-expr with the same columns together.
opt expect=SplitDisjunction
SELECT k, u, v FROM d WHERE (u = 1 OR v = 2) OR (u = 3 OR v = 4)
----
distinct-on
 ├── columns: k:1!null u:2 v:3
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── union-all
 │    ├── columns: k:1!null u:2 v:3
 │    ├── left columns: k:7 u:8 v:9
 │    ├── right columns: k:13 u:14 v:15
 │    ├── index-join d
 │    │    ├── columns: k:7!null u:8!null v:9
 │    │    ├── key: (7)
 │    │    ├── fd: (7)-->(8,9)
 │    │    └── scan d@u
 │    │         ├── columns: k:7!null u:8!null
 │    │         ├── constraint: /8/7
 │    │         │    ├── [/1 - /1]
 │    │         │    └── [/3 - /3]
 │    │         ├── key: (7)
 │    │         └── fd: (7)-->(8)
 │    └── index-join d
 │         ├── columns: k:13!null u:14 v:15!null
 │         ├── key: (13)
 │         ├── fd: (13)-->(14,15)
 │         └── scan d@v
 │              ├── columns: k:13!null v:15!null
 │              ├── constraint: /15/13
 │              │    ├── [/2 - /2]
 │              │    └── [/4 - /4]
 │              ├── key: (13)
 │              └── fd: (13)-->(15)
 └── aggregations
      ├── const-agg [as=u:2, outer=(2)]
      │    └── u:2
      └── const-agg [as=v:3, outer=(3)]
           └── v:3

# Group sub-expr with the same columns together. Output should have a single union expr.
opt expect=SplitDisjunction
SELECT k, u, v FROM d WHERE u = 1 OR u = 3 OR v = 2 OR v = 4 OR u = 5 OR v = 6
----
distinct-on
 ├── columns: k:1!null u:2 v:3
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── union-all
 │    ├── columns: k:1!null u:2 v:3
 │    ├── left columns: k:7 u:8 v:9
 │    ├── right columns: k:13 u:14 v:15
 │    ├── index-join d
 │    │    ├── columns: k:7!null u:8!null v:9
 │    │    ├── key: (7)
 │    │    ├── fd: (7)-->(8,9)
 │    │    └── scan d@u
 │    │         ├── columns: k:7!null u:8!null
 │    │         ├── constraint: /8/7
 │    │         │    ├── [/1 - /1]
 │    │         │    ├── [/3 - /3]
 │    │         │    └── [/5 - /5]
 │    │         ├── key: (7)
 │    │         └── fd: (7)-->(8)
 │    └── index-join d
 │         ├── columns: k:13!null u:14 v:15!null
 │         ├── key: (13)
 │         ├── fd: (13)-->(14,15)
 │         └── scan d@v
 │              ├── columns: k:13!null v:15!null
 │              ├── constraint: /15/13
 │              │    ├── [/2 - /2]
 │              │    ├── [/4 - /4]
 │              │    └── [/6 - /6]
 │              ├── key: (13)
 │              └── fd: (13)-->(15)
 └── aggregations
      ├── const-agg [as=u:2, outer=(2)]
      │    └── u:2
      └── const-agg [as=v:3, outer=(3)]
           └── v:3

# Group sub-expr with the same columns together. Output should have a single union expr.
opt expect=SplitDisjunction
SELECT k, u, v FROM d WHERE (u = 3 OR v = 2) OR (u = 5 OR v = 4) OR v = 6
----
distinct-on
 ├── columns: k:1!null u:2 v:3
 ├── grouping columns: k:1!null
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── union-all
 │    ├── columns: k:1!null u:2 v:3
 │    ├── left columns: k:7 u:8 v:9
 │    ├── right columns: k:13 u:14 v:15
 │    ├── index-join d
 │    │    ├── columns: k:7!null u:8!null v:9
 │    │    ├── key: (7)
 │    │    ├── fd: (7)-->(8,9)
 │    │    └── scan d@u
 │    │         ├── columns: k:7!null u:8!null
 │    │         ├── constraint: /8/7
 │    │         │    ├── [/3 - /3]
 │    │         │    └── [/5 - /5]
 │    │         ├── key: (7)
 │    │         └── fd: (7)-->(8)
 │    └── index-join d
 │         ├── columns: k:13!null u:14 v:15!null
 │         ├── key: (13)
 │         ├── fd: (13)-->(14,15)
 │         └── scan d@v
 │              ├── columns: k:13!null v:15!null
 │              ├── constraint: /15/13
 │              │    ├── [/2 - /2]
 │              │    ├── [/4 - /4]
 │              │    └── [/6 - /6]
 │              ├── key: (13)
 │              └── fd: (13)-->(15)
 └── aggregations
      ├── const-agg [as=u:2, outer=(2)]
      │    └── u:2
      └── const-agg [as=v:3, outer=(3)]
           └── v:3

# Find the first disjunction in the filters that have different column sets on
# the left and right.
opt expect=SplitDisjunction
SELECT k FROM d WHERE (w = 1 OR w = 2) AND (u = 3 OR v = 4)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3 w:4!null
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2-4)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3 w:4!null
      │    ├── left columns: k:7 u:8 v:9 w:10
      │    ├── right columns: k:13 u:14 v:15 w:16
      │    ├── ordering: +1
      │    ├── select
      │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8), (7)-->(9,10)
      │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    ├── index-join d
      │    │    │    ├── columns: k:7!null u:8 v:9 w:10
      │    │    │    ├── key: (7)
      │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
      │    │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    │    └── scan d@u
      │    │    │         ├── columns: k:7!null u:8!null
      │    │    │         ├── constraint: /8/7: [/3 - /3]
      │    │    │         ├── key: (7)
      │    │    │         ├── fd: ()-->(8)
      │    │    │         └── ordering: +7 opt(8) [actual: +7]
      │    │    └── filters
      │    │         └── (w:10 = 1) OR (w:10 = 2) [outer=(10), constraints=(/10: [/1 - /1] [/2 - /2]; tight)]
      │    └── select
      │         ├── columns: k:13!null u:14 v:15!null w:16!null
      │         ├── key: (13)
      │         ├── fd: ()-->(15), (13)-->(14,16)
      │         ├── ordering: +13 opt(15) [actual: +13]
      │         ├── index-join d
      │         │    ├── columns: k:13!null u:14 v:15 w:16
      │         │    ├── key: (13)
      │         │    ├── fd: ()-->(15), (13)-->(14,16)
      │         │    ├── ordering: +13 opt(15) [actual: +13]
      │         │    └── scan d@v
      │         │         ├── columns: k:13!null v:15!null
      │         │         ├── constraint: /15/13: [/4 - /4]
      │         │         ├── key: (13)
      │         │         ├── fd: ()-->(15)
      │         │         └── ordering: +13 opt(15) [actual: +13]
      │         └── filters
      │              └── (w:16 = 1) OR (w:16 = 2) [outer=(16), constraints=(/16: [/1 - /1] [/2 - /2]; tight)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           ├── const-agg [as=v:3, outer=(3)]
           │    └── v:3
           └── const-agg [as=w:4, outer=(4)]
                └── w:4

# Find the first disjunction in the filters that have columns on the left and
# right that constrain a scan.
opt expect=SplitDisjunction
SELECT k FROM d WHERE (u = 1 OR w = 2) AND (u = 3 OR v = 4)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3 w:4
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2-4)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3 w:4
      │    ├── left columns: k:7 u:8 v:9 w:10
      │    ├── right columns: k:13 u:14 v:15 w:16
      │    ├── ordering: +1
      │    ├── select
      │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8,10), (7)-->(9)
      │    │    ├── ordering: +7 opt(8,10) [actual: +7]
      │    │    ├── index-join d
      │    │    │    ├── columns: k:7!null u:8 v:9 w:10
      │    │    │    ├── key: (7)
      │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
      │    │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    │    └── scan d@u
      │    │    │         ├── columns: k:7!null u:8!null
      │    │    │         ├── constraint: /8/7: [/3 - /3]
      │    │    │         ├── key: (7)
      │    │    │         ├── fd: ()-->(8)
      │    │    │         └── ordering: +7 opt(8) [actual: +7]
      │    │    └── filters
      │    │         └── w:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)]
      │    └── select
      │         ├── columns: k:13!null u:14 v:15!null w:16
      │         ├── key: (13)
      │         ├── fd: ()-->(15), (13)-->(14,16)
      │         ├── ordering: +13 opt(15) [actual: +13]
      │         ├── index-join d
      │         │    ├── columns: k:13!null u:14 v:15 w:16
      │         │    ├── key: (13)
      │         │    ├── fd: ()-->(15), (13)-->(14,16)
      │         │    ├── ordering: +13 opt(15) [actual: +13]
      │         │    └── scan d@v
      │         │         ├── columns: k:13!null v:15!null
      │         │         ├── constraint: /15/13: [/4 - /4]
      │         │         ├── key: (13)
      │         │         ├── fd: ()-->(15)
      │         │         └── ordering: +13 opt(15) [actual: +13]
      │         └── filters
      │              └── (u:14 = 1) OR (w:16 = 2) [outer=(14,16)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           ├── const-agg [as=v:3, outer=(3)]
           │    └── v:3
           └── const-agg [as=w:4, outer=(4)]
                └── w:4

# Don't apply when outer columns of both sides of OR do not intersect with index columns.
opt expect-not=SplitDisjunction
SELECT k, u, w FROM d WHERE u = 1 OR w = 1
----
select
 ├── columns: k:1!null u:2 w:4
 ├── key: (1)
 ├── fd: (1)-->(2,4)
 ├── scan d
 │    ├── columns: k:1!null u:2 w:4
 │    ├── key: (1)
 │    └── fd: (1)-->(2,4)
 └── filters
      └── (u:2 = 1) OR (w:4 = 1) [outer=(2,4)]

# Don't apply to queries without strict keys.
opt expect-not=SplitDisjunction
SELECT u, v FROM d WHERE u = 1 OR v = 1
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── ordering: +1
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8), (7)-->(9)
      │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7: [/1 - /1]
      │    │         ├── key: (7)
      │    │         ├── fd: ()-->(8)
      │    │         └── ordering: +7 opt(8) [actual: +7]
      │    └── index-join d
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: ()-->(15), (13)-->(14)
      │         ├── ordering: +13 opt(15) [actual: +13]
      │         └── scan d@v
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/13: [/1 - /1]
      │              ├── key: (13)
      │              ├── fd: ()-->(15)
      │              └── ordering: +13 opt(15) [actual: +13]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Don't apply to disjunctions with identical colsets on the left and right.
opt expect-not=SplitDisjunction
SELECT k FROM d WHERE u = 1 OR u = 5
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── scan d@u
      ├── columns: k:1!null u:2!null
      ├── constraint: /2/1
      │    ├── [/1 - /1]
      │    └── [/5 - /5]
      ├── key: (1)
      └── fd: (1)-->(2)

# Verifies that flags are copied to the duplicated scan.
opt expect=SplitDisjunction
SELECT k FROM a@{NO_INDEX_JOIN} WHERE u = 1 OR v = 1
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2,3), (3)~~>(1,2)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:6 u:7 v:8
      │    ├── right columns: k:11 u:12 v:13
      │    ├── ordering: +1
      │    ├── scan a@u
      │    │    ├── columns: k:6!null u:7!null v:8
      │    │    ├── constraint: /7/6: [/1 - /1]
      │    │    ├── flags: no-index-join
      │    │    ├── key: (6)
      │    │    ├── fd: ()-->(7), (6)-->(8), (8)~~>(6)
      │    │    └── ordering: +6 opt(7) [actual: +6]
      │    └── scan a@v
      │         ├── columns: k:11!null u:12 v:13!null
      │         ├── constraint: /13: [/1 - /1]
      │         ├── flags: no-index-join
      │         ├── cardinality: [0 - 1]
      │         ├── key: ()
      │         └── fd: ()-->(11-13)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Columns are passed-through correctly when EliminateUnionAllLeft is applied.
opt expect=SplitDisjunction
SELECT k FROM d WHERE u = 2 OR (v = 1 AND v = 3)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null u:2!null v:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1 opt(2)
      ├── key: (1)
      ├── fd: ()-->(2), (1)-->(3)
      ├── project
      │    ├── columns: k:1!null u:2!null v:3
      │    ├── key: (1)
      │    ├── fd: ()-->(2), (1)-->(3)
      │    ├── ordering: +1 opt(2) [actual: +1]
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8), (7)-->(9)
      │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7: [/2 - /2]
      │    │         ├── key: (7)
      │    │         ├── fd: ()-->(8)
      │    │         └── ordering: +7 opt(8) [actual: +7]
      │    └── projections
      │         ├── k:7 [as=k:1, outer=(7)]
      │         ├── u:8 [as=u:2, outer=(8)]
      │         └── v:9 [as=v:3, outer=(9)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Columns are passed-through correctly when EliminateUnionAllRight is applied.
opt expect=SplitDisjunction
SELECT k FROM d WHERE (u = 2 AND u = 4) OR v = 1
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3!null
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1 opt(3)
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(2)
      ├── project
      │    ├── columns: k:1!null u:2 v:3!null
      │    ├── key: (1)
      │    ├── fd: ()-->(3), (1)-->(2)
      │    ├── ordering: +1 opt(3) [actual: +1]
      │    ├── index-join d
      │    │    ├── columns: k:13!null u:14 v:15!null
      │    │    ├── key: (13)
      │    │    ├── fd: ()-->(15), (13)-->(14)
      │    │    ├── ordering: +13 opt(15) [actual: +13]
      │    │    └── scan d@v
      │    │         ├── columns: k:13!null v:15!null
      │    │         ├── constraint: /15/13: [/1 - /1]
      │    │         ├── key: (13)
      │    │         ├── fd: ()-->(15)
      │    │         └── ordering: +13 opt(15) [actual: +13]
      │    └── projections
      │         ├── k:13 [as=k:1, outer=(13)]
      │         ├── u:14 [as=u:2, outer=(14)]
      │         └── v:15 [as=v:3, outer=(15)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Regression test for #52207. Partial index predicates in TableMeta must be
# duplicated when TableMeta is duplicated.
exec-ddl
CREATE TABLE t52207 (
  k INT PRIMARY KEY,
  a INT,
  b INT,
  INDEX idx_a (a) WHERE a > 0,
  INDEX idx_b (b) WHERE b > 0
)
----

opt expect=SplitDisjunction
SELECT k FROM t52207 WHERE a = 1 OR b = 1
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── distinct-on
      ├── columns: k:1!null a:2 b:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null a:2 b:3
      │    ├── left columns: k:6 a:7 b:8
      │    ├── right columns: k:11 a:12 b:13
      │    ├── ordering: +1
      │    ├── index-join t52207
      │    │    ├── columns: k:6!null a:7!null b:8
      │    │    ├── key: (6)
      │    │    ├── fd: ()-->(7), (6)-->(8)
      │    │    ├── ordering: +6 opt(7) [actual: +6]
      │    │    └── scan t52207@idx_a,partial
      │    │         ├── columns: k:6!null a:7!null
      │    │         ├── constraint: /7/6: [/1 - /1]
      │    │         ├── key: (6)
      │    │         ├── fd: ()-->(7)
      │    │         └── ordering: +6 opt(7) [actual: +6]
      │    └── index-join t52207
      │         ├── columns: k:11!null a:12 b:13!null
      │         ├── key: (11)
      │         ├── fd: ()-->(13), (11)-->(12)
      │         ├── ordering: +11 opt(13) [actual: +11]
      │         └── scan t52207@idx_b,partial
      │              ├── columns: k:11!null b:13!null
      │              ├── constraint: /13/11: [/1 - /1]
      │              ├── key: (11)
      │              ├── fd: ()-->(13)
      │              └── ordering: +11 opt(13) [actual: +11]
      └── aggregations
           ├── const-agg [as=a:2, outer=(2)]
           │    └── a:2
           └── const-agg [as=b:3, outer=(3)]
                └── b:3

# Regression test for #58390. SplitDisjunction must not generate a cycle in the
# memo when there is a partial index with a predicate identical to the
# disjunction in the query filter.
exec-ddl
CREATE TABLE t58390 (
  k INT PRIMARY KEY,
  a INT,
  b INT,
  c INT,
  INDEX (c) WHERE a > 1 OR b > 1
)
----

memo
SELECT * FROM t58390 WHERE a > 1 OR b > 1
----
memo (optimized, ~25KB, required=[presentation: k:1,a:2,b:3,c:4])
 ├── G1: (select G2 G3) (index-join G4 t58390,cols=(1-4)) (distinct-on G5 G6 cols=(1)) (distinct-on G5 G6 cols=(1),ordering=+1)
 │    └── [presentation: k:1,a:2,b:3,c:4]
 │         ├── best: (select G2 G3)
 │         └── cost: 1118.85
 ├── G2: (scan t58390,cols=(1-4))
 │    └── []
 │         ├── best: (scan t58390,cols=(1-4))
 │         └── cost: 1108.82
 ├── G3: (filters G7)
 ├── G4: (scan t58390@t58390_c_idx,partial,cols=(1,4))
 │    └── []
 │         ├── best: (scan t58390@t58390_c_idx,partial,cols=(1,4))
 │         └── cost: 595.80
 ├── G5: (union-all G8 G9)
 │    ├── [ordering: +1]
 │    │    ├── best: (union-all G8="[ordering: +7]" G9="[ordering: +13]")
 │    │    └── cost: 2244.39
 │    └── []
 │         ├── best: (union-all G8 G9)
 │         └── cost: 2244.39
 ├── G6: (aggregations G10 G11 G12)
 ├── G7: (or G13 G14)
 ├── G8: (select G15 G16) (select G17 G16)
 │    ├── [ordering: +7]
 │    │    ├── best: (select G15="[ordering: +7]" G16)
 │    │    └── cost: 1118.85
 │    └── []
 │         ├── best: (select G15 G16)
 │         └── cost: 1118.85
 ├── G9: (select G18 G19) (select G20 G19)
 │    ├── [ordering: +13]
 │    │    ├── best: (select G18="[ordering: +13]" G19)
 │    │    └── cost: 1118.85
 │    └── []
 │         ├── best: (select G18 G19)
 │         └── cost: 1118.85
 ├── G10: (const-agg G21)
 ├── G11: (const-agg G22)
 ├── G12: (const-agg G23)
 ├── G13: (gt G21 G24)
 ├── G14: (gt G22 G24)
 ├── G15: (scan t58390,cols=(7-10))
 │    ├── [ordering: +7]
 │    │    ├── best: (scan t58390,cols=(7-10))
 │    │    └── cost: 1108.82
 │    └── []
 │         ├── best: (scan t58390,cols=(7-10))
 │         └── cost: 1108.82
 ├── G16: (filters G25)
 ├── G17: (index-join G26 t58390,cols=(7-10))
 │    ├── [ordering: +7]
 │    │    ├── best: (index-join G26="[ordering: +7]" t58390,cols=(7-10))
 │    │    └── cost: 4091.63
 │    └── []
 │         ├── best: (index-join G26 t58390,cols=(7-10))
 │         └── cost: 3968.04
 ├── G18: (scan t58390,cols=(13-16))
 │    ├── [ordering: +13]
 │    │    ├── best: (scan t58390,cols=(13-16))
 │    │    └── cost: 1108.82
 │    └── []
 │         ├── best: (scan t58390,cols=(13-16))
 │         └── cost: 1108.82
 ├── G19: (filters G27)
 ├── G20: (index-join G28 t58390,cols=(13-16))
 │    ├── [ordering: +13]
 │    │    ├── best: (index-join G28="[ordering: +13]" t58390,cols=(13-16))
 │    │    └── cost: 4091.63
 │    └── []
 │         ├── best: (index-join G28 t58390,cols=(13-16))
 │         └── cost: 3968.04
 ├── G21: (variable a)
 ├── G22: (variable b)
 ├── G23: (variable c)
 ├── G24: (const 1)
 ├── G25: (gt G29 G24)
 ├── G26: (scan t58390@t58390_c_idx,partial,cols=(7,10))
 │    ├── [ordering: +7]
 │    │    ├── best: (sort G26)
 │    │    └── cost: 719.39
 │    └── []
 │         ├── best: (scan t58390@t58390_c_idx,partial,cols=(7,10))
 │         └── cost: 595.80
 ├── G27: (gt G30 G24)
 ├── G28: (scan t58390@t58390_c_idx,partial,cols=(13,16))
 │    ├── [ordering: +13]
 │    │    ├── best: (sort G28)
 │    │    └── cost: 719.39
 │    └── []
 │         ├── best: (scan t58390@t58390_c_idx,partial,cols=(13,16))
 │         └── cost: 595.80
 ├── G29: (variable a)
 └── G30: (variable b)

# Regression test for #61795. Equivalence dependencies are propagated to
# UnionAll expressions generated by SplitDisjunction, preventing failed
# test-build assertions in ordering.checkRequired that require that ordering
# column groups contain columns that are known to be equivalent.
exec-ddl
CREATE TABLE t61795 (
  a INT PRIMARY KEY,
  b INT,
  c INT,
  UNIQUE (b)
)
----

memo expect=SplitDisjunction
SELECT t1.a
FROM t61795 AS t1
JOIN t61795 AS t2 ON t1.c = t1.b AND t1.b = t2.b
WHERE t1.a = 10 OR t2.b != abs(t2.b)
ORDER BY t1.b ASC
----
memo (optimized, ~37KB, required=[presentation: a:1] [ordering: +2])
 ├── G1: (project G2 G3 a b)
 │    ├── [presentation: a:1] [ordering: +2]
 │    │    ├── best: (sort G1)
 │    │    └── cost: 1098.75
 │    └── []
 │         ├── best: (project G2 G3 a b)
 │         └── cost: 1098.69
 ├── G2: (select G4 G5) (select G6 G7) (distinct-on G8 G9 cols=(1)) (distinct-on G8 G9 cols=(1),ordering=+1)
 │    ├── [ordering: +(2|3)]
 │    │    ├── best: (sort G2)
 │    │    └── cost: 1098.73
 │    └── []
 │         ├── best: (select G4 G5)
 │         └── cost: 1098.66
 ├── G3: (projections)
 ├── G4: (scan t61795 [as=t1],cols=(1-3))
 │    ├── [ordering: +2]
 │    │    ├── best: (sort G4)
 │    │    └── cost: 1338.10
 │    └── []
 │         ├── best: (scan t61795 [as=t1],cols=(1-3))
 │         └── cost: 1088.62
 ├── G5: (filters G10 G11)
 ├── G6: (index-join G12 t61795,cols=(1-3))
 │    ├── [ordering: +2]
 │    │    ├── best: (index-join G12="[ordering: +2]" t61795,cols=(1-3))
 │    │    └── cost: 3058.10
 │    └── []
 │         ├── best: (index-join G12 t61795,cols=(1-3))
 │         └── cost: 3058.10
 ├── G7: (filters G10)
 ├── G8: (union-all G13 G14)
 │    ├── [ordering: +(2|3)]
 │    │    ├── best: (union-all G13 G14="[ordering: +(17|18)]")
 │    │    └── cost: 1107.83
 │    ├── [ordering: +1]
 │    │    ├── best: (union-all G13 G14="[ordering: +16]")
 │    │    └── cost: 1107.79
 │    └── []
 │         ├── best: (union-all G13 G14)
 │         └── cost: 1107.79
 ├── G9: (aggregations G15 G16)
 ├── G10: (eq G17 G18)
 ├── G11: (or G19 G20)
 ├── G12: (select G21 G22)
 │    ├── [ordering: +2]
 │    │    ├── best: (select G21="[ordering: +2]" G22)
 │    │    └── cost: 1057.55
 │    └── []
 │         ├── best: (select G21 G22)
 │         └── cost: 1057.55
 ├── G13: (select G23 G24) (select G25 G26) (select G27 G26)
 │    └── []
 │         ├── best: (select G25 G26)
 │         └── cost: 9.10
 ├── G14: (select G28 G29) (select G30 G31)
 │    ├── [ordering: +(17|18)]
 │    │    ├── best: (sort G14)
 │    │    └── cost: 1098.70
 │    ├── [ordering: +16]
 │    │    ├── best: (select G28="[ordering: +16]" G29)
 │    │    └── cost: 1098.66
 │    └── []
 │         ├── best: (select G28 G29)
 │         └── cost: 1098.66
 ├── G15: (const-agg G18)
 ├── G16: (const-agg G17)
 ├── G17: (variable t1.c)
 ├── G18: (variable t1.b)
 ├── G19: (eq G32 G33)
 ├── G20: (ne G18 G34)
 ├── G21: (scan t61795@t61795_b_key [as=t1],cols=(1,2),constrained)
 │    ├── [ordering: +2]
 │    │    ├── best: (scan t61795@t61795_b_key [as=t1],cols=(1,2),constrained)
 │    │    └── cost: 1047.62
 │    └── []
 │         ├── best: (scan t61795@t61795_b_key [as=t1],cols=(1,2),constrained)
 │         └── cost: 1047.62
 ├── G22: (filters G11)
 ├── G23: (scan t61795 [as=t1],cols=(11-13))
 │    └── []
 │         ├── best: (scan t61795 [as=t1],cols=(11-13))
 │         └── cost: 1088.62
 ├── G24: (filters G35 G36)
 ├── G25: (scan t61795 [as=t1],cols=(11-13),constrained)
 │    └── []
 │         ├── best: (scan t61795 [as=t1],cols=(11-13),constrained)
 │         └── cost: 9.07
 ├── G26: (filters G35)
 ├── G27: (index-join G37 t61795,cols=(11-13))
 │    └── []
 │         ├── best: (index-join G37 t61795,cols=(11-13))
 │         └── cost: 1063.60
 ├── G28: (scan t61795 [as=t1],cols=(16-18))
 │    ├── [ordering: +16]
 │    │    ├── best: (scan t61795 [as=t1],cols=(16-18))
 │    │    └── cost: 1088.62
 │    ├── [ordering: +17]
 │    │    ├── best: (sort G28)
 │    │    └── cost: 1338.10
 │    └── []
 │         ├── best: (scan t61795 [as=t1],cols=(16-18))
 │         └── cost: 1088.62
 ├── G29: (filters G38 G39)
 ├── G30: (index-join G40 t61795,cols=(16-18))
 │    ├── [ordering: +16]
 │    │    ├── best: (index-join G40="[ordering: +16]" t61795,cols=(16-18))
 │    │    └── cost: 3122.52
 │    ├── [ordering: +17]
 │    │    ├── best: (index-join G40="[ordering: +17]" t61795,cols=(16-18))
 │    │    └── cost: 3054.07
 │    └── []
 │         ├── best: (index-join G40 t61795,cols=(16-18))
 │         └── cost: 3054.07
 ├── G31: (filters G38)
 ├── G32: (variable t1.a)
 ├── G33: (const 10)
 ├── G34: (function G41 abs)
 ├── G35: (eq G42 G43)
 ├── G36: (eq G44 G33)
 ├── G37: (select G45 G46)
 │    └── []
 │         ├── best: (select G45 G46)
 │         └── cost: 1057.54
 ├── G38: (eq G47 G48)
 ├── G39: (ne G48 G49)
 ├── G40: (select G50 G51)
 │    ├── [ordering: +16]
 │    │    ├── best: (sort G40)
 │    │    └── cost: 1126.00
 │    ├── [ordering: +17]
 │    │    ├── best: (select G50="[ordering: +17]" G51)
 │    │    └── cost: 1057.55
 │    └── []
 │         ├── best: (select G50 G51)
 │         └── cost: 1057.55
 ├── G41: (scalar-list G18)
 ├── G42: (variable t1.c)
 ├── G43: (variable t1.b)
 ├── G44: (variable t1.a)
 ├── G45: (scan t61795@t61795_b_key [as=t1],cols=(11,12),constrained)
 │    └── []
 │         ├── best: (scan t61795@t61795_b_key [as=t1],cols=(11,12),constrained)
 │         └── cost: 1047.62
 ├── G46: (filters G36)
 ├── G47: (variable t1.c)
 ├── G48: (variable t1.b)
 ├── G49: (function G52 abs)
 ├── G50: (scan t61795@t61795_b_key [as=t1],cols=(16,17),constrained)
 │    ├── [ordering: +16]
 │    │    ├── best: (sort G50)
 │    │    └── cost: 1284.42
 │    ├── [ordering: +17]
 │    │    ├── best: (scan t61795@t61795_b_key [as=t1],cols=(16,17),constrained)
 │    │    └── cost: 1047.62
 │    └── []
 │         ├── best: (scan t61795@t61795_b_key [as=t1],cols=(16,17),constrained)
 │         └── cost: 1047.62
 ├── G51: (filters G39)
 └── G52: (scalar-list G48)

# --------------------------------------------------
# SplitDisjunctionAddKey
# --------------------------------------------------

opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE u = 1 OR v = 1
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── ordering: +1
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8), (7)-->(9)
      │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7: [/1 - /1]
      │    │         ├── key: (7)
      │    │         ├── fd: ()-->(8)
      │    │         └── ordering: +7 opt(8) [actual: +7]
      │    └── index-join d
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: ()-->(15), (13)-->(14)
      │         ├── ordering: +13 opt(15) [actual: +13]
      │         └── scan d@v
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/13: [/1 - /1]
      │              ├── key: (13)
      │              ├── fd: ()-->(15)
      │              └── ordering: +13 opt(15) [actual: +13]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

opt expect=SplitDisjunctionAddKey
SELECT u, v, w FROM d WHERE w = 1 AND (u = 1 OR v = 1)
----
project
 ├── columns: u:2 v:3 w:4!null
 ├── fd: ()-->(4)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3 w:4!null
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2-4)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3 w:4!null
      │    ├── left columns: k:7 u:8 v:9 w:10
      │    ├── right columns: k:13 u:14 v:15 w:16
      │    ├── ordering: +1
      │    ├── select
      │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8,10), (7)-->(9)
      │    │    ├── ordering: +7 opt(8,10) [actual: +7]
      │    │    ├── index-join d
      │    │    │    ├── columns: k:7!null u:8 v:9 w:10
      │    │    │    ├── key: (7)
      │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
      │    │    │    ├── ordering: +7 opt(8) [actual: +7]
      │    │    │    └── scan d@u
      │    │    │         ├── columns: k:7!null u:8!null
      │    │    │         ├── constraint: /8/7: [/1 - /1]
      │    │    │         ├── key: (7)
      │    │    │         ├── fd: ()-->(8)
      │    │    │         └── ordering: +7 opt(8) [actual: +7]
      │    │    └── filters
      │    │         └── w:10 = 1 [outer=(10), constraints=(/10: [/1 - /1]; tight), fd=()-->(10)]
      │    └── select
      │         ├── columns: k:13!null u:14 v:15!null w:16!null
      │         ├── key: (13)
      │         ├── fd: ()-->(15,16), (13)-->(14)
      │         ├── ordering: +13 opt(15,16) [actual: +13]
      │         ├── index-join d
      │         │    ├── columns: k:13!null u:14 v:15 w:16
      │         │    ├── key: (13)
      │         │    ├── fd: ()-->(15), (13)-->(14,16)
      │         │    ├── ordering: +13 opt(15) [actual: +13]
      │         │    └── scan d@v
      │         │         ├── columns: k:13!null v:15!null
      │         │         ├── constraint: /15/13: [/1 - /1]
      │         │         ├── key: (13)
      │         │         ├── fd: ()-->(15)
      │         │         └── ordering: +13 opt(15) [actual: +13]
      │         └── filters
      │              └── w:16 = 1 [outer=(16), constraints=(/16: [/1 - /1]; tight), fd=()-->(16)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           ├── const-agg [as=v:3, outer=(3)]
           │    └── v:3
           └── const-agg [as=w:4, outer=(4)]
                └── w:4

opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (u = 1 OR v = 2) AND (u = 10 OR v = 20)
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: k:1!null u:2!null v:3!null
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2!null v:3!null
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── inner-join (zigzag d@u d@v)
      │    │    ├── columns: k:7!null u:8!null v:9!null
      │    │    ├── eq columns: [7] = [7]
      │    │    ├── left fixed columns: [8] = [1]
      │    │    ├── right fixed columns: [9] = [20]
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8,9)
      │    │    └── filters
      │    │         ├── u:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)]
      │    │         └── v:9 = 20 [outer=(9), constraints=(/9: [/20 - /20]; tight), fd=()-->(9)]
      │    └── inner-join (zigzag d@u d@v)
      │         ├── columns: k:13!null u:14!null v:15!null
      │         ├── eq columns: [13] = [13]
      │         ├── left fixed columns: [14] = [10]
      │         ├── right fixed columns: [15] = [2]
      │         ├── key: (13)
      │         ├── fd: ()-->(14,15)
      │         └── filters
      │              ├── v:15 = 2 [outer=(15), constraints=(/15: [/2 - /2]; tight), fd=()-->(15)]
      │              └── u:14 = 10 [outer=(14), constraints=(/14: [/10 - /10]; tight), fd=()-->(14)]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

opt expect=SplitDisjunctionAddKey
SELECT count(*) FROM d WHERE u = 1 OR v = 1
----
scalar-group-by
 ├── columns: count:7!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(7)
 ├── distinct-on
 │    ├── columns: k:1!null u:2 v:3
 │    ├── grouping columns: k:1!null
 │    ├── internal-ordering: +1
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── union-all
 │    │    ├── columns: k:1!null u:2 v:3
 │    │    ├── left columns: k:8 u:9 v:10
 │    │    ├── right columns: k:14 u:15 v:16
 │    │    ├── ordering: +1
 │    │    ├── index-join d
 │    │    │    ├── columns: k:8!null u:9!null v:10
 │    │    │    ├── key: (8)
 │    │    │    ├── fd: ()-->(9), (8)-->(10)
 │    │    │    ├── ordering: +8 opt(9) [actual: +8]
 │    │    │    └── scan d@u
 │    │    │         ├── columns: k:8!null u:9!null
 │    │    │         ├── constraint: /9/8: [/1 - /1]
 │    │    │         ├── key: (8)
 │    │    │         ├── fd: ()-->(9)
 │    │    │         └── ordering: +8 opt(9) [actual: +8]
 │    │    └── index-join d
 │    │         ├── columns: k:14!null u:15 v:16!null
 │    │         ├── key: (14)
 │    │         ├── fd: ()-->(16), (14)-->(15)
 │    │         ├── ordering: +14 opt(16) [actual: +14]
 │    │         └── scan d@v
 │    │              ├── columns: k:14!null v:16!null
 │    │              ├── constraint: /16/14: [/1 - /1]
 │    │              ├── key: (14)
 │    │              ├── fd: ()-->(16)
 │    │              └── ordering: +14 opt(16) [actual: +14]
 │    └── aggregations
 │         ├── const-agg [as=u:2, outer=(2)]
 │         │    └── u:2
 │         └── const-agg [as=v:3, outer=(3)]
 │              └── v:3
 └── aggregations
      └── count-rows [as=count_rows:7]

# Multi-column primary key.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM f WHERE u = 1 OR v = 2
----
project
 ├── columns: u:3 v:4
 └── distinct-on
      ├── columns: k:1!null j:2!null u:3 v:4
      ├── grouping columns: k:1!null j:2!null
      ├── internal-ordering: +1,+2
      ├── key: (1,2)
      ├── fd: (1,2)-->(3,4)
      ├── union-all
      │    ├── columns: k:1!null j:2!null u:3 v:4
      │    ├── left columns: k:7 j:8 u:9 v:10
      │    ├── right columns: k:13 j:14 u:15 v:16
      │    ├── ordering: +1,+2
      │    ├── index-join f
      │    │    ├── columns: k:7!null j:8!null u:9!null v:10
      │    │    ├── key: (7,8)
      │    │    ├── fd: ()-->(9), (7,8)-->(10)
      │    │    ├── ordering: +7,+8 opt(9) [actual: +7,+8]
      │    │    └── scan f@u
      │    │         ├── columns: k:7!null j:8!null u:9!null
      │    │         ├── constraint: /9/7/8: [/1 - /1]
      │    │         ├── key: (7,8)
      │    │         ├── fd: ()-->(9)
      │    │         └── ordering: +7,+8 opt(9) [actual: +7,+8]
      │    └── index-join f
      │         ├── columns: k:13!null j:14!null u:15 v:16!null
      │         ├── key: (13,14)
      │         ├── fd: ()-->(16), (13,14)-->(15)
      │         ├── ordering: +13,+14 opt(16) [actual: +13,+14]
      │         └── scan f@v
      │              ├── columns: k:13!null j:14!null v:16!null
      │              ├── constraint: /16/13/14: [/2 - /2]
      │              ├── key: (13,14)
      │              ├── fd: ()-->(16)
      │              └── ordering: +13,+14 opt(16) [actual: +13,+14]
      └── aggregations
           ├── const-agg [as=u:3, outer=(3)]
           │    └── u:3
           └── const-agg [as=v:4, outer=(4)]
                └── v:4

# Don't expand INs to many ORs.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE u IN (1, 2, 3, 4) OR v IN (5, 6, 7, 8)
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: (7)-->(8,9)
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7: [/1 - /4]
      │    │         ├── key: (7)
      │    │         └── fd: (7)-->(8)
      │    └── index-join d
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: (13)-->(14,15)
      │         └── scan d@v
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/13: [/5 - /8]
      │              ├── key: (13)
      │              └── fd: (13)-->(15)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Split and constrain with an inverted index on JSONB on one side.
opt expect=SplitDisjunctionAddKey
SELECT u, j FROM b WHERE u = 1 OR j @> '{"foo": "bar"}'
----
project
 ├── columns: u:2 j:4
 ├── immutable
 └── distinct-on
      ├── columns: k:1!null u:2 j:4
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2,4)
      ├── union-all
      │    ├── columns: k:1!null u:2 j:4
      │    ├── left columns: k:10 u:11 j:13
      │    ├── right columns: k:19 u:20 j:22
      │    ├── immutable
      │    ├── ordering: +1
      │    ├── index-join b
      │    │    ├── columns: k:10!null u:11!null j:13
      │    │    ├── key: (10)
      │    │    ├── fd: ()-->(11), (10)-->(13)
      │    │    ├── ordering: +10 opt(11) [actual: +10]
      │    │    └── scan b@u
      │    │         ├── columns: k:10!null u:11!null
      │    │         ├── constraint: /11/10: [/1 - /1]
      │    │         ├── key: (10)
      │    │         ├── fd: ()-->(11)
      │    │         └── ordering: +10 opt(11) [actual: +10]
      │    └── index-join b
      │         ├── columns: k:19!null u:20 j:22!null
      │         ├── immutable
      │         ├── key: (19)
      │         ├── fd: (19)-->(20,22)
      │         ├── ordering: +19
      │         └── sort
      │              ├── columns: k:19!null
      │              ├── key: (19)
      │              ├── ordering: +19
      │              └── scan b@j_inv_idx,inverted
      │                   ├── columns: k:19!null
      │                   ├── inverted constraint: /27/19
      │                   │    └── spans: ["foo"/"bar", "foo"/"bar"]
      │                   └── key: (19)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=j:4, outer=(4)]
                └── j:4

# Split and constrain with an inverted index on an array on one side.
opt expect=SplitDisjunctionAddKey
SELECT u, a FROM c WHERE u = 1 OR a @> ARRAY[2]
----
project
 ├── columns: u:3 a:2
 ├── immutable
 └── distinct-on
      ├── columns: k:1!null a:2 u:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null a:2 u:3
      │    ├── left columns: k:9 a:10 u:11
      │    ├── right columns: k:17 a:18 u:19
      │    ├── immutable
      │    ├── ordering: +1
      │    ├── index-join c
      │    │    ├── columns: k:9!null a:10 u:11!null
      │    │    ├── key: (9)
      │    │    ├── fd: ()-->(11), (9)-->(10)
      │    │    ├── ordering: +9 opt(11) [actual: +9]
      │    │    └── scan c@u
      │    │         ├── columns: k:9!null u:11!null
      │    │         ├── constraint: /11/9: [/1 - /1]
      │    │         ├── key: (9)
      │    │         ├── fd: ()-->(11)
      │    │         └── ordering: +9 opt(11) [actual: +9]
      │    └── index-join c
      │         ├── columns: k:17!null a:18!null u:19
      │         ├── immutable
      │         ├── key: (17)
      │         ├── fd: (17)-->(18,19)
      │         ├── ordering: +17
      │         └── sort
      │              ├── columns: k:17!null
      │              ├── key: (17)
      │              ├── ordering: +17
      │              └── scan c@a_inv_idx,inverted
      │                   ├── columns: k:17!null
      │                   ├── inverted constraint: /24/17
      │                   │    └── spans: [2, 2]
      │                   └── key: (17)
      └── aggregations
           ├── const-agg [as=a:2, outer=(2)]
           │    └── a:2
           └── const-agg [as=u:3, outer=(3)]
                └── u:3

# Uncorrelated subquery.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (u = 1 OR v = 1) AND EXISTS (SELECT u, v FROM a)
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: d.k:1!null d.u:2 d.v:3
      ├── grouping columns: d.k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: d.k:1!null d.u:2 d.v:3
      │    ├── left columns: d.k:14 d.u:15 d.v:16
      │    ├── right columns: d.k:20 d.u:21 d.v:22
      │    ├── ordering: +1
      │    ├── index-join d
      │    │    ├── columns: d.k:14!null d.u:15!null d.v:16
      │    │    ├── key: (14)
      │    │    ├── fd: ()-->(15), (14)-->(16)
      │    │    ├── ordering: +14 opt(15) [actual: +14]
      │    │    └── select
      │    │         ├── columns: d.k:14!null d.u:15!null
      │    │         ├── key: (14)
      │    │         ├── fd: ()-->(15)
      │    │         ├── ordering: +14 opt(15) [actual: +14]
      │    │         ├── scan d@u
      │    │         │    ├── columns: d.k:14!null d.u:15!null
      │    │         │    ├── constraint: /15/14: [/1 - /1]
      │    │         │    ├── key: (14)
      │    │         │    ├── fd: ()-->(15)
      │    │         │    └── ordering: +14 opt(15) [actual: +14]
      │    │         └── filters
      │    │              └── coalesce [subquery]
      │    │                   ├── subquery
      │    │                   │    └── project
      │    │                   │         ├── columns: column13:13!null
      │    │                   │         ├── cardinality: [0 - 1]
      │    │                   │         ├── key: ()
      │    │                   │         ├── fd: ()-->(13)
      │    │                   │         ├── scan a
      │    │                   │         │    ├── limit: 1
      │    │                   │         │    └── key: ()
      │    │                   │         └── projections
      │    │                   │              └── true [as=column13:13]
      │    │                   └── false
      │    └── index-join d
      │         ├── columns: d.k:20!null d.u:21 d.v:22!null
      │         ├── key: (20)
      │         ├── fd: ()-->(22), (20)-->(21)
      │         ├── ordering: +20 opt(22) [actual: +20]
      │         └── select
      │              ├── columns: d.k:20!null d.v:22!null
      │              ├── key: (20)
      │              ├── fd: ()-->(22)
      │              ├── ordering: +20 opt(22) [actual: +20]
      │              ├── scan d@v
      │              │    ├── columns: d.k:20!null d.v:22!null
      │              │    ├── constraint: /22/20: [/1 - /1]
      │              │    ├── key: (20)
      │              │    ├── fd: ()-->(22)
      │              │    └── ordering: +20 opt(22) [actual: +20]
      │              └── filters
      │                   └── coalesce [subquery]
      │                        ├── subquery
      │                        │    └── project
      │                        │         ├── columns: column13:13!null
      │                        │         ├── cardinality: [0 - 1]
      │                        │         ├── key: ()
      │                        │         ├── fd: ()-->(13)
      │                        │         ├── scan a
      │                        │         │    ├── limit: 1
      │                        │         │    └── key: ()
      │                        │         └── projections
      │                        │              └── true [as=column13:13]
      │                        └── false
      └── aggregations
           ├── const-agg [as=d.u:2, outer=(2)]
           │    └── d.u:2
           └── const-agg [as=d.v:3, outer=(3)]
                └── d.v:3

# Correlated subquery.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (u = 1 OR v = 1) AND EXISTS (SELECT * FROM a WHERE a.u = d.u)
----
semi-join (lookup a@u)
 ├── columns: u:2 v:3
 ├── key columns: [2] = [8]
 ├── project
 │    ├── columns: d.u:2 d.v:3
 │    └── distinct-on
 │         ├── columns: d.k:1!null d.u:2 d.v:3
 │         ├── grouping columns: d.k:1!null
 │         ├── internal-ordering: +1
 │         ├── key: (1)
 │         ├── fd: (1)-->(2,3)
 │         ├── union-all
 │         │    ├── columns: d.k:1!null d.u:2 d.v:3
 │         │    ├── left columns: d.k:13 d.u:14 d.v:15
 │         │    ├── right columns: d.k:19 d.u:20 d.v:21
 │         │    ├── ordering: +1
 │         │    ├── index-join d
 │         │    │    ├── columns: d.k:13!null d.u:14!null d.v:15
 │         │    │    ├── key: (13)
 │         │    │    ├── fd: ()-->(14), (13)-->(15)
 │         │    │    ├── ordering: +13 opt(14) [actual: +13]
 │         │    │    └── scan d@u
 │         │    │         ├── columns: d.k:13!null d.u:14!null
 │         │    │         ├── constraint: /14/13: [/1 - /1]
 │         │    │         ├── key: (13)
 │         │    │         ├── fd: ()-->(14)
 │         │    │         └── ordering: +13 opt(14) [actual: +13]
 │         │    └── index-join d
 │         │         ├── columns: d.k:19!null d.u:20 d.v:21!null
 │         │         ├── key: (19)
 │         │         ├── fd: ()-->(21), (19)-->(20)
 │         │         ├── ordering: +19 opt(21) [actual: +19]
 │         │         └── scan d@v
 │         │              ├── columns: d.k:19!null d.v:21!null
 │         │              ├── constraint: /21/19: [/1 - /1]
 │         │              ├── key: (19)
 │         │              ├── fd: ()-->(21)
 │         │              └── ordering: +19 opt(21) [actual: +19]
 │         └── aggregations
 │              ├── const-agg [as=d.u:2, outer=(2)]
 │              │    └── d.u:2
 │              └── const-agg [as=d.v:3, outer=(3)]
 │                   └── d.v:3
 └── filters (true)

# Correlated subquery with references to outer columns not in the scan columns.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (u = 1 OR v = 1) AND EXISTS (SELECT * FROM a WHERE a.u = d.w)
----
project
 ├── columns: u:2 v:3
 └── semi-join (lookup a@u)
      ├── columns: d.u:2 d.v:3 w:4
      ├── key columns: [4] = [8]
      ├── project
      │    ├── columns: d.u:2 d.v:3 w:4
      │    └── distinct-on
      │         ├── columns: d.k:1!null d.u:2 d.v:3 w:4
      │         ├── grouping columns: d.k:1!null
      │         ├── internal-ordering: +1
      │         ├── key: (1)
      │         ├── fd: (1)-->(2-4)
      │         ├── union-all
      │         │    ├── columns: d.k:1!null d.u:2 d.v:3 w:4
      │         │    ├── left columns: d.k:13 d.u:14 d.v:15 w:16
      │         │    ├── right columns: d.k:19 d.u:20 d.v:21 w:22
      │         │    ├── ordering: +1
      │         │    ├── index-join d
      │         │    │    ├── columns: d.k:13!null d.u:14!null d.v:15 w:16
      │         │    │    ├── key: (13)
      │         │    │    ├── fd: ()-->(14), (13)-->(15,16)
      │         │    │    ├── ordering: +13 opt(14) [actual: +13]
      │         │    │    └── scan d@u
      │         │    │         ├── columns: d.k:13!null d.u:14!null
      │         │    │         ├── constraint: /14/13: [/1 - /1]
      │         │    │         ├── key: (13)
      │         │    │         ├── fd: ()-->(14)
      │         │    │         └── ordering: +13 opt(14) [actual: +13]
      │         │    └── index-join d
      │         │         ├── columns: d.k:19!null d.u:20 d.v:21!null w:22
      │         │         ├── key: (19)
      │         │         ├── fd: ()-->(21), (19)-->(20,22)
      │         │         ├── ordering: +19 opt(21) [actual: +19]
      │         │         └── scan d@v
      │         │              ├── columns: d.k:19!null d.v:21!null
      │         │              ├── constraint: /21/19: [/1 - /1]
      │         │              ├── key: (19)
      │         │              ├── fd: ()-->(21)
      │         │              └── ordering: +19 opt(21) [actual: +19]
      │         └── aggregations
      │              ├── const-agg [as=d.u:2, outer=(2)]
      │              │    └── d.u:2
      │              ├── const-agg [as=d.v:3, outer=(3)]
      │              │    └── d.v:3
      │              └── const-agg [as=w:4, outer=(4)]
      │                   └── w:4
      └── filters (true)

# Use rowid when there is no explicit primary key.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM no_explicit_primary_key WHERE u = 1 OR v = 5
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: u:2 v:3 rowid:4!null
      ├── grouping columns: rowid:4!null
      ├── internal-ordering: +4
      ├── key: (4)
      ├── fd: (4)-->(2,3)
      ├── union-all
      │    ├── columns: u:2 v:3 rowid:4!null
      │    ├── left columns: u:8 v:9 rowid:10
      │    ├── right columns: u:14 v:15 rowid:16
      │    ├── ordering: +4
      │    ├── index-join no_explicit_primary_key
      │    │    ├── columns: u:8!null v:9 rowid:10!null
      │    │    ├── key: (10)
      │    │    ├── fd: ()-->(8), (10)-->(9)
      │    │    ├── ordering: +10 opt(8) [actual: +10]
      │    │    └── scan no_explicit_primary_key@u
      │    │         ├── columns: u:8!null rowid:10!null
      │    │         ├── constraint: /8/10: [/1 - /1]
      │    │         ├── key: (10)
      │    │         ├── fd: ()-->(8)
      │    │         └── ordering: +10 opt(8) [actual: +10]
      │    └── index-join no_explicit_primary_key
      │         ├── columns: u:14 v:15!null rowid:16!null
      │         ├── key: (16)
      │         ├── fd: ()-->(15), (16)-->(14)
      │         ├── ordering: +16 opt(15) [actual: +16]
      │         └── scan no_explicit_primary_key@v
      │              ├── columns: v:15!null rowid:16!null
      │              ├── constraint: /15/16: [/5 - /5]
      │              ├── key: (16)
      │              ├── fd: ()-->(15)
      │              └── ordering: +16 opt(15) [actual: +16]
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Apply when outer columns of both sides of OR are a subset of index columns.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM e WHERE u = 1 OR v = 1
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── index-join e
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: ()-->(8), (7)-->(9)
      │    │    └── scan e@uw
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/10/7: [/1 - /1]
      │    │         ├── key: (7)
      │    │         └── fd: ()-->(8)
      │    └── index-join e
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: ()-->(15), (13)-->(14)
      │         └── scan e@vw
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/16/13: [/1 - /1]
      │              ├── key: (13)
      │              └── fd: ()-->(15)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Apply when outer columns of both sides of OR are a superset of index columns.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (u = 1 AND w = 2) OR (v = 1 AND w = 3)
----
project
 ├── columns: u:2 v:3
 └── project
      ├── columns: u:2 v:3 w:4!null
      └── distinct-on
           ├── columns: k:1!null u:2 v:3 w:4!null
           ├── grouping columns: k:1!null
           ├── internal-ordering: +1
           ├── key: (1)
           ├── fd: (1)-->(2-4)
           ├── union-all
           │    ├── columns: k:1!null u:2 v:3 w:4!null
           │    ├── left columns: k:7 u:8 v:9 w:10
           │    ├── right columns: k:13 u:14 v:15 w:16
           │    ├── ordering: +1
           │    ├── select
           │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
           │    │    ├── key: (7)
           │    │    ├── fd: ()-->(8,10), (7)-->(9)
           │    │    ├── ordering: +7 opt(8,10) [actual: +7]
           │    │    ├── index-join d
           │    │    │    ├── columns: k:7!null u:8 v:9 w:10
           │    │    │    ├── key: (7)
           │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
           │    │    │    ├── ordering: +7 opt(8) [actual: +7]
           │    │    │    └── scan d@u
           │    │    │         ├── columns: k:7!null u:8!null
           │    │    │         ├── constraint: /8/7: [/1 - /1]
           │    │    │         ├── key: (7)
           │    │    │         ├── fd: ()-->(8)
           │    │    │         └── ordering: +7 opt(8) [actual: +7]
           │    │    └── filters
           │    │         └── w:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)]
           │    └── select
           │         ├── columns: k:13!null u:14 v:15!null w:16!null
           │         ├── key: (13)
           │         ├── fd: ()-->(15,16), (13)-->(14)
           │         ├── ordering: +13 opt(15,16) [actual: +13]
           │         ├── index-join d
           │         │    ├── columns: k:13!null u:14 v:15 w:16
           │         │    ├── key: (13)
           │         │    ├── fd: ()-->(15), (13)-->(14,16)
           │         │    ├── ordering: +13 opt(15) [actual: +13]
           │         │    └── scan d@v
           │         │         ├── columns: k:13!null v:15!null
           │         │         ├── constraint: /15/13: [/1 - /1]
           │         │         ├── key: (13)
           │         │         ├── fd: ()-->(15)
           │         │         └── ordering: +13 opt(15) [actual: +13]
           │         └── filters
           │              └── w:16 = 3 [outer=(16), constraints=(/16: [/3 - /3]; tight), fd=()-->(16)]
           └── aggregations
                ├── const-agg [as=u:2, outer=(2)]
                │    └── u:2
                ├── const-agg [as=v:3, outer=(3)]
                │    └── v:3
                └── const-agg [as=w:4, outer=(4)]
                     └── w:4

# Group sub-expr with the same columns together.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (u = 1 OR v = 2) OR (u = 3 OR v = 4)
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: (7)-->(8,9)
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7
      │    │         │    ├── [/1 - /1]
      │    │         │    └── [/3 - /3]
      │    │         ├── key: (7)
      │    │         └── fd: (7)-->(8)
      │    └── index-join d
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: (13)-->(14,15)
      │         └── scan d@v
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/13
      │              │    ├── [/2 - /2]
      │              │    └── [/4 - /4]
      │              ├── key: (13)
      │              └── fd: (13)-->(15)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Group sub-expr with the same columns together. Output should have a single union expr.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE u = 1 OR u = 3 OR v = 2 OR v = 4 OR u = 5 OR v = 6
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: (7)-->(8,9)
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7
      │    │         │    ├── [/1 - /1]
      │    │         │    ├── [/3 - /3]
      │    │         │    └── [/5 - /5]
      │    │         ├── key: (7)
      │    │         └── fd: (7)-->(8)
      │    └── index-join d
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: (13)-->(14,15)
      │         └── scan d@v
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/13
      │              │    ├── [/2 - /2]
      │              │    ├── [/4 - /4]
      │              │    └── [/6 - /6]
      │              ├── key: (13)
      │              └── fd: (13)-->(15)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Group sub-expr with the same columns together. Output should have a single union expr.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (u = 3 OR v = 2) OR (u = 5 OR v = 4) OR v = 6
----
project
 ├── columns: u:2 v:3
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:7 u:8 v:9
      │    ├── right columns: k:13 u:14 v:15
      │    ├── index-join d
      │    │    ├── columns: k:7!null u:8!null v:9
      │    │    ├── key: (7)
      │    │    ├── fd: (7)-->(8,9)
      │    │    └── scan d@u
      │    │         ├── columns: k:7!null u:8!null
      │    │         ├── constraint: /8/7
      │    │         │    ├── [/3 - /3]
      │    │         │    └── [/5 - /5]
      │    │         ├── key: (7)
      │    │         └── fd: (7)-->(8)
      │    └── index-join d
      │         ├── columns: k:13!null u:14 v:15!null
      │         ├── key: (13)
      │         ├── fd: (13)-->(14,15)
      │         └── scan d@v
      │              ├── columns: k:13!null v:15!null
      │              ├── constraint: /15/13
      │              │    ├── [/2 - /2]
      │              │    ├── [/4 - /4]
      │              │    └── [/6 - /6]
      │              ├── key: (13)
      │              └── fd: (13)-->(15)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Find the first disjunction in the filters that have different column sets on
# the left and right.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (w = 1 OR w = 2) AND (u = 3 OR v = 4)
----
project
 ├── columns: u:2 v:3
 └── project
      ├── columns: u:2 v:3 w:4!null
      └── distinct-on
           ├── columns: k:1!null u:2 v:3 w:4!null
           ├── grouping columns: k:1!null
           ├── internal-ordering: +1
           ├── key: (1)
           ├── fd: (1)-->(2-4)
           ├── union-all
           │    ├── columns: k:1!null u:2 v:3 w:4!null
           │    ├── left columns: k:7 u:8 v:9 w:10
           │    ├── right columns: k:13 u:14 v:15 w:16
           │    ├── ordering: +1
           │    ├── select
           │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
           │    │    ├── key: (7)
           │    │    ├── fd: ()-->(8), (7)-->(9,10)
           │    │    ├── ordering: +7 opt(8) [actual: +7]
           │    │    ├── index-join d
           │    │    │    ├── columns: k:7!null u:8 v:9 w:10
           │    │    │    ├── key: (7)
           │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
           │    │    │    ├── ordering: +7 opt(8) [actual: +7]
           │    │    │    └── scan d@u
           │    │    │         ├── columns: k:7!null u:8!null
           │    │    │         ├── constraint: /8/7: [/3 - /3]
           │    │    │         ├── key: (7)
           │    │    │         ├── fd: ()-->(8)
           │    │    │         └── ordering: +7 opt(8) [actual: +7]
           │    │    └── filters
           │    │         └── (w:10 = 1) OR (w:10 = 2) [outer=(10), constraints=(/10: [/1 - /1] [/2 - /2]; tight)]
           │    └── select
           │         ├── columns: k:13!null u:14 v:15!null w:16!null
           │         ├── key: (13)
           │         ├── fd: ()-->(15), (13)-->(14,16)
           │         ├── ordering: +13 opt(15) [actual: +13]
           │         ├── index-join d
           │         │    ├── columns: k:13!null u:14 v:15 w:16
           │         │    ├── key: (13)
           │         │    ├── fd: ()-->(15), (13)-->(14,16)
           │         │    ├── ordering: +13 opt(15) [actual: +13]
           │         │    └── scan d@v
           │         │         ├── columns: k:13!null v:15!null
           │         │         ├── constraint: /15/13: [/4 - /4]
           │         │         ├── key: (13)
           │         │         ├── fd: ()-->(15)
           │         │         └── ordering: +13 opt(15) [actual: +13]
           │         └── filters
           │              └── (w:16 = 1) OR (w:16 = 2) [outer=(16), constraints=(/16: [/1 - /1] [/2 - /2]; tight)]
           └── aggregations
                ├── const-agg [as=u:2, outer=(2)]
                │    └── u:2
                ├── const-agg [as=v:3, outer=(3)]
                │    └── v:3
                └── const-agg [as=w:4, outer=(4)]
                     └── w:4

# Find the first disjunction in the filters that have columns on the left and
# right that constrain a scan.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE (u = 1 OR w = 2) AND (u = 3 OR v = 4)
----
project
 ├── columns: u:2 v:3
 └── project
      ├── columns: u:2 v:3 w:4
      └── distinct-on
           ├── columns: k:1!null u:2 v:3 w:4
           ├── grouping columns: k:1!null
           ├── internal-ordering: +1
           ├── key: (1)
           ├── fd: (1)-->(2-4)
           ├── union-all
           │    ├── columns: k:1!null u:2 v:3 w:4
           │    ├── left columns: k:7 u:8 v:9 w:10
           │    ├── right columns: k:13 u:14 v:15 w:16
           │    ├── ordering: +1
           │    ├── select
           │    │    ├── columns: k:7!null u:8!null v:9 w:10!null
           │    │    ├── key: (7)
           │    │    ├── fd: ()-->(8,10), (7)-->(9)
           │    │    ├── ordering: +7 opt(8,10) [actual: +7]
           │    │    ├── index-join d
           │    │    │    ├── columns: k:7!null u:8 v:9 w:10
           │    │    │    ├── key: (7)
           │    │    │    ├── fd: ()-->(8), (7)-->(9,10)
           │    │    │    ├── ordering: +7 opt(8) [actual: +7]
           │    │    │    └── scan d@u
           │    │    │         ├── columns: k:7!null u:8!null
           │    │    │         ├── constraint: /8/7: [/3 - /3]
           │    │    │         ├── key: (7)
           │    │    │         ├── fd: ()-->(8)
           │    │    │         └── ordering: +7 opt(8) [actual: +7]
           │    │    └── filters
           │    │         └── w:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)]
           │    └── select
           │         ├── columns: k:13!null u:14 v:15!null w:16
           │         ├── key: (13)
           │         ├── fd: ()-->(15), (13)-->(14,16)
           │         ├── ordering: +13 opt(15) [actual: +13]
           │         ├── index-join d
           │         │    ├── columns: k:13!null u:14 v:15 w:16
           │         │    ├── key: (13)
           │         │    ├── fd: ()-->(15), (13)-->(14,16)
           │         │    ├── ordering: +13 opt(15) [actual: +13]
           │         │    └── scan d@v
           │         │         ├── columns: k:13!null v:15!null
           │         │         ├── constraint: /15/13: [/4 - /4]
           │         │         ├── key: (13)
           │         │         ├── fd: ()-->(15)
           │         │         └── ordering: +13 opt(15) [actual: +13]
           │         └── filters
           │              └── (u:14 = 1) OR (w:16 = 2) [outer=(14,16)]
           └── aggregations
                ├── const-agg [as=u:2, outer=(2)]
                │    └── u:2
                ├── const-agg [as=v:3, outer=(3)]
                │    └── v:3
                └── const-agg [as=w:4, outer=(4)]
                     └── w:4

# Don't apply when outer columns of both sides of OR do not intersect with index columns.
opt expect-not=SplitDisjunctionAddKey
SELECT u, w FROM d WHERE u = 1 OR w = 1
----
select
 ├── columns: u:2 w:4
 ├── scan d
 │    └── columns: u:2 w:4
 └── filters
      └── (u:2 = 1) OR (w:4 = 1) [outer=(2,4)]

# Don't apply to queries with strict keys.
opt expect-not=SplitDisjunctionAddKey
SELECT k, u, v FROM d WHERE u = 1 OR v = 1
----
distinct-on
 ├── columns: k:1!null u:2 v:3
 ├── grouping columns: k:1!null
 ├── internal-ordering: +1
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── union-all
 │    ├── columns: k:1!null u:2 v:3
 │    ├── left columns: k:7 u:8 v:9
 │    ├── right columns: k:13 u:14 v:15
 │    ├── ordering: +1
 │    ├── index-join d
 │    │    ├── columns: k:7!null u:8!null v:9
 │    │    ├── key: (7)
 │    │    ├── fd: ()-->(8), (7)-->(9)
 │    │    ├── ordering: +7 opt(8) [actual: +7]
 │    │    └── scan d@u
 │    │         ├── columns: k:7!null u:8!null
 │    │         ├── constraint: /8/7: [/1 - /1]
 │    │         ├── key: (7)
 │    │         ├── fd: ()-->(8)
 │    │         └── ordering: +7 opt(8) [actual: +7]
 │    └── index-join d
 │         ├── columns: k:13!null u:14 v:15!null
 │         ├── key: (13)
 │         ├── fd: ()-->(15), (13)-->(14)
 │         ├── ordering: +13 opt(15) [actual: +13]
 │         └── scan d@v
 │              ├── columns: k:13!null v:15!null
 │              ├── constraint: /15/13: [/1 - /1]
 │              ├── key: (13)
 │              ├── fd: ()-->(15)
 │              └── ordering: +13 opt(15) [actual: +13]
 └── aggregations
      ├── const-agg [as=u:2, outer=(2)]
      │    └── u:2
      └── const-agg [as=v:3, outer=(3)]
           └── v:3

# Don't apply to disjunctions with identical colsets on the left and right.
opt expect-not=SplitDisjunctionAddKey
SELECT u FROM d WHERE u = 1 OR u = 5
----
scan d@u
 ├── columns: u:2!null
 └── constraint: /2/1
      ├── [/1 - /1]
      └── [/5 - /5]

# Verifies that flags are copied to the duplicated scan.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM a@{NO_INDEX_JOIN} WHERE u = 1 OR v = 1
----
project
 ├── columns: u:2 v:3
 ├── lax-key: (2,3)
 ├── fd: (3)~~>(2)
 └── distinct-on
      ├── columns: k:1!null u:2 v:3
      ├── grouping columns: k:1!null
      ├── internal-ordering: +1
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── union-all
      │    ├── columns: k:1!null u:2 v:3
      │    ├── left columns: k:6 u:7 v:8
      │    ├── right columns: k:11 u:12 v:13
      │    ├── ordering: +1
      │    ├── scan a@u
      │    │    ├── columns: k:6!null u:7!null v:8
      │    │    ├── constraint: /7/6: [/1 - /1]
      │    │    ├── flags: no-index-join
      │    │    ├── key: (6)
      │    │    ├── fd: ()-->(7), (6)-->(8), (8)~~>(6)
      │    │    └── ordering: +6 opt(7) [actual: +6]
      │    └── scan a@v
      │         ├── columns: k:11!null u:12 v:13!null
      │         ├── constraint: /13: [/1 - /1]
      │         ├── flags: no-index-join
      │         ├── cardinality: [0 - 1]
      │         ├── key: ()
      │         └── fd: ()-->(11-13)
      └── aggregations
           ├── const-agg [as=u:2, outer=(2)]
           │    └── u:2
           └── const-agg [as=v:3, outer=(3)]
                └── v:3

# Columns are passed-through correctly when EliminateUnionAllLeft is applied.
opt expect=SplitDisjunctionAddKey
SELECT u, v FROM d WHERE u = 2 OR (v = 1 AND v = 3)
----
project
 ├── columns: u:2!null v:3
 ├── fd: ()-->(2)
 └── project
      ├── columns: k:1!null u:2!null v:3
      ├── key: (1)
      ├── fd: ()-->(2), (1)-->(3)
      ├── index-join d
      │    ├── columns: k:7!null u:8!null v:9
      │    ├── key: (7)
      │    ├── fd: ()-->(8), (7)-->(9)
      │    └── scan d@u
      │         ├── columns: k:7!null u:8!null
      │         ├── constraint: /8/7: [/2 - /2]
      │         ├── key: (7)
      │         └── fd: ()-->(8)
      └── projections
           ├── k:7 [as=k:1, outer=(7)]
           ├── u:8 [as=u:2, outer=(8)]
           └── v:9 [as=v:3, outer=(9)]

exec-ddl
CREATE INDEX idx_i ON p (i)
----

exec-ddl
CREATE INDEX idx_f ON p (f) WHERE s IN ('foo', 'bar', 'baz')
----

# Apply when one side of the disjunction can be "constrained" by an
# unconstrained partial index scan with no remaining filters.
opt expect=SplitDisjunctionAddKey
SELECT i, f, s, b FROM p WHERE i = 10 OR s IN ('foo', 'bar', 'baz')
----
project
 ├── columns: i:2 f:3 s:4 b:5
 └── distinct-on
      ├── columns: k:1!null i:2 f:3 s:4 b:5
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2-5)
      ├── union-all
      │    ├── columns: k:1!null i:2 f:3 s:4 b:5
      │    ├── left columns: k:8 i:9 f:10 s:11 b:12
      │    ├── right columns: k:15 i:16 f:17 s:18 b:19
      │    ├── index-join p
      │    │    ├── columns: k:8!null i:9!null f:10 s:11 b:12
      │    │    ├── key: (8)
      │    │    ├── fd: ()-->(9), (8)-->(10-12)
      │    │    └── scan p@idx_i
      │    │         ├── columns: k:8!null i:9!null
      │    │         ├── constraint: /9/8: [/10 - /10]
      │    │         ├── key: (8)
      │    │         └── fd: ()-->(9)
      │    └── index-join p
      │         ├── columns: k:15!null i:16 f:17 s:18!null b:19
      │         ├── key: (15)
      │         ├── fd: (15)-->(16-19)
      │         └── scan p@idx_f,partial
      │              ├── columns: k:15!null f:17
      │              ├── key: (15)
      │              └── fd: (15)-->(17)
      └── aggregations
           ├── const-agg [as=i:2, outer=(2)]
           │    └── i:2
           ├── const-agg [as=f:3, outer=(3)]
           │    └── f:3
           ├── const-agg [as=s:4, outer=(4)]
           │    └── s:4
           └── const-agg [as=b:5, outer=(5)]
                └── b:5

# Apply when one side of the disjunction can be "constrained" by an
# unconstrained partial index scan with remaining filters.
opt expect=SplitDisjunctionAddKey
SELECT i, s, f, b FROM p WHERE i = 10 OR s = 'foo'
----
project
 ├── columns: i:2 s:4 f:3 b:5
 └── distinct-on
      ├── columns: k:1!null i:2 f:3 s:4 b:5
      ├── grouping columns: k:1!null
      ├── key: (1)
      ├── fd: (1)-->(2-5)
      ├── union-all
      │    ├── columns: k:1!null i:2 f:3 s:4 b:5
      │    ├── left columns: k:8 i:9 f:10 s:11 b:12
      │    ├── right columns: k:15 i:16 f:17 s:18 b:19
      │    ├── index-join p
      │    │    ├── columns: k:8!null i:9!null f:10 s:11 b:12
      │    │    ├── key: (8)
      │    │    ├── fd: ()-->(9), (8)-->(10-12)
      │    │    └── scan p@idx_i
      │    │         ├── columns: k:8!null i:9!null
      │    │         ├── constraint: /9/8: [/10 - /10]
      │    │         ├── key: (8)
      │    │         └── fd: ()-->(9)
      │    └── select
      │         ├── columns: k:15!null i:16 f:17 s:18!null b:19
      │         ├── key: (15)
      │         ├── fd: ()-->(18), (15)-->(16,17,19)
      │         ├── index-join p
      │         │    ├── columns: k:15!null i:16 f:17 s:18 b:19
      │         │    ├── key: (15)
      │         │    ├── fd: (15)-->(16-19)
      │         │    └── scan p@idx_f,partial
      │         │         ├── columns: k:15!null f:17
      │         │         ├── key: (15)
      │         │         └── fd: (15)-->(17)
      │         └── filters
      │              └── s:18 = 'foo' [outer=(18), constraints=(/18: [/'foo' - /'foo']; tight), fd=()-->(18)]
      └── aggregations
           ├── const-agg [as=i:2, outer=(2)]
           │    └── i:2
           ├── const-agg [as=f:3, outer=(3)]
           │    └── f:3
           ├── const-agg [as=s:4, outer=(4)]
           │    └── s:4
           └── const-agg [as=b:5, outer=(5)]
                └── b:5

# Reproduction of #80820.
exec-ddl
CREATE TABLE table80820 (
    col0
        INT8 NOT NULL,
    col1
        INT8 NOT NULL,
    col2
        INT8 NOT NULL,
    col3
        BYTES NOT NULL,
    col4
        CHAR NOT NULL,
    INDEX (col0 DESC, col1, col2)
        PARTITION BY LIST (col0, col1)
            (
                PARTITION one VALUES IN ((1, 10)),
                PARTITION two VALUES IN ((2, 20))
            ),
    INDEX (col3, col4 DESC, col2)
        PARTITION BY LIST (col3, col4)
            (
                PARTITION one VALUES IN (('\xab', 'A')),
                PARTITION two VALUES IN (('\xcd', 'C'))
            )
)
----

opt
SELECT col0, col1, col2 FROM table80820 WHERE col2 < 4
----
select
 ├── columns: col0:1!null col1:2!null col2:3!null
 ├── scan table80820@table80820_col0_col1_col2_idx
 │    └── columns: col0:1!null col1:2!null col2:3!null
 └── filters
      └── col2:3 < 4 [outer=(3), constraints=(/3: (/NULL - /3]; tight)]

opt
SELECT col3, col4, col2 FROM table80820 WHERE col2 < 4
----
select
 ├── columns: col3:4!null col4:5!null col2:3!null
 ├── scan table80820@table80820_col3_col4_col2_idx
 │    └── columns: col2:3!null col3:4!null col4:5!null
 └── filters
      └── col2:3 < 4 [outer=(3), constraints=(/3: (/NULL - /3]; tight)]

# Reproduction for #83976. Do not split disjunctions with trivial columns.
exec-ddl
CREATE TABLE table83976 (c INT AS (5) STORED NOT NULL, UNIQUE INDEX (c));
----

opt expect-not=SplitDisjunction
SELECT * FROM table83976 WHERE c IS NULL OR rowid IS NULL
----
project
 ├── columns: c:1!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── select
      ├── columns: c:1!null rowid:2!null
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(1,2)
      ├── scan table83976
      │    ├── columns: c:1!null rowid:2!null
      │    ├── computed column expressions
      │    │    └── c:1
      │    │         └── 5
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    └── fd: ()-->(1,2)
      └── filters
           └── (c:1 IS NULL) OR (rowid:2 IS NULL) [outer=(1,2)]

# Regression test for #85356
exec-ddl
CREATE TABLE t85356 (c0 DECIMAL(15,3));
----

# Inlining of DECIMAL 3/2 as INT column rowid is not allowed.
opt expect-not=InlineConstVar
SELECT * FROM t85356
WHERE (CASE WHEN t85356.rowid < t85356.rowid THEN t85356.rowid ELSE 1 END) IN (t85356.rowid) AND NOT t85356.rowid!=3/2;
----
project
 ├── columns: c0:1
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1)
 └── select
      ├── columns: c0:1 rowid:2!null
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(1,2)
      ├── scan t85356
      │    ├── columns: c0:1 rowid:2!null
      │    ├── key: (2)
      │    └── fd: (2)-->(1)
      └── filters
           ├── rowid:2 = CASE WHEN (rowid:2 IS NOT DISTINCT FROM CAST(NULL AS INT8)) AND CAST(NULL AS BOOL) THEN rowid:2 ELSE 1 END [outer=(2), constraints=(/2: (/NULL - ])]
           └── rowid:2 = 1.5000000000000000000 [outer=(2), constraints=(/2: (/NULL - ]), fd=()-->(2)]

# Inlining of INT2 constant 2 as INT column rowid is allowed.
opt expect=InlineConstVar
SELECT * FROM t85356
WHERE (CASE WHEN t85356.rowid < t85356.rowid THEN t85356.rowid ELSE 1 END) IN (t85356.rowid) AND NOT t85356.rowid!=2::INT2;
----
values
 ├── columns: c0:1!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1)

# Inlining of INTEGER 2 as DECIMAL column c0 is not allowed because
# `CanHaveCompositeKeyEncoding` returns `true` for decimals.
opt expect-not=InlineConstVar
SELECT * FROM t85356
WHERE (CASE WHEN t85356.c0 < t85356.c0 THEN t85356.c0 ELSE 1.1 END) IN (t85356.c0) AND NOT t85356.c0!=2;
----
select
 ├── columns: c0:1!null
 ├── immutable
 ├── fd: ()-->(1)
 ├── scan t85356
 │    └── columns: c0:1
 └── filters
      ├── c0:1 = CASE WHEN (c0:1 IS NOT DISTINCT FROM CAST(NULL AS DECIMAL(15,3))) AND CAST(NULL AS BOOL) THEN c0:1::DECIMAL ELSE 1.1 END [outer=(1), immutable, constraints=(/1: (/NULL - ])]
      └── c0:1 = 2 [outer=(1), immutable, constraints=(/1: [/2 - /2]; tight), fd=()-->(1)]

exec-ddl
CREATE TABLE t85356_2 (c0 TIMESTAMP, c1 DATE);
----

# Inlining of a DATE constant as a TIMESTAMP is allowed.
opt expect=InlineConstVar
SELECT * FROM t85356_2
WHERE (CASE WHEN t85356_2.c0 < t85356_2.c0 THEN t85356_2.c0 ELSE '2000-01-01T02:00:00'::timestamp END) IN
        (t85356_2.c0) AND NOT t85356_2.c0!='12/01/01'::date;
----
values
 ├── columns: c0:1!null c1:2!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1,2)

# The lossy CAST of timestamp to DATE is not allowed for inlining.
opt expect-not=InlineConstVar
SELECT * FROM t85356_2
WHERE (CASE WHEN t85356_2.c1 < t85356_2.c1 THEN t85356_2.c1 ELSE '12/01/01'::date END) IN
        (t85356_2.c1) AND NOT t85356_2.c1!='2000-01-01T02:00:00'::timestamp;
----
select
 ├── columns: c0:1 c1:2!null
 ├── immutable
 ├── fd: ()-->(2)
 ├── scan t85356_2
 │    └── columns: c0:1 c1:2
 └── filters
      ├── c1:2 = CASE WHEN (c1:2 IS NOT DISTINCT FROM CAST(NULL AS DATE)) AND CAST(NULL AS BOOL) THEN c1:2 ELSE '2001-12-01' END [outer=(2), constraints=(/2: (/NULL - ])]
      └── c1:2 = '2000-01-01 02:00:00' [outer=(2), immutable, constraints=(/2: (/NULL - ]), fd=()-->(2)]

# A non-lossy CAST of timestamp to DATE is allowed for inlining.
opt expect=InlineConstVar
SELECT * FROM t85356_2
WHERE (CASE WHEN t85356_2.c1 < t85356_2.c1 THEN t85356_2.c1 ELSE '12/01/01'::date END) IN
        (t85356_2.c1) AND NOT t85356_2.c1!='2000-01-01T00:00:00'::timestamp;
----
values
 ├── columns: c0:1!null c1:2!null
 ├── cardinality: [0 - 0]
 ├── key: ()
 └── fd: ()-->(1,2)

# A statement which could actually return rows is inlined but correctly is not
# simplified into a `norows` values expression.
opt expect=InlineConstVar
SELECT * FROM t85356_2
WHERE (CASE WHEN t85356_2.c0 < t85356_2.c0 THEN t85356_2.c0 ELSE '12/01/01'::date::timestamp END) IN
        (t85356_2.c0) AND NOT t85356_2.c0!='12/01/01'::date;
----
select
 ├── columns: c0:1!null c1:2
 ├── fd: ()-->(1)
 ├── scan t85356_2
 │    └── columns: c0:1 c1:2
 └── filters
      └── c0:1 = '2001-12-01 00:00:00' [outer=(1), constraints=(/1: [/'2001-12-01 00:00:00' - /'2001-12-01 00:00:00']; tight), fd=()-->(1)]

# Regression test for 124455: LIKE expressions with an escaped backslash before
# the wildcard should still become constrained scans.

exec-ddl
CREATE TABLE t124455 (s STRING PRIMARY KEY)
----

opt expect=GenerateConstrainedScans
SELECT * FROM t124455 WHERE s LIKE e'\\\\%'
----
scan t124455
 ├── columns: s:1!null
 ├── constraint: /1: [/e'\\' - /']')
 └── key: (1)
