# --------------------------------------------------
# GenerateConstrainedScans + Computed Cols
# --------------------------------------------------

exec-ddl
CREATE TABLE t_int (
    k_int INT,
    c_int INT AS (k_int % 4) STORED,
    c_int_2 INT AS (k_int % 4) STORED,
    INDEX c_int_index (c_int, k_int)
)
----

exec-ddl
CREATE TABLE t_rand (
    k_int INT,
    c_int INT AS ((random()*100)::INT + k_int) STORED,
    INDEX c_int_index (c_int, k_int)
)
----

exec-ddl
CREATE TABLE t_mult (
    k_int INT,
    k_int_2 INT,
    c_int INT AS (k_int % 4) STORED,
    c_mult INT AS (c_mult_2 * c_int * k_int * k_int_2) STORED,
    c_mult_2 INT AS (k_int + 1) STORED,
    INDEX c_mult_index (c_mult, c_mult_2, c_int, k_int, k_int_2)
)
----

exec-ddl
CREATE TABLE hashed (
    k STRING,
    hash INT AS (fnv32(k) % 4) STORED CHECK (hash IN (0, 1, 2, 3)),
    INDEX (hash, k)
)
----

exec-ddl
CREATE TABLE composite_types (
    pk INT PRIMARY KEY,

    i INT,
    f FLOAT,
    d DECIMAL,

    cf FLOAT AS (f+1) STORED,
    cif FLOAT AS (i::FLOAT) VIRTUAL,
    cd DECIMAL AS (d+1) VIRTUAL,
    cs STRING AS (d::STRING) STORED,

    INDEX cf_idx (cf),
    INDEX cif_idx (cif),
    INDEX cd_idx (cd),
    INDEX cs_idx (cs)
)
----

# Constrain the index using computed column. Ensure that another computed column
# depending on the same base column isn't included as a filter (c_int_2).
opt
SELECT k_int FROM t_int WHERE k_int = 5
----
scan t_int@c_int_index
 ├── columns: k_int:1!null
 ├── constraint: /2/1/4: [/1/5 - /1/5]
 └── fd: ()-->(1)

# Use index with multiple computed columns, based on multiple input columns in
# acyclic graph.
opt
SELECT k_int, k_int_2, c_mult, c_mult_2, c_int FROM t_mult WHERE k_int = 5 AND k_int_2 = 10
----
scan t_mult@c_mult_index
 ├── columns: k_int:1!null k_int_2:2!null c_mult:4 c_mult_2:5 c_int:3
 ├── constraint: /4/5/3/1/2/6: [/300/6/1/5/10 - /300/6/1/5/10]
 └── fd: ()-->(1-5)

# Test computed + check columns in same table.
opt
SELECT * FROM hashed WHERE k = 'andy'
----
scan hashed@hashed_hash_k_idx
 ├── columns: k:1!null hash:2
 ├── constraint: /2/1/3: [/1/'andy' - /1/'andy']
 └── fd: ()-->(1,2)

# Don't constrain when filter has multiple columns.
opt
SELECT k_int FROM t_mult WHERE (k_int, k_int_2) > (1, 2)
----
project
 ├── columns: k_int:1!null
 ├── immutable
 └── select
      ├── columns: k_int:1!null k_int_2:2
      ├── immutable
      ├── scan t_mult
      │    ├── columns: k_int:1 k_int_2:2
      │    └── computed column expressions
      │         ├── c_int:3
      │         │    └── k_int:1 % 4
      │         ├── c_mult:4
      │         │    └── k_int_2:2 * (k_int:1 * (c_mult_2:5 * c_int:3))
      │         └── c_mult_2:5
      │              └── k_int:1 + 1
      └── filters
           └── (k_int:1, k_int_2:2) > (1, 2) [outer=(1,2), immutable, constraints=(/1/2: [/1/3 - ]; tight)]

# Don't constrain when filter has multiple spans.
opt
SELECT k_int FROM t_mult WHERE k_int = 2 OR k_int = 3
----
select
 ├── columns: k_int:1!null
 ├── scan t_mult
 │    ├── columns: k_int:1
 │    └── computed column expressions
 │         ├── c_int:3
 │         │    └── k_int:1 % 4
 │         ├── c_mult:4
 │         │    └── k_int_2:2 * (k_int:1 * (c_mult_2:5 * c_int:3))
 │         └── c_mult_2:5
 │              └── k_int:1 + 1
 └── filters
      └── (k_int:1 = 2) OR (k_int:1 = 3) [outer=(1), constraints=(/1: [/2 - /2] [/3 - /3]; tight)]

# Constrain the index for a NULL value.
opt
SELECT k_int FROM t_int WHERE k_int IS NULL
----
scan t_int@c_int_index
 ├── columns: k_int:1
 ├── constraint: /2/1/4: [/NULL/NULL - /NULL/NULL]
 └── fd: ()-->(1)


# Don't constrain the index when the computed column has a volatile function.
opt
SELECT k_int FROM t_rand WHERE k_int = 5.0
----
select
 ├── columns: k_int:1!null
 ├── fd: ()-->(1)
 ├── scan t_rand
 │    └── columns: k_int:1
 └── filters
      └── k_int:1 = 5 [outer=(1), constraints=(/1: [/5 - /5]; tight), fd=()-->(1)]

# Verify that a stored NULL value is handled correctly (#44132).
exec-ddl
CREATE TABLE null_col (
    a INT,
    b INT AS (NULL) STORED,
    INDEX ab (a, b)
)
----

opt
SELECT a, b FROM null_col WHERE a = 1
----
scan null_col@ab
 ├── columns: a:1!null b:2
 ├── constraint: /1/2/3: [/1/NULL - /1/NULL]
 └── fd: ()-->(1,2)

# We should be able to infer the value of cf, because the expression is not
# composite-sensitive.
opt
SELECT pk FROM composite_types@cf_idx WHERE f=1
----
project
 ├── columns: pk:1!null
 ├── key: (1)
 └── select
      ├── columns: pk:1!null f:3!null
      ├── key: (1)
      ├── fd: ()-->(3)
      ├── index-join composite_types
      │    ├── columns: pk:1!null f:3
      │    ├── key: (1)
      │    ├── fd: (1)-->(3)
      │    └── scan composite_types@cf_idx
      │         ├── columns: pk:1!null
      │         ├── constraint: /5/1: [/2.0 - /2.0]
      │         ├── flags: force-index=cf_idx
      │         └── key: (1)
      └── filters
           └── f:3 = 1.0 [outer=(3), constraints=(/3: [/1.0 - /1.0]; tight), fd=()-->(3)]

# We should be able to infer the value of cif, because the expression is not
# composite-sensitive (it does not depend on composite values).
opt
SELECT pk FROM composite_types@cif_idx WHERE i=1
----
project
 ├── columns: pk:1!null
 ├── key: (1)
 └── select
      ├── columns: pk:1!null i:2!null
      ├── key: (1)
      ├── fd: ()-->(2)
      ├── index-join composite_types
      │    ├── columns: pk:1!null i:2
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    └── scan composite_types@cif_idx
      │         ├── columns: pk:1!null
      │         ├── constraint: /6/1: [/1.0 - /1.0]
      │         ├── flags: force-index=cif_idx
      │         └── key: (1)
      └── filters
           └── i:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]

# We should be able to infer the value of cd, because the expression is not
# composite-sensitive.
opt
SELECT pk FROM composite_types@cd_idx WHERE d=1
----
project
 ├── columns: pk:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: pk:1!null d:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── index-join composite_types
      │    ├── columns: pk:1!null d:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── scan composite_types@cd_idx
      │         ├── columns: pk:1!null
      │         ├── constraint: /7/1: [/2 - /2]
      │         ├── flags: force-index=cd_idx
      │         └── key: (1)
      └── filters
           └── d:4 = 1 [outer=(4), immutable, constraints=(/4: [/1 - /1]; tight), fd=()-->(4)]

# We should not be able to infer the value of cs because the expression is
# composite-sensitive.
opt
SELECT pk FROM composite_types@cs_idx WHERE d=1
----
project
 ├── columns: pk:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: pk:1!null d:4!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(4)
      ├── index-join composite_types
      │    ├── columns: pk:1!null d:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(4)
      │    └── scan composite_types@cs_idx
      │         ├── columns: pk:1!null
      │         ├── flags: force-index=cs_idx
      │         └── key: (1)
      └── filters
           └── d:4 = 1 [outer=(4), immutable, constraints=(/4: [/1 - /1]; tight), fd=()-->(4)]

# Regression test for #83390. The optimizer should constrain an expression index
# with an IS NULL expression.
exec-ddl
CREATE TABLE t83390 (
  k INT PRIMARY KEY,
  a INT,
  INDEX idx ((a IS NULL))
)
----

opt
SELECT * FROM t83390@idx WHERE a IS NULL
----
index-join t83390
 ├── columns: k:1!null a:2
 ├── key: (1)
 ├── fd: ()-->(2)
 └── scan t83390@idx
      ├── columns: k:1!null
      ├── constraint: /5/1: [/true - /true]
      ├── flags: force-index=idx
      └── key: (1)
