exec-ddl
CREATE TABLE a (x INT, y INT)
----

exec-ddl
CREATE TABLE kuv (k INT PRIMARY KEY, u FLOAT, v STRING)
----

# A tight contradiction constraint is built for a false filter.
build
SELECT * FROM a WHERE false
----
project
 ├── columns: x:1(int) y:2(int)
 ├── cardinality: [0 - 0]
 ├── prune: (1,2)
 └── select
      ├── columns: x:1(int) y:2(int) rowid:3(int!null) crdb_internal_mvcc_timestamp:4(decimal) tableoid:5(oid)
      ├── cardinality: [0 - 0]
      ├── key: (3)
      ├── fd: (3)-->(1,2,4,5)
      ├── prune: (1-5)
      ├── interesting orderings: (+3)
      ├── scan a
      │    ├── columns: x:1(int) y:2(int) rowid:3(int!null) crdb_internal_mvcc_timestamp:4(decimal) tableoid:5(oid)
      │    ├── key: (3)
      │    ├── fd: (3)-->(1,2,4,5)
      │    ├── prune: (1-5)
      │    └── interesting orderings: (+3)
      └── filters
           └── false [type=bool, constraints=(contradiction; tight)]

opt
SELECT * FROM a WHERE x > 1
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)]
           ├── variable: x:1 [type=int]
           └── const: 1 [type=int]

# Verify that 1 is determined to be constant (from the intersection of the
# constraints).
opt
SELECT * FROM a WHERE x > 0 AND x < 2
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── fd: ()-->(1)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── range [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]
           └── and [type=bool]
                ├── gt [type=bool]
                │    ├── variable: x:1 [type=int]
                │    └── const: 0 [type=int]
                └── lt [type=bool]
                     ├── variable: x:1 [type=int]
                     └── const: 2 [type=int]

opt
SELECT * FROM a WHERE x >= 1
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── ge [type=bool, outer=(1), constraints=(/1: [/1 - ]; tight)]
           ├── variable: x:1 [type=int]
           └── const: 1 [type=int]

opt
SELECT * FROM a WHERE x < 1
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── lt [type=bool, outer=(1), constraints=(/1: (/NULL - /0]; tight)]
           ├── variable: x:1 [type=int]
           └── const: 1 [type=int]

opt
SELECT * FROM a WHERE x <= 1
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── le [type=bool, outer=(1), constraints=(/1: (/NULL - /1]; tight)]
           ├── variable: x:1 [type=int]
           └── const: 1 [type=int]

opt
SELECT * FROM a WHERE x = 1
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── fd: ()-->(1)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]
           ├── variable: x:1 [type=int]
           └── const: 1 [type=int]

opt
SELECT * FROM a WHERE x > 1 AND x < 5
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── range [type=bool, outer=(1), constraints=(/1: [/2 - /4]; tight)]
           └── and [type=bool]
                ├── gt [type=bool]
                │    ├── variable: x:1 [type=int]
                │    └── const: 1 [type=int]
                └── lt [type=bool]
                     ├── variable: x:1 [type=int]
                     └── const: 5 [type=int]

opt
SELECT * FROM a WHERE x = 1 AND y = 5
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── fd: ()-->(1,2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]
      │    ├── variable: x:1 [type=int]
      │    └── const: 1 [type=int]
      └── eq [type=bool, outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)]
           ├── variable: y:2 [type=int]
           └── const: 5 [type=int]

opt
SELECT * FROM a WHERE x > 1 AND x < 5 AND y >= 7 AND y <= 9
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      ├── range [type=bool, outer=(1), constraints=(/1: [/2 - /4]; tight)]
      │    └── and [type=bool]
      │         ├── gt [type=bool]
      │         │    ├── variable: x:1 [type=int]
      │         │    └── const: 1 [type=int]
      │         └── lt [type=bool]
      │              ├── variable: x:1 [type=int]
      │              └── const: 5 [type=int]
      └── range [type=bool, outer=(2), constraints=(/2: [/7 - /9]; tight)]
           └── and [type=bool]
                ├── ge [type=bool]
                │    ├── variable: y:2 [type=int]
                │    └── const: 7 [type=int]
                └── le [type=bool]
                     ├── variable: y:2 [type=int]
                     └── const: 9 [type=int]

# Verify the resulting constraints are not tight.
opt
SELECT * FROM a WHERE x > 1 AND x < 5 AND x + y = 5
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      ├── range [type=bool, outer=(1), constraints=(/1: [/2 - /4]; tight)]
      │    └── and [type=bool]
      │         ├── gt [type=bool]
      │         │    ├── variable: x:1 [type=int]
      │         │    └── const: 1 [type=int]
      │         └── lt [type=bool]
      │              ├── variable: x:1 [type=int]
      │              └── const: 5 [type=int]
      └── eq [type=bool, outer=(1,2), immutable]
           ├── plus [type=int]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── const: 5 [type=int]

opt
SELECT * FROM a WHERE x > 1 AND x + y >= 5 AND x + y <= 7
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      ├── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)]
      │    ├── variable: x:1 [type=int]
      │    └── const: 1 [type=int]
      ├── ge [type=bool, outer=(1,2), immutable]
      │    ├── plus [type=int]
      │    │    ├── variable: x:1 [type=int]
      │    │    └── variable: y:2 [type=int]
      │    └── const: 5 [type=int]
      └── le [type=bool, outer=(1,2), immutable]
           ├── plus [type=int]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── const: 7 [type=int]

# Verify that we ignore some mixed-type comparisons.
opt
SELECT * FROM a WHERE x > 1.5
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── gt [type=bool, outer=(1), constraints=(/1: (/NULL - ])]
           ├── variable: x:1 [type=int]
           └── const: 1.5 [type=decimal]

# This is a safe mixed-type comparison.
opt
SELECT * FROM kuv WHERE u > 1::INT
----
select
 ├── columns: k:1(int!null) u:2(float!null) v:3(string)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,3)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── gt [type=bool, outer=(2), constraints=(/2: [/1.0000000000000002 - ]; tight)]
           ├── variable: u:2 [type=float]
           └── const: 1.0 [type=float]

opt
SELECT * FROM kuv WHERE v <= 'foo' AND v >= 'bar'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── range [type=bool, outer=(3), constraints=(/3: [/'bar' - /'foo']; tight)]
           └── and [type=bool]
                ├── le [type=bool]
                │    ├── variable: v:3 [type=string]
                │    └── const: 'foo' [type=string]
                └── ge [type=bool]
                     ├── variable: v:3 [type=string]
                     └── const: 'bar' [type=string]

# Test IN.
opt
SELECT * FROM a WHERE x IN (1, 2, 3, NULL)
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── in [type=bool, outer=(1), constraints=(/1: [/1 - /1] [/2 - /2] [/3 - /3]; tight)]
           ├── variable: x:1 [type=int]
           └── tuple [type=tuple{unknown, int, int, int}]
                ├── null [type=unknown]
                ├── const: 1 [type=int]
                ├── const: 2 [type=int]
                └── const: 3 [type=int]

opt disable=SimplifyIsNullCondition
SELECT * FROM a WHERE rowid IS NULL
----
project
 ├── columns: x:1(int) y:2(int)
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1,2)
 ├── prune: (1,2)
 └── scan a
      ├── columns: x:1(int) y:2(int) rowid:3(int!null)
      ├── constraint: contradiction
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(1-3)
      └── prune: (1,2)

# Test IN in combination with another condition on the same column (which rules
# out some of the entries in the IN condition).
opt
SELECT * FROM a WHERE x IN (1, 3, 5, 7, 9) AND x > 6
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── range [type=bool, outer=(1), constraints=(/1: [/7 - /7] [/9 - /9]; tight)]
           └── and [type=bool]
                ├── in [type=bool]
                │    ├── variable: x:1 [type=int]
                │    └── tuple [type=tuple{int, int, int, int, int}]
                │         ├── const: 1 [type=int]
                │         ├── const: 3 [type=int]
                │         ├── const: 5 [type=int]
                │         ├── const: 7 [type=int]
                │         └── const: 9 [type=int]
                └── gt [type=bool]
                     ├── variable: x:1 [type=int]
                     └── const: 6 [type=int]

# Test IN in combination with a condition on another column.
opt
SELECT * FROM a WHERE x IN (1, 3) AND y > 4
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      ├── in [type=bool, outer=(1), constraints=(/1: [/1 - /1] [/3 - /3]; tight)]
      │    ├── variable: x:1 [type=int]
      │    └── tuple [type=tuple{int, int}]
      │         ├── const: 1 [type=int]
      │         └── const: 3 [type=int]
      └── gt [type=bool, outer=(2), constraints=(/2: [/5 - ]; tight)]
           ├── variable: y:2 [type=int]
           └── const: 4 [type=int]

# Test tuple inequality.
opt
SELECT * FROM a WHERE (x, y) > (1, 2)
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── gt [type=bool, outer=(1,2), immutable, constraints=(/1/2: [/1/3 - ]; tight)]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── tuple [type=tuple{int, int}]
                ├── const: 1 [type=int]
                └── const: 2 [type=int]

opt
SELECT * FROM a WHERE (x, y) >= (1, 2)
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── ge [type=bool, outer=(1,2), immutable, constraints=(/1/2: [/1/2 - ]; tight)]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── tuple [type=tuple{int, int}]
                ├── const: 1 [type=int]
                └── const: 2 [type=int]

opt
SELECT * FROM a WHERE (x, y) < (1, 2)
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── lt [type=bool, outer=(1,2), immutable, constraints=(/1/2: (/NULL - /1/1]; tight)]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── tuple [type=tuple{int, int}]
                ├── const: 1 [type=int]
                └── const: 2 [type=int]

opt
SELECT * FROM a WHERE (x, y) <= (1, 2)
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── le [type=bool, outer=(1,2), immutable, constraints=(/1/2: (/NULL - /1/2]; tight)]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── tuple [type=tuple{int, int}]
                ├── const: 1 [type=int]
                └── const: 2 [type=int]

# Test that we ignore tuple inequalities when the types don't match up.
opt
SELECT * FROM a WHERE (x, y) >= (1, 2.5)
----
select
 ├── columns: x:1(int) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── ge [type=bool, outer=(1,2), immutable]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── tuple [type=tuple{int, decimal}]
                ├── const: 1 [type=int]
                └── const: 2.5 [type=decimal]

# Test that we ignore tuple inequalities when they contain NULLs.
opt
SELECT * FROM a WHERE (x, y) >= (1, NULL)
----
select
 ├── columns: x:1(int) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── ge [type=bool, outer=(1,2), immutable]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── tuple [type=tuple{int, unknown}]
                ├── const: 1 [type=int]
                └── null [type=unknown]

# Test that we ignore tuple inequalities when we have something other than
# simple variables in the left tuple.
opt
SELECT * FROM a WHERE (x, 1) >= (1, 2)
----
select
 ├── columns: x:1(int) y:2(int)
 ├── immutable
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── ge [type=bool, outer=(1), immutable]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: x:1 [type=int]
           │    └── const: 1 [type=int]
           └── tuple [type=tuple{int, int}]
                ├── const: 1 [type=int]
                └── const: 2 [type=int]

exec-ddl
CREATE TABLE abc (a INT, b BOOL, c STRING)
----

opt
SELECT * FROM abc WHERE a != 5
----
select
 ├── columns: a:1(int!null) b:2(bool) c:3(string)
 ├── prune: (2,3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── ne [type=bool, outer=(1), constraints=(/1: (/NULL - /4] [/6 - ]; tight)]
           ├── variable: a:1 [type=int]
           └── const: 5 [type=int]

opt
SELECT * FROM abc WHERE a IS DISTINCT FROM 5
----
select
 ├── columns: a:1(int) b:2(bool) c:3(string)
 ├── prune: (2,3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── is-not [type=bool, outer=(1), constraints=(/1: [ - /4] [/6 - ]; tight)]
           ├── variable: a:1 [type=int]
           └── const: 5 [type=int]

opt
SELECT * FROM abc WHERE b != true
----
select
 ├── columns: a:1(int) b:2(bool!null) c:3(string)
 ├── fd: ()-->(2)
 ├── prune: (1,3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── not [type=bool, outer=(2), constraints=(/2: [/false - /false]; tight), fd=()-->(2)]
           └── variable: b:2 [type=bool]

opt
SELECT * FROM abc WHERE b != false
----
select
 ├── columns: a:1(int) b:2(bool!null) c:3(string)
 ├── fd: ()-->(2)
 ├── prune: (1,3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── variable: b:2 [type=bool, outer=(2), constraints=(/2: [/true - /true]; tight), fd=()-->(2)]

opt
SELECT * FROM abc WHERE b IS NOT true
----
select
 ├── columns: a:1(int) b:2(bool) c:3(string)
 ├── prune: (1,3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── is-not [type=bool, outer=(2), constraints=(/2: [ - /false]; tight)]
           ├── variable: b:2 [type=bool]
           └── true [type=bool]

opt
SELECT * FROM abc WHERE b IS NOT false
----
select
 ├── columns: a:1(int) b:2(bool) c:3(string)
 ├── prune: (1,3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── is-not [type=bool, outer=(2), constraints=(/2: [ - /false) [/true - ]; tight)]
           ├── variable: b:2 [type=bool]
           └── false [type=bool]

opt
SELECT * FROM abc WHERE b
----
select
 ├── columns: a:1(int) b:2(bool!null) c:3(string)
 ├── fd: ()-->(2)
 ├── prune: (1,3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── variable: b:2 [type=bool, outer=(2), constraints=(/2: [/true - /true]; tight), fd=()-->(2)]

opt
SELECT * FROM abc WHERE NOT b
----
select
 ├── columns: a:1(int) b:2(bool!null) c:3(string)
 ├── fd: ()-->(2)
 ├── prune: (1,3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── not [type=bool, outer=(2), constraints=(/2: [/false - /false]; tight), fd=()-->(2)]
           └── variable: b:2 [type=bool]

opt
SELECT * FROM abc WHERE a > 5 AND b
----
select
 ├── columns: a:1(int!null) b:2(bool!null) c:3(string)
 ├── fd: ()-->(2)
 ├── prune: (3)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      ├── gt [type=bool, outer=(1), constraints=(/1: [/6 - ]; tight)]
      │    ├── variable: a:1 [type=int]
      │    └── const: 5 [type=int]
      └── variable: b:2 [type=bool, outer=(2), constraints=(/2: [/true - /true]; tight), fd=()-->(2)]

opt
SELECT * FROM abc WHERE c != 'foo'
----
select
 ├── columns: a:1(int) b:2(bool) c:3(string!null)
 ├── prune: (1,2)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── ne [type=bool, outer=(3), constraints=(/3: (/NULL - /'foo') [/e'foo\x00' - ]; tight)]
           ├── variable: c:3 [type=string]
           └── const: 'foo' [type=string]

opt
SELECT * FROM abc WHERE c IS DISTINCT FROM 'foo'
----
select
 ├── columns: a:1(int) b:2(bool) c:3(string)
 ├── prune: (1,2)
 ├── scan abc
 │    ├── columns: a:1(int) b:2(bool) c:3(string)
 │    └── prune: (1-3)
 └── filters
      └── is-not [type=bool, outer=(3), constraints=(/3: [ - /'foo') [/e'foo\x00' - ]; tight)]
           ├── variable: c:3 [type=string]
           └── const: 'foo' [type=string]

opt
SELECT * FROM (SELECT (x, y) AS col FROM a) WHERE col > (1, 2)
----
select
 ├── columns: col:6(tuple{int, int}!null)
 ├── immutable
 ├── project
 │    ├── columns: col:6(tuple{int, int})
 │    ├── prune: (6)
 │    ├── scan a
 │    │    ├── columns: x:1(int) y:2(int)
 │    │    └── prune: (1,2)
 │    └── projections
 │         └── tuple [as=col:6, type=tuple{int, int}, outer=(1,2)]
 │              ├── variable: x:1 [type=int]
 │              └── variable: y:2 [type=int]
 └── filters
      └── gt [type=bool, outer=(6), immutable, constraints=(/6: [/(1, 3) - ]; tight)]
           ├── variable: col:6 [type=tuple{int, int}]
           └── tuple [type=tuple{int, int}]
                ├── const: 1 [type=int]
                └── const: 2 [type=int]

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

opt
SELECT * FROM c WHERE (v, u) IN ((1, 2), (3, 50), (5, 100))
----
scan c@v
 ├── columns: k:1(int!null) u:2(int!null) v:3(int!null)
 ├── constraint: /3/2/1
 │    ├── [/1/2 - /1/2]
 │    ├── [/3/50 - /3/50]
 │    └── [/5/100 - /5/100]
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1)
 └── interesting orderings: (+1) (+3,+2,+1)

opt format=hide-qual
SELECT * FROM c WHERE (v, u) IN ((1, 2), (1, 3), (1, 4))
----
scan c@v
 ├── columns: k:1(int!null) u:2(int!null) v:3(int!null)
 ├── constraint: /3/2/1: [/1/2 - /1/4]
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1)
 └── interesting orderings: (+1 opt(3)) (+2,+1 opt(3))

opt format=hide-qual
SELECT * FROM c WHERE (v, u) IN ((1, 2), (3, 2), (5, 2))
----
scan c@v
 ├── columns: k:1(int!null) u:2(int!null) v:3(int!null)
 ├── constraint: /3/2/1
 │    ├── [/1/2 - /1/2]
 │    ├── [/3/2 - /3/2]
 │    └── [/5/2 - /5/2]
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3)
 ├── prune: (1)
 └── interesting orderings: (+1 opt(2)) (+3,+1 opt(2))

opt format=hide-qual
SELECT * FROM c WHERE (v, u) IN ((1, 2), (1, 2), (1, 2))
----
scan c@v
 ├── columns: k:1(int!null) u:2(int!null) v:3(int!null)
 ├── constraint: /3/2/1: [/1/2 - /1/2]
 ├── key: (1)
 ├── fd: ()-->(2,3)
 ├── prune: (1)
 └── interesting orderings: (+1 opt(2,3))

opt format=hide-qual
SELECT * FROM c WHERE (v, u) IN ((1, 2), (1, 3), (1, 4))
----
scan c@v
 ├── columns: k:1(int!null) u:2(int!null) v:3(int!null)
 ├── constraint: /3/2/1: [/1/2 - /1/4]
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1)
 └── interesting orderings: (+1 opt(3)) (+2,+1 opt(3))

opt format=hide-qual
SELECT * FROM c WHERE (v, u) IN ((1, 2), (3, 2), (5, 2))
----
scan c@v
 ├── columns: k:1(int!null) u:2(int!null) v:3(int!null)
 ├── constraint: /3/2/1
 │    ├── [/1/2 - /1/2]
 │    ├── [/3/2 - /3/2]
 │    └── [/5/2 - /5/2]
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3)
 ├── prune: (1)
 └── interesting orderings: (+1 opt(2)) (+3,+1 opt(2))

opt format=hide-qual
SELECT * FROM c WHERE (v, u) IN ((1, 2), (1, 2), (1, 2))
----
scan c@v
 ├── columns: k:1(int!null) u:2(int!null) v:3(int!null)
 ├── constraint: /3/2/1: [/1/2 - /1/2]
 ├── key: (1)
 ├── fd: ()-->(2,3)
 ├── prune: (1)
 └── interesting orderings: (+1 opt(2,3))

# A tuple with NULL in it can't match anything, so it should be excluded from the constraints.
opt
SELECT * FROM c WHERE (v, u) IN ((1, 2), (3, 50), (5, NULL))
----
scan c@v
 ├── columns: k:1(int!null) u:2(int!null) v:3(int!null)
 ├── constraint: /3/2/1
 │    ├── [/1/2 - /1/2]
 │    └── [/3/50 - /3/50]
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1)
 └── interesting orderings: (+1) (+3,+2,+1)

# TODO(justin): ideally we would be normalizing away the 2 on the LHS here to
# get v = 1 and tight spans.
opt
SELECT * FROM c WHERE (v, 2) IN ((1, 2), (3, 50), (5, 100))
----
select
 ├── columns: k:1(int!null) u:2(int) v:3(int!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1) (+3,+2,+1)
 ├── scan c@v
 │    ├── columns: k:1(int!null) u:2(int) v:3(int!null)
 │    ├── constraint: /3/2/1
 │    │    ├── [/1 - /1]
 │    │    ├── [/3 - /3]
 │    │    └── [/5 - /5]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+3,+2,+1)
 └── filters
      └── in [type=bool, outer=(3), constraints=(/3: [/1 - /1] [/3 - /3] [/5 - /5])]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: v:3 [type=int]
           │    └── const: 2 [type=int]
           └── tuple [type=tuple{tuple{int, int}, tuple{int, int}, tuple{int, int}}]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 1 [type=int]
                │    └── const: 2 [type=int]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 3 [type=int]
                │    └── const: 50 [type=int]
                └── tuple [type=tuple{int, int}]
                     ├── const: 5 [type=int]
                     └── const: 100 [type=int]

# TODO(justin): in a perfect world we would be able to somehow transform this
# filter to (v, u) IN ((1, 1), (3, 47), (5, 95)) in order to get tight spans.
# This could be achieved via row-reduction.
opt
SELECT * FROM c WHERE (v, u + v) IN ((1, 2), (3, 50), (5, 100))
----
select
 ├── columns: k:1(int!null) u:2(int) v:3(int!null)
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1)
 ├── interesting orderings: (+1) (+3,+2,+1)
 ├── scan c@v
 │    ├── columns: k:1(int!null) u:2(int) v:3(int!null)
 │    ├── constraint: /3/2/1
 │    │    ├── [/1 - /1]
 │    │    ├── [/3 - /3]
 │    │    └── [/5 - /5]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+3,+2,+1)
 └── filters
      └── in [type=bool, outer=(2,3), immutable, constraints=(/3: [/1 - /1] [/3 - /3] [/5 - /5])]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: v:3 [type=int]
           │    └── plus [type=int]
           │         ├── variable: u:2 [type=int]
           │         └── variable: v:3 [type=int]
           └── tuple [type=tuple{tuple{int, int}, tuple{int, int}, tuple{int, int}}]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 1 [type=int]
                │    └── const: 2 [type=int]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 3 [type=int]
                │    └── const: 50 [type=int]
                └── tuple [type=tuple{int, int}]
                     ├── const: 5 [type=int]
                     └── const: 100 [type=int]

opt
SELECT * FROM c WHERE (v, u) IN ((1, 2), (k, 50), (5, 100))
----
select
 ├── columns: k:1(int!null) u:2(int!null) v:3(int)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── interesting orderings: (+1) (+3,+2,+1)
 ├── scan c
 │    ├── columns: k:1(int!null) u:2(int) v:3(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1) (+3,+2,+1)
 └── filters
      └── in [type=bool, outer=(1-3), constraints=(/2: [/2 - /2] [/50 - /50] [/100 - /100])]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: v:3 [type=int]
           │    └── variable: u:2 [type=int]
           └── tuple [type=tuple{tuple{int, int}, tuple{int, int}, tuple{int, int}}]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 1 [type=int]
                │    └── const: 2 [type=int]
                ├── tuple [type=tuple{int, int}]
                │    ├── variable: k:1 [type=int]
                │    └── const: 50 [type=int]
                └── tuple [type=tuple{int, int}]
                     ├── const: 5 [type=int]
                     └── const: 100 [type=int]

exec-ddl
CREATE TABLE d
(
    k INT PRIMARY KEY,
    p INT,
    q INT
)
----

opt format=hide-qual
SELECT * FROM d WHERE (p, q) IN ((1, 2), (1, 3), (1, 4))
----
select
 ├── columns: k:1(int!null) p:2(int!null) q:3(int!null)
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3)
 ├── prune: (1)
 ├── interesting orderings: (+1 opt(2))
 ├── scan d
 │    ├── columns: k:1(int!null) p:2(int) q:3(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── in [type=bool, outer=(2,3), constraints=(/2/3: [/1/2 - /1/2] [/1/3 - /1/3] [/1/4 - /1/4]; /3: [/2 - /2] [/3 - /3] [/4 - /4]; tight), fd=()-->(2)]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: p:2 [type=int]
           │    └── variable: q:3 [type=int]
           └── tuple [type=tuple{tuple{int, int}, tuple{int, int}, tuple{int, int}}]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 1 [type=int]
                │    └── const: 2 [type=int]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 1 [type=int]
                │    └── const: 3 [type=int]
                └── tuple [type=tuple{int, int}]
                     ├── const: 1 [type=int]
                     └── const: 4 [type=int]

opt format=hide-qual
SELECT * FROM d WHERE (p, q) IN ((2, 1), (3, 1), (4, 1))
----
select
 ├── columns: k:1(int!null) p:2(int!null) q:3(int!null)
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1)
 ├── interesting orderings: (+1 opt(3))
 ├── scan d
 │    ├── columns: k:1(int!null) p:2(int) q:3(int)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── in [type=bool, outer=(2,3), constraints=(/2/3: [/2/1 - /2/1] [/3/1 - /3/1] [/4/1 - /4/1]; /3: [/1 - /1]; tight), fd=()-->(3)]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: p:2 [type=int]
           │    └── variable: q:3 [type=int]
           └── tuple [type=tuple{tuple{int, int}, tuple{int, int}, tuple{int, int}}]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 2 [type=int]
                │    └── const: 1 [type=int]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 3 [type=int]
                │    └── const: 1 [type=int]
                └── tuple [type=tuple{int, int}]
                     ├── const: 4 [type=int]
                     └── const: 1 [type=int]

exec-ddl
CREATE TABLE e
(
    k INT PRIMARY KEY,
    t TIMESTAMP,
    d TIMESTAMP,
    INDEX (t),
    INDEX (d)
)
----

opt
SELECT k FROM e WHERE d > '2018-07-01' AND d < '2018-07-01'::DATE + '1w'::INTERVAL
----
project
 ├── columns: k:1(int!null)
 ├── key: (1)
 ├── prune: (1)
 ├── interesting orderings: (+1)
 └── scan e@e_d_idx
      ├── columns: k:1(int!null) d:3(timestamp!null)
      ├── constraint: /3/1: [/'2018-07-01 00:00:00.000001' - /'2018-07-07 23:59:59.999999']
      ├── key: (1)
      ├── fd: (1)-->(3)
      ├── prune: (1)
      └── interesting orderings: (+1) (+3,+1)

# Verify constraints for tuple IN (tuple, ..), when the tuples are not sorted.
opt
SELECT * FROM (SELECT (x, y) AS foo FROM a) WHERE foo IN ((3, 4), (1, 2))
----
select
 ├── columns: foo:6(tuple{int, int}!null)
 ├── project
 │    ├── columns: foo:6(tuple{int, int})
 │    ├── prune: (6)
 │    ├── scan a
 │    │    ├── columns: x:1(int) y:2(int)
 │    │    └── prune: (1,2)
 │    └── projections
 │         └── tuple [as=foo:6, type=tuple{int, int}, outer=(1,2)]
 │              ├── variable: x:1 [type=int]
 │              └── variable: y:2 [type=int]
 └── filters
      └── in [type=bool, outer=(6), constraints=(/6: [/(1, 2) - /(1, 2)] [/(3, 4) - /(3, 4)]; tight)]
           ├── variable: foo:6 [type=tuple{int, int}]
           └── tuple [type=tuple{tuple{int, int}, tuple{int, int}}]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 3 [type=int]
                │    └── const: 4 [type=int]
                └── tuple [type=tuple{int, int}]
                     ├── const: 1 [type=int]
                     └── const: 2 [type=int]

# Regression test for #77364. Constraints should be built when an IN expression
# is normalized from = ANY.
opt
SELECT * FROM a WHERE (x, y) = ANY(ARRAY[(1, 10), (2, 20)])
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── in [type=bool, outer=(1,2), constraints=(/1/2: [/1/10 - /1/10] [/2/20 - /2/20]; /2: [/10 - /10] [/20 - /20]; tight)]
           ├── tuple [type=tuple{int, int}]
           │    ├── variable: x:1 [type=int]
           │    └── variable: y:2 [type=int]
           └── tuple [type=tuple{tuple{int, int}, tuple{int, int}}]
                ├── tuple [type=tuple{int, int}]
                │    ├── const: 1 [type=int]
                │    └── const: 10 [type=int]
                └── tuple [type=tuple{int, int}]
                     ├── const: 2 [type=int]
                     └── const: 20 [type=int]

# Tests for string operators (LIKE, SIMILAR TO).
opt
SELECT * FROM kuv WHERE v LIKE 'ABC%'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABD'); tight)]
           ├── variable: v:3 [type=string]
           └── const: 'ABC%' [type=string]

opt
SELECT * FROM kuv WHERE v LIKE '\ABC%'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABD'); tight)]
           ├── variable: v:3 [type=string]
           └── const: e'\\ABC%' [type=string]

# Like doesn't support RE syntax.
opt
SELECT * FROM kuv WHERE v LIKE 'ABC.*'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1,2)
 ├── interesting orderings: (+1 opt(3))
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: [/'ABC.*' - /'ABC.*']; tight), fd=()-->(3)]
           ├── variable: v:3 [type=string]
           └── const: 'ABC.*' [type=string]

opt
SELECT * FROM kuv WHERE v LIKE 'ABC_'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABD'))]
           ├── variable: v:3 [type=string]
           └── const: 'ABC_' [type=string]

opt
SELECT * FROM kuv WHERE v LIKE 'ABC%Z'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABD'))]
           ├── variable: v:3 [type=string]
           └── const: 'ABC%Z' [type=string]

opt
SELECT * FROM kuv WHERE v LIKE 'ABC'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1,2)
 ├── interesting orderings: (+1 opt(3))
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABC']; tight), fd=()-->(3)]
           ├── variable: v:3 [type=string]
           └── const: 'ABC' [type=string]

opt
SELECT * FROM kuv WHERE v LIKE '%'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: (/NULL - ])]
           ├── variable: v:3 [type=string]
           └── const: '%' [type=string]

opt
SELECT * FROM kuv WHERE v LIKE '%XY'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: (/NULL - ])]
           ├── variable: v:3 [type=string]
           └── const: '%XY' [type=string]

# Regression test for #124455. Tight constraints should be generated for LIKE
# expressions with an escaped backslash before a wildcard character.
opt
SELECT * FROM kuv WHERE v LIKE e'\\\\%'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── like [type=bool, outer=(3), constraints=(/3: [/e'\\' - /']'); tight)]
           ├── variable: v:3 [type=string]
           └── const: e'\\\\%' [type=string]

opt
SELECT * FROM kuv WHERE v SIMILAR TO 'ABC.*'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── similar-to [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABD'))]
           ├── variable: v:3 [type=string]
           └── const: 'ABC.*' [type=string]

opt
SELECT * FROM kuv WHERE v SIMILAR TO 'ABC.*Z'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── similar-to [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABD'))]
           ├── variable: v:3 [type=string]
           └── const: 'ABC.*Z' [type=string]

opt
SELECT * FROM kuv WHERE v SIMILAR TO 'ABC'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 ├── prune: (1,2)
 ├── interesting orderings: (+1 opt(3))
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── similar-to [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABC']; tight), fd=()-->(3)]
           ├── variable: v:3 [type=string]
           └── const: 'ABC' [type=string]

opt
SELECT * FROM kuv WHERE v SIMILAR TO '(ABC|ABCDEF).*'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── similar-to [type=bool, outer=(3), constraints=(/3: [/'ABC' - /'ABD'))]
           ├── variable: v:3 [type=string]
           └── const: '(ABC|ABCDEF).*' [type=string]

opt
SELECT * FROM kuv WHERE v SIMILAR TO '.*'
----
select
 ├── columns: k:1(int!null) u:2(float) v:3(string!null)
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan kuv
 │    ├── columns: k:1(int!null) u:2(float) v:3(string)
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── similar-to [type=bool, outer=(3), constraints=(/3: [/'' - ])]
           ├── variable: v:3 [type=string]
           └── const: '.*' [type=string]

# We can determine that the constraint set is tight when there is a single
# variable and tight constraints are combined with OR.
opt
SELECT * FROM a WHERE x <= 5 OR x = 10 OR x = 15
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── prune: (2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── or [type=bool, outer=(1), constraints=(/1: (/NULL - /5] [/10 - /10] [/15 - /15]; tight)]
           ├── or [type=bool]
           │    ├── le [type=bool]
           │    │    ├── variable: x:1 [type=int]
           │    │    └── const: 5 [type=int]
           │    └── eq [type=bool]
           │         ├── variable: x:1 [type=int]
           │         └── const: 10 [type=int]
           └── eq [type=bool]
                ├── variable: x:1 [type=int]
                └── const: 15 [type=int]

# The constraint set is also tight when each side has a single constraint with
# matching columns.
opt
SELECT * FROM a WHERE (x, y) < (1, 2) OR (x, y) > (3, 4)
----
select
 ├── columns: x:1(int!null) y:2(int)
 ├── immutable
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── or [type=bool, outer=(1,2), immutable, constraints=(/1/2: (/NULL - /1/1] [/3/5 - ]; tight)]
           ├── lt [type=bool]
           │    ├── tuple [type=tuple{int, int}]
           │    │    ├── variable: x:1 [type=int]
           │    │    └── variable: y:2 [type=int]
           │    └── tuple [type=tuple{int, int}]
           │         ├── const: 1 [type=int]
           │         └── const: 2 [type=int]
           └── gt [type=bool]
                ├── tuple [type=tuple{int, int}]
                │    ├── variable: x:1 [type=int]
                │    └── variable: y:2 [type=int]
                └── tuple [type=tuple{int, int}]
                     ├── const: 3 [type=int]
                     └── const: 4 [type=int]


# The constraint set is not tight if there are multiple constraints with
# different variables.
opt
SELECT * FROM a WHERE (x > 1 AND y > 10) OR (x < 5 AND y < 50)
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── or [type=bool, outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ])]
           ├── and [type=bool]
           │    ├── gt [type=bool]
           │    │    ├── variable: x:1 [type=int]
           │    │    └── const: 1 [type=int]
           │    └── gt [type=bool]
           │         ├── variable: y:2 [type=int]
           │         └── const: 10 [type=int]
           └── and [type=bool]
                ├── lt [type=bool]
                │    ├── variable: x:1 [type=int]
                │    └── const: 5 [type=int]
                └── lt [type=bool]
                     ├── variable: y:2 [type=int]
                     └── const: 50 [type=int]

# A union constraint set is tight if the left is a contradiction and the right
# is tight.
opt
SELECT * FROM a WHERE (x = 1 AND x = 3) OR (x = 10 AND y = 20)
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── fd: ()-->(1,2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── or [type=bool, outer=(1,2), constraints=(/1: [/10 - /10]; /2: [/20 - /20]; tight), fd=()-->(1,2)]
           ├── and [type=bool]
           │    ├── eq [type=bool]
           │    │    ├── variable: x:1 [type=int]
           │    │    └── const: 1 [type=int]
           │    └── eq [type=bool]
           │         ├── variable: x:1 [type=int]
           │         └── const: 3 [type=int]
           └── and [type=bool]
                ├── eq [type=bool]
                │    ├── variable: x:1 [type=int]
                │    └── const: 10 [type=int]
                └── eq [type=bool]
                     ├── variable: y:2 [type=int]
                     └── const: 20 [type=int]

# A union constraint set is tight if the right is a contradiction and the left
# is tight.
opt
SELECT * FROM a WHERE (x = 10 AND y = 20) OR (x = 1 AND x = 3)
----
select
 ├── columns: x:1(int!null) y:2(int!null)
 ├── fd: ()-->(1,2)
 ├── scan a
 │    ├── columns: x:1(int) y:2(int)
 │    └── prune: (1,2)
 └── filters
      └── or [type=bool, outer=(1,2), constraints=(/1: [/10 - /10]; /2: [/20 - /20]; tight), fd=()-->(1,2)]
           ├── and [type=bool]
           │    ├── eq [type=bool]
           │    │    ├── variable: x:1 [type=int]
           │    │    └── const: 10 [type=int]
           │    └── eq [type=bool]
           │         ├── variable: y:2 [type=int]
           │         └── const: 20 [type=int]
           └── and [type=bool]
                ├── eq [type=bool]
                │    ├── variable: x:1 [type=int]
                │    └── const: 1 [type=int]
                └── eq [type=bool]
                     ├── variable: x:1 [type=int]
                     └── const: 3 [type=int]

# Tests for the containment operator.

exec-ddl
CREATE TABLE f
(
    k INT PRIMARY KEY,
    j JSON,
    a INT[]
)
----

# Containment implies the column is not null.
opt
SELECT 1 FROM f WHERE j @> '{"x": "y"}'
----
project
 ├── columns: "?column?":6(int!null)
 ├── immutable
 ├── fd: ()-->(6)
 ├── prune: (6)
 ├── select
 │    ├── columns: j:2(jsonb!null)
 │    ├── immutable
 │    ├── scan f
 │    │    ├── columns: j:2(jsonb)
 │    │    └── prune: (2)
 │    └── filters
 │         └── contains [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])]
 │              ├── variable: j:2 [type=jsonb]
 │              └── const: '{"x": "y"}' [type=jsonb]
 └── projections
      └── const: 1 [as="?column?":6, type=int]

opt
SELECT 1 FROM f WHERE a @> ARRAY[1]
----
project
 ├── columns: "?column?":6(int!null)
 ├── immutable
 ├── fd: ()-->(6)
 ├── prune: (6)
 ├── select
 │    ├── columns: a:3(int[]!null)
 │    ├── immutable
 │    ├── scan f
 │    │    ├── columns: a:3(int[])
 │    │    └── prune: (3)
 │    └── filters
 │         └── contains [type=bool, outer=(3), immutable, constraints=(/3: (/NULL - ])]
 │              ├── variable: a:3 [type=int[]]
 │              └── const: ARRAY[1] [type=int[]]
 └── projections
      └── const: 1 [as="?column?":6, type=int]

opt
SELECT * from f where a @> ARRAY[NULL]::INT[]
----
select
 ├── columns: k:1(int!null) j:2(jsonb) a:3(int[])
 ├── cardinality: [0 - 0]
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 ├── prune: (1,2)
 ├── interesting orderings: (+1)
 ├── scan f
 │    ├── columns: k:1(int!null) j:2(jsonb) a:3(int[])
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,3)
 │    ├── prune: (1-3)
 │    └── interesting orderings: (+1)
 └── filters
      └── contains [type=bool, outer=(3), immutable, constraints=(contradiction; tight)]
           ├── variable: a:3 [type=int[]]
           └── const: ARRAY[NULL] [type=int[]]
