# ------------------------
# Tests without Histograms
# ------------------------

exec-ddl
CREATE TABLE a (
  k INT PRIMARY KEY,
  i INT,
  s STRING,
  t STRING
)
----

exec-ddl
ALTER TABLE a INJECT STATISTICS '[
  {
    "columns": ["k"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 5000,
    "avg_size": 1
  },
  {
    "columns": ["i"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 500,
    "avg_size": 2
  },
  {
    "columns": ["s"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 50,
    "null_count": 275,
    "avg_size": 5
  },
  {
    "columns": ["t"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 500,
    "null_count": 50,
    "avg_size": 6
  }
]'
----

# Unconstrained partial index scan.
# Distinct and null counts are updated based on the partial index predicate.

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

opt
SELECT * FROM a WHERE s = 'foo'
----
index-join a
 ├── columns: k:1(int!null) i:2(int) s:3(string!null) t:4(string)
 ├── stats: [rows=96.42857, distinct(3)=1, null(3)=0]
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2,4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int)
      ├── stats: [rows=96.42857, distinct(3)=1, null(3)=0]
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
DROP INDEX idx
----

# Test for select filter applied after an unconstrained partial index scan.

exec-ddl
CREATE INDEX idx ON a (s) WHERE i > 0 AND i < 50
----

opt
SELECT * FROM a WHERE i > 25 AND i < 50
----
select
 ├── columns: k:1(int!null) i:2(int!null) s:3(string) t:4(string)
 ├── stats: [rows=240, distinct(2)=24, null(2)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 ├── index-join a
 │    ├── columns: k:1(int!null) i:2(int) s:3(string) t:4(string)
 │    ├── stats: [rows=490]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4)
 │    └── scan a@idx,partial
 │         ├── columns: k:1(int!null) s:3(string)
 │         ├── stats: [rows=490, distinct(2)=49, null(2)=0]
 │         ├── key: (1)
 │         └── fd: (1)-->(3)
 └── filters
      └── i:2 > 25 [type=bool, outer=(2), constraints=(/2: [/26 - ]; tight)]

exec-ddl
DROP INDEX idx
----

# Test for multiple unapplied conjunctions due to non-tight constraints.

exec-ddl
CREATE INDEX idx ON a (i) WHERE i < k AND i % 3 = 0
----

opt
SELECT * FROM a WHERE i < k AND i % 3 = 0
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string) t:4(string)
 ├── immutable
 ├── stats: [rows=555.5556, distinct(1)=555.556, null(1)=0, distinct(2)=500, null(2)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int!null)
      ├── stats: [rows=555.5556, distinct(1)=555.556, null(1)=0, distinct(2)=500, null(2)=0]
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
DROP INDEX idx
----

# Test for an indexed column that is also constrained by partial index predicate.

exec-ddl
CREATE INDEX idx ON a (i) WHERE i > 0 AND i < 50
----

opt
SELECT * FROM a WHERE i > 15 AND i < 30
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string) t:4(string)
 ├── stats: [rows=140, distinct(2)=14, null(2)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int!null)
      ├── constraint: /2/1: [/16 - /29]
      ├── stats: [rows=140, distinct(2)=14, null(2)=0]
      ├── key: (1)
      └── fd: (1)-->(2)

opt disable=GenerateConstrainedScans
SELECT * FROM a WHERE i > 15 AND i < 30
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string) t:4(string)
 ├── stats: [rows=140, distinct(2)=14, null(2)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── select
      ├── columns: k:1(int!null) i:2(int!null)
      ├── stats: [rows=140, distinct(2)=14, null(2)=0]
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── scan a@idx,partial
      │    ├── columns: k:1(int!null) i:2(int!null)
      │    ├── stats: [rows=490, distinct(1)=490, null(1)=0, distinct(2)=49, null(2)=0]
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      └── filters
           └── (i:2 > 15) AND (i:2 < 30) [type=bool, outer=(2), constraints=(/2: [/16 - /29]; tight)]

opt
SELECT * FROM a WHERE (i > 0 AND i < 10) OR (i > 40 AND i < 50)
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string) t:4(string)
 ├── stats: [rows=180, distinct(2)=18, null(2)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int!null)
      ├── constraint: /2/1
      │    ├── [/1 - /9]
      │    └── [/41 - /49]
      ├── stats: [rows=180, distinct(2)=18, null(2)=0]
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
DROP INDEX idx
----

# Test for FuncDep equivalencies.

exec-ddl
CREATE INDEX idx ON a (s, t) WHERE s = t
----

opt
SELECT * FROM a WHERE s = t AND s LIKE '%foo%' AND t LIKE '%bar%'
----
index-join a
 ├── columns: k:1(int!null) i:2(int) s:3(string!null) t:4(string!null)
 ├── stats: [rows=1.04895, distinct(3)=1.04895, null(3)=0, distinct(4)=1.04895, null(4)=0, distinct(3,4)=1.04895, null(3,4)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)==(4), (4)==(3)
 └── select
      ├── columns: k:1(int!null) s:3(string!null) t:4(string!null)
      ├── stats: [rows=1.0395, distinct(3)=1.0395, null(3)=0, distinct(4)=1.0395, null(4)=0]
      ├── key: (1)
      ├── fd: (1)-->(3,4), (3)==(4), (4)==(3)
      ├── scan a@idx,partial
      │    ├── columns: k:1(int!null) s:3(string!null) t:4(string!null)
      │    ├── stats: [rows=9.3555, distinct(1)=9.3555, null(1)=0, distinct(3)=9.3555, null(3)=0, distinct(4)=9.3555, null(4)=0]
      │    ├── key: (1)
      │    └── fd: (1)-->(3,4), (3)==(4), (4)==(3)
      └── filters
           ├── s:3 LIKE '%foo%' [type=bool, outer=(3), constraints=(/3: (/NULL - ])]
           └── t:4 LIKE '%bar%' [type=bool, outer=(4), constraints=(/4: (/NULL - ])]

exec-ddl
DROP INDEX idx
----

# Test for null predicate.

exec-ddl
CREATE INDEX idx ON a (i) WHERE s IS NULL
----

opt
SELECT * FROM a WHERE s IS NULL
----
index-join a
 ├── columns: k:1(int!null) i:2(int) s:3(string) t:4(string)
 ├── stats: [rows=275, distinct(3)=1, null(3)=275]
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2,4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int)
      ├── stats: [rows=275, distinct(3)=1, null(3)=275]
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
DROP INDEX idx
----

# Test for a partial index with a predicate that references an un-indexed
# column.

exec-ddl
CREATE INDEX idx ON a (i) WHERE s IN ('foo', 'bar', 'baz')
----

opt
SELECT * FROM a WHERE i > 10 AND i < 20 AND s IN ('foo', 'bar', 'baz')
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null) t:4(string)
 ├── stats: [rows=20.70875, distinct(2)=9, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=20.7088, null(2,3)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int!null)
      ├── constraint: /2/1: [/11 - /19]
      ├── stats: [rows=20.70875, distinct(2)=9, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=20.7088, null(2,3)=0]
      ├── key: (1)
      └── fd: (1)-->(2)

opt
SELECT * FROM a WHERE i > 10 AND i < 20 AND s = 'baz'
----
select
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null) t:4(string)
 ├── stats: [rows=1.735715, distinct(2)=1.73571, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1.73571, null(2,3)=0]
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2,4)
 ├── index-join a
 │    ├── columns: k:1(int!null) i:2(int) s:3(string) t:4(string)
 │    ├── stats: [rows=20.70875]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4)
 │    └── scan a@idx,partial
 │         ├── columns: k:1(int!null) i:2(int!null)
 │         ├── constraint: /2/1: [/11 - /19]
 │         ├── stats: [rows=20.70875, distinct(2)=9, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=20.7088, null(2,3)=0]
 │         ├── key: (1)
 │         └── fd: (1)-->(2)
 └── filters
      └── s:3 = 'baz' [type=bool, outer=(3), constraints=(/3: [/'baz' - /'baz']; tight), fd=()-->(3)]

opt
SELECT * FROM a WHERE (i = 100 AND s = 'foo') OR (i = 200 AND s = 'bar')
----
select
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null) t:4(string)
 ├── stats: [rows=0.8417822, distinct(2)=0.841782, null(2)=0, distinct(3)=0.841782, null(3)=0, distinct(2,3)=0.841782, null(2,3)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 ├── index-join a
 │    ├── columns: k:1(int!null) i:2(int) s:3(string) t:4(string)
 │    ├── stats: [rows=3.876057]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4)
 │    └── scan a@idx,partial
 │         ├── columns: k:1(int!null) i:2(int!null)
 │         ├── constraint: /2/1
 │         │    ├── [/100 - /100]
 │         │    └── [/200 - /200]
 │         ├── stats: [rows=3.876057, distinct(2)=2, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=3.87606, null(2,3)=0]
 │         ├── key: (1)
 │         └── fd: (1)-->(2)
 └── filters
      └── ((i:2 = 100) AND (s:3 = 'foo')) OR ((i:2 = 200) AND (s:3 = 'bar')) [type=bool, outer=(2,3), constraints=(/2: [/100 - /100] [/200 - /200]; /3: [/'bar' - /'bar'] [/'foo' - /'foo'])]

exec-ddl
DROP INDEX idx
----

# Test for a partial index with a predicate that references indexed and
# un-indexed columns.

exec-ddl
CREATE INDEX idx ON a (i) WHERE i > 0 AND i < 50 AND s = 'foo'
----

opt
SELECT * FROM a WHERE i > 10 AND i < 20 AND s = 'foo'
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null) t:4(string)
 ├── stats: [rows=1.735715, distinct(2)=1.73571, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1.73571, null(2,3)=0]
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2,4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int!null)
      ├── constraint: /2/1: [/11 - /19]
      ├── stats: [rows=1.735715, distinct(2)=1.73571, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1.73571, null(2,3)=0]
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
DROP INDEX idx
----

# Test for a multi-column partial index with a predicate the references one
# indexed column.

exec-ddl
CREATE INDEX idx ON a (i, s) WHERE s IN ('foo', 'bar', 'baz')
----

opt
SELECT * FROM a WHERE i > 10 AND i < 20 AND s IN ('foo', 'bar', 'baz')
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null) t:4(string)
 ├── stats: [rows=20.70875, distinct(2)=9, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=20.7088, null(2,3)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
      ├── constraint: /2/3/1: [/11 - /19]
      ├── stats: [rows=20.70875, distinct(2)=9, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=20.7088, null(2,3)=0]
      ├── key: (1)
      └── fd: (1)-->(2,3)

opt
SELECT * FROM a WHERE i > 10 AND i < 50 AND s = 'baz'
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null) t:4(string)
 ├── stats: [rows=7.521429, distinct(2)=7.52143, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=7.52143, null(2,3)=0]
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2,4)
 └── select
      ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
      ├── stats: [rows=30.27301, distinct(3)=1, null(3)=0]
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(2)
      ├── scan a@idx,partial
      │    ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
      │    ├── constraint: /2/3/1: [/11/'baz' - /49/'baz']
      │    ├── stats: [rows=90.81902, distinct(1)=90.819, null(1)=0, distinct(2)=39, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=90.819, null(2,3)=0]
      │    ├── key: (1)
      │    └── fd: (1)-->(2,3)
      └── filters
           └── s:3 = 'baz' [type=bool, outer=(3), constraints=(/3: [/'baz' - /'baz']; tight), fd=()-->(3)]

exec-ddl
DROP INDEX idx
----

# Test for a multi-column partial index with a predicate that references all
# indexed columns.

exec-ddl
CREATE INDEX idx ON a (i, s) WHERE i > 0 AND i < 50 AND s = 'foo'
----

opt
SELECT * FROM a WHERE i > 10 AND i < 20 AND s = 'foo'
----
index-join a
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null) t:4(string)
 ├── stats: [rows=1.735715, distinct(2)=1.73571, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1.73571, null(2,3)=0]
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2,4)
 └── scan a@idx,partial
      ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
      ├── constraint: /2/3/1: [/11 - /19]
      ├── stats: [rows=1.735715, distinct(2)=1.73571, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1.73571, null(2,3)=0]
      ├── key: (1)
      └── fd: ()-->(3), (1)-->(2)

exec-ddl
DROP INDEX idx
----

# Regression test for #60502. Ensure that a constrained scan is preferred over
# an unconstrained scan.
exec-ddl
CREATE TABLE t (
   pk1 INT NOT NULL,
   pk2 INT NOT NULL,
   b1 BOOL,
   b2 BOOL,
   PRIMARY KEY (pk1, pk2),
   INDEX (pk2) WHERE (b1 = false) AND (b2 = false)
)
----

opt
SELECT * FROM t WHERE pk2 = 1 AND b1 = false AND b2 = false
----
project
 ├── columns: pk1:1(int!null) pk2:2(int!null) b1:3(bool!null) b2:4(bool!null)
 ├── stats: [rows=2.45025, distinct(2)=1, null(2)=0, distinct(3)=1, null(3)=0, distinct(4)=1, null(4)=0, distinct(2-4)=1, null(2-4)=0]
 ├── key: (1)
 ├── fd: ()-->(2-4)
 ├── scan t@t_pk2_idx,partial
 │    ├── columns: pk1:1(int!null) pk2:2(int!null)
 │    ├── constraint: /2/1: [/1 - /1]
 │    ├── stats: [rows=2.45025, distinct(2)=1, null(2)=0, distinct(3)=1, null(3)=0, distinct(4)=1, null(4)=0, distinct(2-4)=1, null(2-4)=0]
 │    ├── key: (1)
 │    └── fd: ()-->(2)
 └── projections
      ├── false [as=b1:3, type=bool]
      └── false [as=b2:4, type=bool]


# ---------------------
# Tests with Histograms
# ---------------------

exec-ddl
CREATE TABLE hist (
  k INT PRIMARY KEY,
  i INT,
  s STRING
)
----

exec-ddl
ALTER TABLE hist INJECT STATISTICS '[
  {
    "columns": ["i"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 1000,
    "distinct_count": 41,
    "null_count": 30,
    "avg_size": 2,
    "histo_col_type": "int",
    "histo_buckets": [
      {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "0"},
      {"num_eq": 10, "num_range": 90, "distinct_range": 9, "upper_bound": "100"},
      {"num_eq": 10, "num_range": 180, "distinct_range": 9, "upper_bound": "200"},
      {"num_eq": 20, "num_range": 270, "distinct_range": 9, "upper_bound": "300"},
      {"num_eq": 30, "num_range": 360, "distinct_range": 9, "upper_bound": "400"}
    ]
  },
  {
    "columns": ["s"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 1000,
    "distinct_count": 40,
    "avg_size": 3,
    "histo_col_type": "string",
    "histo_buckets": [
      {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "apple"},
      {"num_eq": 100, "num_range": 100, "distinct_range": 9, "upper_bound": "banana"},
      {"num_eq": 100, "num_range": 100, "distinct_range": 9, "upper_bound": "cherry"},
      {"num_eq": 200, "num_range": 100, "distinct_range": 9, "upper_bound": "mango"},
      {"num_eq": 200, "num_range": 100, "distinct_range": 9, "upper_bound": "pineapple"}
    ]
  }
]'
----

exec-ddl
CREATE INDEX idx ON hist (s) WHERE i > 100 AND i <= 150
----

opt
SELECT * FROM hist WHERE i > 125 AND i < 150
----
select
 ├── columns: k:1(int!null) i:2(int!null) s:3(string)
 ├── stats: [rows=43.63636, distinct(2)=3.09091, null(2)=0]
 │   histogram(2)=  0   0   41.818 1.8182
 │                <--- 125 -------- 149 -
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join hist
 │    ├── columns: k:1(int!null) i:2(int) s:3(string)
 │    ├── stats: [rows=90.90909]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan hist@idx,partial
 │         ├── columns: k:1(int!null) s:3(string)
 │         ├── stats: [rows=90.90909, distinct(2)=5.45455, null(2)=0]
 │         │   histogram(2)=  0   0   89.091 1.8182
 │         │                <--- 100 -------- 150 -
 │         ├── key: (1)
 │         └── fd: (1)-->(3)
 └── filters
      └── (i:2 > 125) AND (i:2 < 150) [type=bool, outer=(2), constraints=(/2: [/126 - /149]; tight)]

exec-ddl
DROP INDEX idx
----

# Test for an indexed column that is also constrained by partial index predicate.

exec-ddl
CREATE INDEX idx ON hist (i) WHERE i > 100 AND i <= 200
----

opt
SELECT * FROM hist WHERE i > 125 AND i < 150
----
index-join hist
 ├── columns: k:1(int!null) i:2(int!null) s:3(string)
 ├── stats: [rows=43.63636, distinct(2)=3.09091, null(2)=0]
 │   histogram(2)=  0   0   41.818 1.8182
 │                <--- 125 -------- 149 -
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── scan hist@idx,partial
      ├── columns: k:1(int!null) i:2(int!null)
      ├── constraint: /2/1: [/126 - /149]
      ├── stats: [rows=43.63636, distinct(2)=3.09091, null(2)=0]
      │   histogram(2)=  0   0   41.818 1.8182
      │                <--- 125 -------- 149 -
      ├── key: (1)
      └── fd: (1)-->(2)

opt disable=GenerateConstrainedScans
SELECT * FROM hist WHERE i > 125 AND i < 150
----
index-join hist
 ├── columns: k:1(int!null) i:2(int!null) s:3(string)
 ├── stats: [rows=43.63636, distinct(2)=3.09091, null(2)=0]
 │   histogram(2)=  0   0   41.818 1.8182
 │                <--- 125 -------- 149 -
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── select
      ├── columns: k:1(int!null) i:2(int!null)
      ├── stats: [rows=43.63636, distinct(2)=3.09091, null(2)=0]
      │   histogram(2)=  0   0   41.818 1.8182
      │                <--- 125 -------- 149 -
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── scan hist@idx,partial
      │    ├── columns: k:1(int!null) i:2(int!null)
      │    ├── stats: [rows=190, distinct(1)=190, null(1)=0, distinct(2)=10, null(2)=0]
      │    │   histogram(2)=  0   0   180  10
      │    │                <--- 100 ----- 200
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      └── filters
           └── (i:2 > 125) AND (i:2 < 150) [type=bool, outer=(2), constraints=(/2: [/126 - /149]; tight)]

opt
SELECT * FROM hist WHERE (i > 100 AND i < 125) OR (i > 150 AND i < 175)
----
index-join hist
 ├── columns: k:1(int!null) i:2(int!null) s:3(string)
 ├── stats: [rows=87.27273, distinct(2)=6.18182, null(2)=0]
 │   histogram(2)=  0   0   41.818 1.8182 0   0   41.818 1.8182
 │                <--- 100 -------- 124 ---- 150 -------- 174 -
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── scan hist@idx,partial
      ├── columns: k:1(int!null) i:2(int!null)
      ├── constraint: /2/1
      │    ├── [/101 - /124]
      │    └── [/151 - /174]
      ├── stats: [rows=87.27273, distinct(2)=6.18182, null(2)=0]
      │   histogram(2)=  0   0   41.818 1.8182 0   0   41.818 1.8182
      │                <--- 100 -------- 124 ---- 150 -------- 174 -
      ├── key: (1)
      └── fd: (1)-->(2)

exec-ddl
DROP INDEX idx
----

# Test for a partial index with a predicate that references an un-indexed
# column.

exec-ddl
CREATE INDEX idx ON hist (i) WHERE s IN ('banana', 'cherry', 'mango')
----

opt
SELECT * FROM hist WHERE i > 125 AND i < 130 AND s IN ('banana', 'cherry', 'mango')
----
index-join hist
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
 ├── stats: [rows=3.02224, distinct(2)=1.27273, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=3.02224, null(2,3)=0]
 │   histogram(2)=  0   0   2.2667 0.75556
 │                <--- 125 --------- 129 -
 │   histogram(3)=  0  0.75556   0  0.75556   0  1.5111
 │                <--- 'banana' --- 'cherry' --- 'mango'
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── scan hist@idx,partial
      ├── columns: k:1(int!null) i:2(int!null)
      ├── constraint: /2/1: [/126 - /129]
      ├── stats: [rows=3.02224, distinct(2)=1.27273, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=3.02224, null(2,3)=0]
      │   histogram(2)=  0   0   2.2667 0.75556
      │                <--- 125 --------- 129 -
      │   histogram(3)=  0  0.75556   0  0.75556   0  1.5111
      │                <--- 'banana' --- 'cherry' --- 'mango'
      ├── key: (1)
      └── fd: (1)-->(2)

opt
SELECT * FROM hist WHERE i > 125 AND i < 130 AND s = 'mango'
----
select
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
 ├── stats: [rows=1.454546, distinct(2)=1.27273, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1.27273, null(2,3)=0]
 │   histogram(2)=  0   0   1.0909 0.36364
 │                <--- 125 --------- 129 -
 │   histogram(3)=  0  1.4545
 │                <--- 'mango'
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── index-join hist
 │    ├── columns: k:1(int!null) i:2(int) s:3(string)
 │    ├── stats: [rows=3.02224]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan hist@idx,partial
 │         ├── columns: k:1(int!null) i:2(int!null)
 │         ├── constraint: /2/1: [/126 - /129]
 │         ├── stats: [rows=3.02224, distinct(2)=1.27273, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=3.02224, null(2,3)=0]
 │         │   histogram(2)=  0   0   2.2667 0.75556
 │         │                <--- 125 --------- 129 -
 │         │   histogram(3)=  0  0.75556   0  0.75556   0  1.5111
 │         │                <--- 'banana' --- 'cherry' --- 'mango'
 │         ├── key: (1)
 │         └── fd: (1)-->(2)
 └── filters
      └── s:3 = 'mango' [type=bool, outer=(3), constraints=(/3: [/'mango' - /'mango']; tight), fd=()-->(3)]

opt
SELECT * FROM hist WHERE (i = 100 AND s = 'banana') OR (i = 200 AND s = 'cherry')
----
select
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
 ├── stats: [rows=1.270987, distinct(2)=1.27099, null(2)=0, distinct(3)=1.27099, null(3)=0, distinct(2,3)=1.27099, null(2,3)=0]
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── index-join hist
 │    ├── columns: k:1(int!null) i:2(int) s:3(string)
 │    ├── stats: [rows=8.556886]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    └── scan hist@idx,partial
 │         ├── columns: k:1(int!null) i:2(int!null)
 │         ├── constraint: /2/1
 │         │    ├── [/100 - /100]
 │         │    └── [/200 - /200]
 │         ├── stats: [rows=8.556886, distinct(2)=2, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=5.95077, null(2,3)=0]
 │         │   histogram(2)=  0 4.2784 0 4.2784
 │         │                <--- 100 ---- 200 -
 │         │   histogram(3)=  0   2.1392   0   2.1392   0  4.2784
 │         │                <--- 'banana' --- 'cherry' --- 'mango'
 │         ├── key: (1)
 │         └── fd: (1)-->(2)
 └── filters
      └── ((i:2 = 100) AND (s:3 = 'banana')) OR ((i:2 = 200) AND (s:3 = 'cherry')) [type=bool, outer=(2,3), constraints=(/2: [/100 - /100] [/200 - /200]; /3: [/'banana' - /'banana'] [/'cherry' - /'cherry'])]

exec-ddl
DROP INDEX idx
----

# Test for a partial index with a predicate that references indexed and
# un-indexed columns.

exec-ddl
CREATE INDEX idx ON hist (i) WHERE i > 100 AND i <= 200 AND s = 'banana'
----

opt
SELECT * FROM hist WHERE i > 125 AND i < 150 AND s = 'banana'
----
project
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
 ├── stats: [rows=4.363636, distinct(2)=3.09091, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=3.09091, null(2,3)=0]
 │   histogram(2)=  0   0   4.1818 0.18182
 │                <--- 125 --------- 149 -
 │   histogram(3)=  0   4.3636
 │                <--- 'banana'
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── scan hist@idx,partial
 │    ├── columns: k:1(int!null) i:2(int!null)
 │    ├── constraint: /2/1: [/126 - /149]
 │    ├── stats: [rows=4.363636, distinct(2)=3.09091, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=3.09091, null(2,3)=0]
 │    │   histogram(2)=  0   0   4.1818 0.18182
 │    │                <--- 125 --------- 149 -
 │    │   histogram(3)=  0   4.3636
 │    │                <--- 'banana'
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── projections
      └── 'banana' [as=s:3, type=string]

exec-ddl
DROP INDEX idx
----

# Test for a multi-column partial index with a predicate the references one
# indexed column.

exec-ddl
CREATE INDEX idx ON hist (i, s) WHERE s IN ('banana', 'cherry', 'mango')
----

opt
SELECT * FROM hist WHERE i > 125 AND i < 130 AND s IN ('banana', 'cherry', 'mango')
----
scan hist@idx,partial
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
 ├── constraint: /2/3/1: [/126 - /129]
 ├── stats: [rows=3.02224, distinct(2)=1.27273, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=3.02224, null(2,3)=0]
 │   histogram(2)=  0   0   2.2667 0.75556
 │                <--- 125 --------- 129 -
 │   histogram(3)=  0  0.75556   0  0.75556   0  1.5111
 │                <--- 'banana' --- 'cherry' --- 'mango'
 ├── key: (1)
 └── fd: (1)-->(2,3)

opt
SELECT * FROM hist WHERE i > 125 AND i < 130 AND s = 'mango'
----
select
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
 ├── stats: [rows=1.454546, distinct(2)=1.27273, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1.27273, null(2,3)=0]
 │   histogram(2)=  0   0   1.0909 0.36364
 │                <--- 125 --------- 129 -
 │   histogram(3)=  0  1.4545
 │                <--- 'mango'
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── scan hist@idx,partial
 │    ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
 │    ├── constraint: /2/3/1: [/126/'mango' - /129/'mango']
 │    ├── stats: [rows=3.02224, distinct(2)=1.27273, null(2)=0, distinct(3)=3, null(3)=0, distinct(2,3)=3.02224, null(2,3)=0]
 │    │   histogram(2)=  0   0   2.2667 0.75556
 │    │                <--- 125 --------- 129 -
 │    │   histogram(3)=  0  0.75556   0  0.75556   0  1.5111
 │    │                <--- 'banana' --- 'cherry' --- 'mango'
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3)
 └── filters
      └── s:3 = 'mango' [type=bool, outer=(3), constraints=(/3: [/'mango' - /'mango']; tight), fd=()-->(3)]

exec-ddl
DROP INDEX idx
----

# Test for a multi-column partial index with a predicate that references all
# indexed columns.

exec-ddl
CREATE INDEX idx ON hist (i, s) WHERE i > 100 AND i <= 200 AND s = 'banana'
----

opt
SELECT * FROM hist WHERE i > 125 AND i < 130 AND s = 'banana'
----
scan hist@idx,partial
 ├── columns: k:1(int!null) i:2(int!null) s:3(string!null)
 ├── constraint: /2/3/1: [/126 - /129]
 ├── stats: [rows=0.7272728, distinct(2)=0.727273, null(2)=0, distinct(3)=0.727273, null(3)=0, distinct(2,3)=0.727273, null(2,3)=0]
 │   histogram(2)=  0   0   0.54545 0.18182
 │                <--- 125 ---------- 129 -
 │   histogram(3)=  0  0.72727
 │                <--- 'banana'
 ├── key: (1)
 └── fd: ()-->(3), (1)-->(2)

exec-ddl
DROP INDEX idx
----

# ----------------------------------
# Tests for partial inverted indexes
# ----------------------------------

exec-ddl
CREATE TABLE inv (
  k INT PRIMARY KEY,
  i INT,
  j JSON,
  s STRING,
  INVERTED INDEX partial (j) WHERE s IN ('foo', 'bar')
)
----

exec-ddl
ALTER TABLE inv INJECT STATISTICS '[
  {
    "columns": ["k"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 5000,
    "avg_size": 5
  },
  {
    "columns": ["i"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 500,
    "avg_size": 2
  },
  {
    "columns": ["j"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 500,
    "avg_size": 34
  },
  {
    "columns": ["s"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 500,
    "null_count": 50,
    "avg_size": 23
  }
]'
----

opt
SELECT k FROM inv@partial WHERE j @> '{"x": "y"}' AND s IN ('foo', 'bar')
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=2.204409]
 ├── key: (1)
 └── scan inv@partial,inverted,partial
      ├── columns: k:1(int!null)
      ├── inverted constraint: /7/1
      │    └── spans: ["x"/"y", "x"/"y"]
      ├── flags: force-index=partial
      ├── stats: [rows=2.204409, distinct(4)=2, null(4)=0, distinct(7)=2.20441, null(7)=0]
      └── key: (1)

opt
SELECT * FROM inv@partial WHERE j @> '{"x": "y"}' AND s IN ('foo', 'bar')
----
index-join inv
 ├── columns: k:1(int!null) i:2(int) j:3(jsonb!null) s:4(string!null)
 ├── immutable
 ├── stats: [rows=2.204409, distinct(4)=2, null(4)=0]
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── scan inv@partial,inverted,partial
      ├── columns: k:1(int!null)
      ├── inverted constraint: /7/1
      │    └── spans: ["x"/"y", "x"/"y"]
      ├── flags: force-index=partial
      ├── stats: [rows=2.204409, distinct(4)=2, null(4)=0, distinct(7)=2.20441, null(7)=0]
      └── key: (1)

opt
SELECT k FROM inv@partial WHERE j @> '{"x": "y"}' AND s = 'foo'
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=1.102204]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) j:3(jsonb!null) s:4(string!null)
      ├── immutable
      ├── stats: [rows=1.102204, distinct(4)=1, null(4)=0]
      ├── key: (1)
      ├── fd: ()-->(4), (1)-->(3)
      ├── index-join inv
      │    ├── columns: k:1(int!null) j:3(jsonb) s:4(string)
      │    ├── stats: [rows=2.204409]
      │    ├── key: (1)
      │    ├── fd: (1)-->(3,4)
      │    └── scan inv@partial,inverted,partial
      │         ├── columns: k:1(int!null)
      │         ├── inverted constraint: /7/1
      │         │    └── spans: ["x"/"y", "x"/"y"]
      │         ├── flags: force-index=partial
      │         ├── stats: [rows=2.204409, distinct(4)=2, null(4)=0, distinct(7)=2.20441, null(7)=0]
      │         └── key: (1)
      └── filters
           └── s:4 = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

opt
SELECT * FROM inv@partial WHERE j @> '{"x": "y"}' AND s = 'foo'
----
select
 ├── columns: k:1(int!null) i:2(int) j:3(jsonb!null) s:4(string!null)
 ├── immutable
 ├── stats: [rows=1.102204, distinct(4)=1, null(4)=0]
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3)
 ├── index-join inv
 │    ├── columns: k:1(int!null) i:2(int) j:3(jsonb) s:4(string)
 │    ├── stats: [rows=2.204409]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4)
 │    └── scan inv@partial,inverted,partial
 │         ├── columns: k:1(int!null)
 │         ├── inverted constraint: /7/1
 │         │    └── spans: ["x"/"y", "x"/"y"]
 │         ├── flags: force-index=partial
 │         ├── stats: [rows=2.204409, distinct(4)=2, null(4)=0, distinct(7)=2.20441, null(7)=0]
 │         └── key: (1)
 └── filters
      └── s:4 = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

opt
SELECT * FROM inv@partial WHERE j @> '{"x": "y"}' AND s = 'foo' AND i > 0 AND i < 10
----
select
 ├── columns: k:1(int!null) i:2(int!null) j:3(jsonb!null) s:4(string!null)
 ├── immutable
 ├── stats: [rows=0.01983973, distinct(2)=0.0198397, null(2)=0, distinct(4)=0.0198397, null(4)=0, distinct(2,4)=0.0198397, null(2,4)=0]
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3)
 ├── index-join inv
 │    ├── columns: k:1(int!null) i:2(int) j:3(jsonb) s:4(string)
 │    ├── stats: [rows=2.204409]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4)
 │    └── scan inv@partial,inverted,partial
 │         ├── columns: k:1(int!null)
 │         ├── inverted constraint: /7/1
 │         │    └── spans: ["x"/"y", "x"/"y"]
 │         ├── flags: force-index=partial
 │         ├── stats: [rows=2.204409, distinct(4)=2, null(4)=0, distinct(7)=2.20441, null(7)=0]
 │         └── key: (1)
 └── filters
      ├── (i:2 > 0) AND (i:2 < 10) [type=bool, outer=(2), constraints=(/2: [/1 - /9]; tight)]
      └── s:4 = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight), fd=()-->(4)]

# --------------------------------------------------
# Tests for partial inverted indexes with histograms
# --------------------------------------------------

exec-ddl
CREATE TABLE inv_hist (
  k INT PRIMARY KEY,
  i INT,
  j JSON,
  s STRING,
  INVERTED INDEX partial (j) WHERE s IN ('apple', 'banana', 'cherry')
)
----

# Histogram boundaries are from JSON values '{"a": 1}', '{"g": 7}', and '{"n":
# 14}'.
exec-ddl
ALTER TABLE inv_hist INJECT STATISTICS '[
  {
    "columns": ["i"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 1000,
    "distinct_count": 41,
    "null_count": 30,
    "avg_size": 2,
    "histo_col_type": "int",
    "histo_buckets": [
      {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "0"},
      {"num_eq": 10, "num_range": 90, "distinct_range": 9, "upper_bound": "100"},
      {"num_eq": 10, "num_range": 180, "distinct_range": 9, "upper_bound": "200"},
      {"num_eq": 20, "num_range": 270, "distinct_range": 9, "upper_bound": "300"},
      {"num_eq": 30, "num_range": 360, "distinct_range": 9, "upper_bound": "400"}
    ]
  },
  {
    "columns": ["j"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 1000,
    "distinct_count": 201,
    "null_count": 200,
    "avg_size": 34,
    "histo_col_type": "bytes",
    "histo_buckets": [
      {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "\\x376100012a0200"},
      {"num_eq": 100, "num_range": 200, "distinct_range": 99, "upper_bound": "\\x376700012a0e00"},
      {"num_eq": 200, "num_range": 300, "distinct_range": 99, "upper_bound": "\\x376e00012a1c00"}
    ]
  },
  {
    "columns": ["s"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 1000,
    "distinct_count": 40,
    "avg_size": 23,
    "histo_col_type": "string",
    "histo_buckets": [
      {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "apple"},
      {"num_eq": 100, "num_range": 100, "distinct_range": 9, "upper_bound": "banana"},
      {"num_eq": 100, "num_range": 100, "distinct_range": 9, "upper_bound": "cherry"},
      {"num_eq": 200, "num_range": 100, "distinct_range": 9, "upper_bound": "mango"},
      {"num_eq": 200, "num_range": 100, "distinct_range": 9, "upper_bound": "pineapple"}
    ]
  }
]'
----

opt
SELECT k FROM inv_hist@partial WHERE j @> '{"g": 7}' AND s IN ('apple', 'banana', 'cherry')
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=17.77778]
 ├── key: (1)
 └── scan inv_hist@partial,inverted,partial
      ├── columns: k:1(int!null)
      ├── inverted constraint: /7/1
      │    └── spans: ["g"/7, "g"/7]
      ├── flags: force-index=partial
      ├── stats: [rows=20, distinct(4)=2, null(4)=0, distinct(7)=1, null(7)=0, distinct(4,7)=2, null(4,7)=0]
      │   histogram(4)=  0     10     0     10
      │                <--- 'banana' --- 'cherry'
      │   histogram(7)=  0          20          1.1895e-16          0
      │                <--- '\x376700012a0e00' ------------ '\x376700012a0e01'
      └── key: (1)

opt
SELECT * FROM inv_hist@partial WHERE j @> '{"g": 7}' AND s IN ('apple', 'banana', 'cherry')
----
index-join inv_hist
 ├── columns: k:1(int!null) i:2(int) j:3(jsonb!null) s:4(string!null)
 ├── immutable
 ├── stats: [rows=17.77778, distinct(4)=2, null(4)=0]
 │   histogram(4)=  0   8.8889   0   8.8889
 │                <--- 'banana' --- 'cherry'
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 └── scan inv_hist@partial,inverted,partial
      ├── columns: k:1(int!null)
      ├── inverted constraint: /7/1
      │    └── spans: ["g"/7, "g"/7]
      ├── flags: force-index=partial
      ├── stats: [rows=20, distinct(4)=2, null(4)=0, distinct(7)=1, null(7)=0, distinct(4,7)=2, null(4,7)=0]
      │   histogram(4)=  0     10     0     10
      │                <--- 'banana' --- 'cherry'
      │   histogram(7)=  0          20          1.1895e-16          0
      │                <--- '\x376700012a0e00' ------------ '\x376700012a0e01'
      └── key: (1)

opt
SELECT k FROM inv_hist@partial WHERE j @> '{"g": 7}' AND s = 'banana'
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=8.888889]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) j:3(jsonb!null) s:4(string!null)
      ├── immutable
      ├── stats: [rows=8.888889, distinct(4)=1, null(4)=0]
      │   histogram(4)=  0   8.8889
      │                <--- 'banana'
      ├── key: (1)
      ├── fd: ()-->(4), (1)-->(3)
      ├── index-join inv_hist
      │    ├── columns: k:1(int!null) j:3(jsonb) s:4(string)
      │    ├── stats: [rows=20]
      │    ├── key: (1)
      │    ├── fd: (1)-->(3,4)
      │    └── scan inv_hist@partial,inverted,partial
      │         ├── columns: k:1(int!null)
      │         ├── inverted constraint: /7/1
      │         │    └── spans: ["g"/7, "g"/7]
      │         ├── flags: force-index=partial
      │         ├── stats: [rows=20, distinct(4)=2, null(4)=0, distinct(7)=1, null(7)=0, distinct(4,7)=2, null(4,7)=0]
      │         │   histogram(4)=  0     10     0     10
      │         │                <--- 'banana' --- 'cherry'
      │         │   histogram(7)=  0          20          1.1895e-16          0
      │         │                <--- '\x376700012a0e00' ------------ '\x376700012a0e01'
      │         └── key: (1)
      └── filters
           └── s:4 = 'banana' [type=bool, outer=(4), constraints=(/4: [/'banana' - /'banana']; tight), fd=()-->(4)]

opt
SELECT * FROM inv_hist@partial WHERE j @> '{"g": 7}' AND s = 'banana'
----
select
 ├── columns: k:1(int!null) i:2(int) j:3(jsonb!null) s:4(string!null)
 ├── immutable
 ├── stats: [rows=8.888889, distinct(4)=1, null(4)=0]
 │   histogram(4)=  0   8.8889
 │                <--- 'banana'
 ├── key: (1)
 ├── fd: ()-->(4), (1)-->(2,3)
 ├── index-join inv_hist
 │    ├── columns: k:1(int!null) i:2(int) j:3(jsonb) s:4(string)
 │    ├── stats: [rows=20]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4)
 │    └── scan inv_hist@partial,inverted,partial
 │         ├── columns: k:1(int!null)
 │         ├── inverted constraint: /7/1
 │         │    └── spans: ["g"/7, "g"/7]
 │         ├── flags: force-index=partial
 │         ├── stats: [rows=20, distinct(4)=2, null(4)=0, distinct(7)=1, null(7)=0, distinct(4,7)=2, null(4,7)=0]
 │         │   histogram(4)=  0     10     0     10
 │         │                <--- 'banana' --- 'cherry'
 │         │   histogram(7)=  0          20          1.1895e-16          0
 │         │                <--- '\x376700012a0e00' ------------ '\x376700012a0e01'
 │         └── key: (1)
 └── filters
      └── s:4 = 'banana' [type=bool, outer=(4), constraints=(/4: [/'banana' - /'banana']; tight), fd=()-->(4)]

opt
SELECT * FROM inv_hist@partial WHERE j @> '{"g": 7}' AND s IN ('apple', 'banana', 'cherry') AND i > 0 AND i <= 100
----
select
 ├── columns: k:1(int!null) i:2(int!null) j:3(jsonb!null) s:4(string!null)
 ├── immutable
 ├── stats: [rows=3.080741, distinct(2)=3.08074, null(2)=0, distinct(4)=2, null(4)=0, distinct(2,4)=3.08074, null(2,4)=0]
 │   histogram(2)=  0  0  2.7727 0.30807
 │                <--- 0 --------- 100 -
 │   histogram(4)=  0   1.5404   0   1.5404
 │                <--- 'banana' --- 'cherry'
 ├── key: (1)
 ├── fd: (1)-->(2-4)
 ├── index-join inv_hist
 │    ├── columns: k:1(int!null) i:2(int) j:3(jsonb) s:4(string)
 │    ├── stats: [rows=20]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4)
 │    └── scan inv_hist@partial,inverted,partial
 │         ├── columns: k:1(int!null)
 │         ├── inverted constraint: /7/1
 │         │    └── spans: ["g"/7, "g"/7]
 │         ├── flags: force-index=partial
 │         ├── stats: [rows=20, distinct(4)=2, null(4)=0, distinct(7)=1, null(7)=0, distinct(4,7)=2, null(4,7)=0]
 │         │   histogram(4)=  0     10     0     10
 │         │                <--- 'banana' --- 'cherry'
 │         │   histogram(7)=  0          20          1.1895e-16          0
 │         │                <--- '\x376700012a0e00' ------------ '\x376700012a0e01'
 │         └── key: (1)
 └── filters
      └── (i:2 > 0) AND (i:2 <= 100) [type=bool, outer=(2), constraints=(/2: [/1 - /100]; tight)]

# ----------------------------------------------------------
# Tests for partial inverted spatial indexes
# ----------------------------------------------------------

exec-ddl
CREATE TABLE spatial (
    k INT PRIMARY KEY,
    g GEOMETRY,
    s STRING,
    INVERTED INDEX i (g),
    INVERTED INDEX p (g) WHERE s IN ('apple', 'banana', 'cherry')
)
----

# Add non-histogram stats.
exec-ddl
ALTER TABLE spatial INJECT STATISTICS '[
  {
    "columns": ["g"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 7,
    "null_count": 0,
    "avg_size": 12
  },
  {
    "columns": ["s"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 40,
    "null_count": 0,
    "avg_size": 18
  }
]'
----

# The partial index should be preferred over the non-partial index or PK.
opt
SELECT k FROM spatial WHERE st_intersects('LINESTRING(0.5 0.5, 0.7 0.7)', g) AND s IN ('apple', 'banana', 'cherry')
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=16.66667]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) g:2(geometry!null) s:3(string!null)
      ├── immutable
      ├── stats: [rows=16.66667, distinct(2)=7, null(2)=0, distinct(3)=3, null(3)=0]
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── index-join spatial
      │    ├── columns: k:1(int!null) g:2(geometry) s:3(string)
      │    ├── stats: [rows=16.66667]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── inverted-filter
      │         ├── columns: k:1(int!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\x12\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_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool]
      │         ├── stats: [rows=16.66667]
      │         ├── key: (1)
      │         └── scan spatial@p,inverted,partial
      │              ├── columns: k:1(int!null) g_inverted_key:7(encodedkey!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\x12\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"]
      │              ├── stats: [rows=16.66667, distinct(1)=16.6667, null(1)=0, distinct(3)=3, null(3)=0, distinct(7)=16.6667, null(7)=0]
      │              └── key: (1,7)
      └── filters
           └── st_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]

# The partial index should be preferred over the non-partial index or PK when
# there is an additional filter to apply on s.
opt
SELECT k FROM spatial WHERE st_intersects('LINESTRING(0.5 0.5, 0.7 0.7)', g) AND s = 'banana'
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=5.555556]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) g:2(geometry!null) s:3(string!null)
      ├── immutable
      ├── stats: [rows=5.555556, distinct(2)=5.55556, null(2)=0, distinct(3)=1, null(3)=0]
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(2)
      ├── index-join spatial
      │    ├── columns: k:1(int!null) g:2(geometry) s:3(string)
      │    ├── stats: [rows=16.66667]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── inverted-filter
      │         ├── columns: k:1(int!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\x12\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_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool]
      │         ├── stats: [rows=16.66667]
      │         ├── key: (1)
      │         └── scan spatial@p,inverted,partial
      │              ├── columns: k:1(int!null) g_inverted_key:7(encodedkey!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\x12\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"]
      │              ├── stats: [rows=16.66667, distinct(1)=16.6667, null(1)=0, distinct(3)=3, null(3)=0, distinct(7)=16.6667, null(7)=0]
      │              └── key: (1,7)
      └── filters
           ├── st_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]
           └── s:3 = 'banana' [type=bool, outer=(3), constraints=(/3: [/'banana' - /'banana']; tight), fd=()-->(3)]

# Add null values for s.
exec-ddl
ALTER TABLE spatial INJECT STATISTICS '[
  {
    "columns": ["g"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 7,
    "null_count": 0,
    "avg_size": 12
  },
  {
    "columns": ["s"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 40,
    "null_count": 1000,
    "avg_size": 18
  }
]'
----

opt
SELECT k FROM spatial WHERE st_intersects('LINESTRING(0.5 0.5, 0.7 0.7)', g) AND s IN ('apple', 'banana', 'cherry')
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=8.547009]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) g:2(geometry!null) s:3(string!null)
      ├── immutable
      ├── stats: [rows=8.547009, distinct(2)=7, null(2)=0, distinct(3)=3, null(3)=0]
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── index-join spatial
      │    ├── columns: k:1(int!null) g:2(geometry) s:3(string)
      │    ├── stats: [rows=8.547009]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── inverted-filter
      │         ├── columns: k:1(int!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\x12\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_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool]
      │         ├── stats: [rows=8.547009]
      │         ├── key: (1)
      │         └── scan spatial@p,inverted,partial
      │              ├── columns: k:1(int!null) g_inverted_key:7(encodedkey!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\x12\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"]
      │              ├── stats: [rows=8.547009, distinct(1)=8.54701, null(1)=0, distinct(3)=3, null(3)=0, distinct(7)=8.54701, null(7)=0]
      │              └── key: (1,7)
      └── filters
           └── st_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]

opt
SELECT k FROM spatial WHERE st_intersects('LINESTRING(0.5 0.5, 0.7 0.7)', g) AND s = 'banana'
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=2.849003]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) g:2(geometry!null) s:3(string!null)
      ├── immutable
      ├── stats: [rows=2.849003, distinct(2)=2.849, null(2)=0, distinct(3)=1, null(3)=0]
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(2)
      ├── index-join spatial
      │    ├── columns: k:1(int!null) g:2(geometry) s:3(string)
      │    ├── stats: [rows=8.547009]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── inverted-filter
      │         ├── columns: k:1(int!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\x12\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_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool]
      │         ├── stats: [rows=8.547009]
      │         ├── key: (1)
      │         └── scan spatial@p,inverted,partial
      │              ├── columns: k:1(int!null) g_inverted_key:7(encodedkey!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\x12\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"]
      │              ├── stats: [rows=8.547009, distinct(1)=8.54701, null(1)=0, distinct(3)=3, null(3)=0, distinct(7)=8.54701, null(7)=0]
      │              └── key: (1,7)
      └── filters
           ├── st_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]
           └── s:3 = 'banana' [type=bool, outer=(3), constraints=(/3: [/'banana' - /'banana']; tight), fd=()-->(3)]

# Add histogram statistics.
#
# Histogram boundaries are from a `POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0,
# 0.0 1.0, 0.0 0.0))` row. The row_count is lower than the sum of the
# histogram's num_eq and num_range because there are more entries in
# the inverted index than rows in the table.
exec-ddl
ALTER TABLE spatial INJECT STATISTICS '[
  {
    "columns": ["g"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 7,
    "null_count": 0,
    "avg_size": 12,
    "histo_col_type":"BYTES",
    "histo_buckets":[
      {"num_eq": 1000, "num_range": 0, "distinct_range": 0, "upper_bound": "\\x42fd0555555555555555"},
      {"num_eq": 1000, "num_range": 1000, "distinct_range": 1, "upper_bound": "\\x42fd0fffffff00000000"},
      {"num_eq": 1000, "num_range": 1000, "distinct_range": 1, "upper_bound": "\\x42fd1000000100000000"},
      {"num_eq": 1000, "num_range": 1000, "distinct_range": 1, "upper_bound": "\\x42fd1aaaaaab00000000"}
     ]
  },
  {
    "columns": ["s"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 40,
    "null_count": 0,
    "avg_size": 18,
    "histo_col_type": "string",
    "histo_buckets": [
      {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "apple"},
      {"num_eq": 100, "num_range": 300, "distinct_range": 9, "upper_bound": "banana"},
      {"num_eq": 100, "num_range": 300, "distinct_range": 9, "upper_bound": "cherry"},
      {"num_eq": 200, "num_range": 400, "distinct_range": 9, "upper_bound": "mango"},
      {"num_eq": 200, "num_range": 400, "distinct_range": 9, "upper_bound": "pineapple"}
    ]
  }
]'
----

# The partial index should be preferred over the non-partial index or PK.
opt
SELECT k FROM spatial WHERE st_intersects('LINESTRING(0.5 0.5, 0.7 0.7)', g) AND s IN ('apple', 'banana', 'cherry')
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=22.22222]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) g:2(geometry!null) s:3(string!null)
      ├── immutable
      ├── stats: [rows=22.22222, distinct(2)=7, null(2)=0, distinct(3)=2, null(3)=0]
      │   histogram(3)=  0   11.111   0   11.111
      │                <--- 'banana' --- 'cherry'
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── index-join spatial
      │    ├── columns: k:1(int!null) g:2(geometry) s:3(string)
      │    ├── stats: [rows=118.7568]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── inverted-filter
      │         ├── columns: k:1(int!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\x12\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_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool]
      │         ├── stats: [rows=118.7568]
      │         ├── key: (1)
      │         └── scan spatial@p,inverted,partial
      │              ├── columns: k:1(int!null) g_inverted_key:7(encodedkey!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\x12\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"]
      │              ├── stats: [rows=118.7568, distinct(1)=33.9305, null(1)=0, distinct(3)=2, null(3)=0, distinct(7)=1.18757, null(7)=0, distinct(3,7)=2.37514, null(3,7)=0]
      │              │   histogram(3)=  0   59.378   0   59.378
      │              │                <--- 'banana' --- 'cherry'
      │              │   histogram(7)=  0             0              1.8225e-10            100             18.757             0              0             0
      │              │                <--- '\x42fd1000000000000001' ------------ '\x42fd1000000100000000' -------- '\x42fd1200000000000000' --- '\x42fd1400000000000001'
      │              └── key: (1,7)
      └── filters
           └── st_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]

# The partial index should be preferred over the non-partial index or PK when
# there is an additional filter to apply on s.
opt
SELECT k FROM spatial WHERE st_intersects('LINESTRING(0.5 0.5, 0.7 0.7)', g) AND s = 'banana'
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=11.11111]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) g:2(geometry!null) s:3(string!null)
      ├── immutable
      ├── stats: [rows=11.11111, distinct(2)=7, null(2)=0, distinct(3)=1, null(3)=0]
      │   histogram(3)=  0   11.111
      │                <--- 'banana'
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(2)
      ├── index-join spatial
      │    ├── columns: k:1(int!null) g:2(geometry) s:3(string)
      │    ├── stats: [rows=118.7568]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── inverted-filter
      │         ├── columns: k:1(int!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\x12\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_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool]
      │         ├── stats: [rows=118.7568]
      │         ├── key: (1)
      │         └── scan spatial@p,inverted,partial
      │              ├── columns: k:1(int!null) g_inverted_key:7(encodedkey!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\x12\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"]
      │              ├── stats: [rows=118.7568, distinct(1)=33.9305, null(1)=0, distinct(3)=2, null(3)=0, distinct(7)=1.18757, null(7)=0, distinct(3,7)=2.37514, null(3,7)=0]
      │              │   histogram(3)=  0   59.378   0   59.378
      │              │                <--- 'banana' --- 'cherry'
      │              │   histogram(7)=  0             0              1.8225e-10            100             18.757             0              0             0
      │              │                <--- '\x42fd1000000000000001' ------------ '\x42fd1000000100000000' -------- '\x42fd1200000000000000' --- '\x42fd1400000000000001'
      │              └── key: (1,7)
      └── filters
           ├── st_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]
           └── s:3 = 'banana' [type=bool, outer=(3), constraints=(/3: [/'banana' - /'banana']; tight), fd=()-->(3)]

# Add null values for s.
exec-ddl
ALTER TABLE spatial INJECT STATISTICS '[
  {
    "columns": ["g"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 7,
    "null_count": 0,
    "avg_size": 12,
    "histo_col_type":"BYTES",
    "histo_buckets":[
      {"num_eq": 1000, "num_range": 0, "distinct_range": 0, "upper_bound": "\\x42fd0555555555555555"},
      {"num_eq": 1000, "num_range": 1000, "distinct_range": 1, "upper_bound": "\\x42fd0fffffff00000000"},
      {"num_eq": 1000, "num_range": 1000, "distinct_range": 1, "upper_bound": "\\x42fd1000000100000000"},
      {"num_eq": 1000, "num_range": 1000, "distinct_range": 1, "upper_bound": "\\x42fd1aaaaaab00000000"}
     ]
  },
  {
    "columns": ["s"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 2000,
    "distinct_count": 40,
    "null_count": 100,
    "avg_size": 18,
    "histo_col_type": "string",
    "histo_buckets": [
      {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "apple"},
      {"num_eq": 100, "num_range": 200, "distinct_range": 9, "upper_bound": "banana"},
      {"num_eq": 100, "num_range": 300, "distinct_range": 9, "upper_bound": "cherry"},
      {"num_eq": 200, "num_range": 400, "distinct_range": 9, "upper_bound": "mango"},
      {"num_eq": 200, "num_range": 400, "distinct_range": 9, "upper_bound": "pineapple"}
    ]
  }
]'
----

opt
SELECT k FROM spatial WHERE st_intersects('LINESTRING(0.5 0.5, 0.7 0.7)', g) AND s IN ('apple', 'banana', 'cherry')
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=22.22222]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) g:2(geometry!null) s:3(string!null)
      ├── immutable
      ├── stats: [rows=22.22222, distinct(2)=7, null(2)=0, distinct(3)=2, null(3)=0]
      │   histogram(3)=  0   11.111   0   11.111
      │                <--- 'banana' --- 'cherry'
      ├── key: (1)
      ├── fd: (1)-->(2,3)
      ├── index-join spatial
      │    ├── columns: k:1(int!null) g:2(geometry) s:3(string)
      │    ├── stats: [rows=121.5695]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── inverted-filter
      │         ├── columns: k:1(int!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\x12\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_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool]
      │         ├── stats: [rows=121.5695]
      │         ├── key: (1)
      │         └── scan spatial@p,inverted,partial
      │              ├── columns: k:1(int!null) g_inverted_key:7(encodedkey!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\x12\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"]
      │              ├── stats: [rows=121.5695, distinct(1)=34.7341, null(1)=0, distinct(3)=2, null(3)=0, distinct(7)=1.18757, null(7)=0, distinct(3,7)=2.37514, null(3,7)=0]
      │              │   histogram(3)=  0   60.785   0   60.785
      │              │                <--- 'banana' --- 'cherry'
      │              │   histogram(7)=  0             0              1.8657e-10           102.37           19.201             0              0             0
      │              │                <--- '\x42fd1000000000000001' ------------ '\x42fd1000000100000000' -------- '\x42fd1200000000000000' --- '\x42fd1400000000000001'
      │              └── key: (1,7)
      └── filters
           └── st_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]

opt
SELECT k FROM spatial WHERE st_intersects('LINESTRING(0.5 0.5, 0.7 0.7)', g) AND s = 'banana'
----
project
 ├── columns: k:1(int!null)
 ├── immutable
 ├── stats: [rows=11.11111]
 ├── key: (1)
 └── select
      ├── columns: k:1(int!null) g:2(geometry!null) s:3(string!null)
      ├── immutable
      ├── stats: [rows=11.11111, distinct(2)=7, null(2)=0, distinct(3)=1, null(3)=0]
      │   histogram(3)=  0   11.111
      │                <--- 'banana'
      ├── key: (1)
      ├── fd: ()-->(3), (1)-->(2)
      ├── index-join spatial
      │    ├── columns: k:1(int!null) g:2(geometry) s:3(string)
      │    ├── stats: [rows=121.5695]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,3)
      │    └── inverted-filter
      │         ├── columns: k:1(int!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\x12\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_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool]
      │         ├── stats: [rows=121.5695]
      │         ├── key: (1)
      │         └── scan spatial@p,inverted,partial
      │              ├── columns: k:1(int!null) g_inverted_key:7(encodedkey!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\x12\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"]
      │              ├── stats: [rows=121.5695, distinct(1)=34.7341, null(1)=0, distinct(3)=2, null(3)=0, distinct(7)=1.18757, null(7)=0, distinct(3,7)=2.37514, null(3,7)=0]
      │              │   histogram(3)=  0   60.785   0   60.785
      │              │                <--- 'banana' --- 'cherry'
      │              │   histogram(7)=  0             0              1.8657e-10           102.37           19.201             0              0             0
      │              │                <--- '\x42fd1000000000000001' ------------ '\x42fd1000000100000000' -------- '\x42fd1200000000000000' --- '\x42fd1400000000000001'
      │              └── key: (1,7)
      └── filters
           ├── st_intersects('010200000002000000000000000000E03F000000000000E03F666666666666E63F666666666666E63F', g:2) [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]
           └── s:3 = 'banana' [type=bool, outer=(3), constraints=(/3: [/'banana' - /'banana']; tight), fd=()-->(3)]
