exec-ddl
CREATE TABLE t (
  k INT PRIMARY KEY,
  i INT,
  s STRING,
  b BOOL,
  t TIMESTAMPTZ,
  INDEX (i, s, b),
  INDEX (i, t),
  INDEX (t)
)
----

# --------------------------------------------------
# GenerateParameterizedJoin
# --------------------------------------------------

opt expect=GenerateParameterizedJoin
SELECT * FROM t WHERE k = $1
----
project
 ├── columns: k:1!null i:2 s:3 b:4 t:5
 ├── cardinality: [0 - 1]
 ├── has-placeholder
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2 s:3 b:4 t:5 "$1":8!null
      ├── flags: disallow merge join
      ├── key columns: [8] = [1]
      ├── lookup columns are key
      ├── cardinality: [0 - 1]
      ├── has-placeholder
      ├── key: ()
      ├── fd: ()-->(1-5,8), (1)==(8), (8)==(1)
      ├── values
      │    ├── columns: "$1":8
      │    ├── cardinality: [1 - 1]
      │    ├── has-placeholder
      │    ├── key: ()
      │    ├── fd: ()-->(8)
      │    └── ($1,)
      └── filters (true)

opt expect=GenerateParameterizedJoin
SELECT * FROM t WHERE k = $1::INT
----
project
 ├── columns: k:1!null i:2 s:3 b:4 t:5
 ├── cardinality: [0 - 1]
 ├── has-placeholder
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2 s:3 b:4 t:5 "$1":8!null
      ├── flags: disallow merge join
      ├── key columns: [8] = [1]
      ├── lookup columns are key
      ├── cardinality: [0 - 1]
      ├── has-placeholder
      ├── key: ()
      ├── fd: ()-->(1-5,8), (1)==(8), (8)==(1)
      ├── values
      │    ├── columns: "$1":8
      │    ├── cardinality: [1 - 1]
      │    ├── has-placeholder
      │    ├── key: ()
      │    ├── fd: ()-->(8)
      │    └── ($1,)
      └── filters (true)

opt expect=GenerateParameterizedJoin
SELECT * FROM t WHERE i = $1 AND s = $2 AND b = $3
----
project
 ├── columns: k:1!null i:2!null s:3!null b:4!null t:5
 ├── has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2-4), (1)-->(5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3!null b:4!null t:5 "$1":8!null "$2":9!null "$3":10!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2-4,8-10), (1)-->(5), (2)==(8), (8)==(2), (3)==(9), (9)==(3), (4)==(10), (10)==(4)
      ├── inner-join (lookup t@t_i_s_b_idx)
      │    ├── columns: k:1!null i:2!null s:3!null b:4!null "$1":8!null "$2":9!null "$3":10!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8 9 10] = [2 3 4]
      │    ├── has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(2-4,8-10), (2)==(8), (8)==(2), (3)==(9), (9)==(3), (4)==(10), (10)==(4)
      │    ├── values
      │    │    ├── columns: "$1":8 "$2":9 "$3":10
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8-10)
      │    │    └── ($1, $2, $3)
      │    └── filters (true)
      └── filters (true)

# A placeholder referenced multiple times in the filters should only appear once
# in the Values expression.
opt expect=GenerateParameterizedJoin
SELECT * FROM t WHERE k = $1 AND i = $1
----
project
 ├── columns: k:1!null i:2!null s:3 b:4 t:5
 ├── cardinality: [0 - 1]
 ├── has-placeholder
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3 b:4 t:5 "$1":8!null
      ├── flags: disallow merge join
      ├── key columns: [8] = [1]
      ├── lookup columns are key
      ├── cardinality: [0 - 1]
      ├── has-placeholder
      ├── key: ()
      ├── fd: ()-->(1-5,8), (1)==(2,8), (2)==(1,8), (8)==(1,2)
      ├── values
      │    ├── columns: "$1":8
      │    ├── cardinality: [1 - 1]
      │    ├── has-placeholder
      │    ├── key: ()
      │    ├── fd: ()-->(8)
      │    └── ($1,)
      └── filters
           └── k:1 = i:2 [outer=(1,2), constraints=(/1: (/NULL - ]; /2: (/NULL - ]), fd=(1)==(2), (2)==(1)]

# The generated join should not be reordered and merge joins should not be
# explored on it.
opt expect=GenerateParameterizedJoin expect-not=(ReorderJoins,GenerateMergeJoins)
SELECT * FROM t WHERE i = $1
----
project
 ├── columns: k:1!null i:2!null s:3 b:4 t:5
 ├── has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3-5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3 b:4 t:5 "$1":8!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2,8), (1)-->(3-5), (2)==(8), (8)==(2)
      ├── inner-join (lookup t@t_i_t_idx)
      │    ├── columns: k:1!null i:2!null t:5 "$1":8!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8] = [2]
      │    ├── has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(2,8), (1)-->(5), (2)==(8), (8)==(2)
      │    ├── values
      │    │    ├── columns: "$1":8
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8)
      │    │    └── ($1,)
      │    └── filters (true)
      └── filters (true)

opt expect=GenerateParameterizedJoin
SELECT * FROM t WHERE k = (SELECT i FROM t WHERE k = $1)
----
project
 ├── columns: k:1!null i:2 s:3 b:4 t:5
 ├── cardinality: [0 - 1]
 ├── has-placeholder
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2 s:3 b:4 t:5 k:8!null i:9!null
      ├── key columns: [9] = [1]
      ├── lookup columns are key
      ├── cardinality: [0 - 1]
      ├── has-placeholder
      ├── key: ()
      ├── fd: ()-->(1-5,8,9), (1)==(9), (9)==(1)
      ├── project
      │    ├── columns: k:8!null i:9
      │    ├── cardinality: [0 - 1]
      │    ├── has-placeholder
      │    ├── key: ()
      │    ├── fd: ()-->(8,9)
      │    └── inner-join (lookup t)
      │         ├── columns: k:8!null i:9 "$1":15!null
      │         ├── flags: disallow merge join
      │         ├── key columns: [15] = [8]
      │         ├── lookup columns are key
      │         ├── cardinality: [0 - 1]
      │         ├── has-placeholder
      │         ├── key: ()
      │         ├── fd: ()-->(8,9,15), (8)==(15), (15)==(8)
      │         ├── values
      │         │    ├── columns: "$1":15
      │         │    ├── cardinality: [1 - 1]
      │         │    ├── has-placeholder
      │         │    ├── key: ()
      │         │    ├── fd: ()-->(15)
      │         │    └── ($1,)
      │         └── filters (true)
      └── filters (true)

# TODO(mgartner): The rule doesn't apply because the filters do not reference
# the placeholder directly. Consider ways to handle cases like this.
opt
SELECT * FROM t WHERE k = (SELECT $1::INT)
----
project
 ├── columns: k:1!null i:2 s:3 b:4 t:5
 ├── cardinality: [0 - 1]
 ├── has-placeholder
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── select
      ├── columns: k:1!null i:2 s:3 b:4 t:5 int8:8!null
      ├── cardinality: [0 - 1]
      ├── has-placeholder
      ├── key: ()
      ├── fd: ()-->(1-5,8), (1)==(8), (8)==(1)
      ├── project
      │    ├── columns: int8:8 k:1!null i:2 s:3 b:4 t:5
      │    ├── has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(8), (1)-->(2-5)
      │    ├── scan t
      │    │    ├── columns: k:1!null i:2 s:3 b:4 t:5
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2-5)
      │    └── projections
      │         └── $1 [as=int8:8]
      └── filters
           └── k:1 = int8:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

exec-ddl
CREATE INDEX partial_idx ON t(t) WHERE t IS NOT NULL
----

opt expect=GenerateParameterizedJoin
SELECT * FROM t WHERE t = $1
----
project
 ├── columns: k:1!null i:2 s:3 b:4 t:5!null
 ├── has-placeholder
 ├── key: (1)
 ├── fd: ()-->(5), (1)-->(2-4)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2 s:3 b:4 t:5!null "$1":8!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── has-placeholder
      ├── key: (1)
      ├── fd: ()-->(5,8), (1)-->(2-4), (5)==(8), (8)==(5)
      ├── inner-join (lookup t@partial_idx,partial)
      │    ├── columns: k:1!null t:5!null "$1":8!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8] = [5]
      │    ├── has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(5,8), (5)==(8), (8)==(5)
      │    ├── values
      │    │    ├── columns: "$1":8
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8)
      │    │    └── ($1,)
      │    └── filters (true)
      └── filters (true)

exec-ddl
DROP INDEX partial_idx
----

exec-ddl
CREATE INDEX partial_idx ON t(i, t) WHERE i IS NOT NULL AND t IS NOT NULL
----

opt expect=GenerateParameterizedJoin
SELECT * FROM t WHERE i = $1 AND t = $2
----
project
 ├── columns: k:1!null i:2!null s:3 b:4 t:5!null
 ├── has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2,5), (1)-->(3,4)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3 b:4 t:5!null "$1":8!null "$2":9!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2,5,8,9), (1)-->(3,4), (2)==(8), (8)==(2), (5)==(9), (9)==(5)
      ├── inner-join (lookup t@partial_idx,partial)
      │    ├── columns: k:1!null i:2!null t:5!null "$1":8!null "$2":9!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8 9] = [2 5]
      │    ├── has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(2,5,8,9), (2)==(8), (8)==(2), (5)==(9), (9)==(5)
      │    ├── values
      │    │    ├── columns: "$1":8 "$2":9
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8,9)
      │    │    └── ($1, $2)
      │    └── filters (true)
      └── filters (true)

exec-ddl
DROP INDEX partial_idx
----

exec-ddl
CREATE INDEX partial_idx ON t(s) WHERE k = i
----

opt expect=GenerateParameterizedJoin
SELECT * FROM t@partial_idx WHERE s = $1 AND k = $2 AND i = $2
----
project
 ├── columns: k:1!null i:2!null s:3!null b:4 t:5
 ├── cardinality: [0 - 1]
 ├── has-placeholder
 ├── key: ()
 ├── fd: ()-->(1-5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3!null b:4 t:5 "$1":8!null "$2":9!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── cardinality: [0 - 1]
      ├── has-placeholder
      ├── key: ()
      ├── fd: ()-->(1-5,8,9), (1)==(2,9), (2)==(1,9), (9)==(1,2), (3)==(8), (8)==(3)
      ├── inner-join (lookup t@partial_idx,partial)
      │    ├── columns: k:1!null s:3!null "$1":8!null "$2":9!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8 9] = [3 1]
      │    ├── lookup columns are key
      │    ├── cardinality: [0 - 1]
      │    ├── has-placeholder
      │    ├── key: ()
      │    ├── fd: ()-->(1,3,8,9), (3)==(8), (8)==(3), (1)==(9), (9)==(1)
      │    ├── values
      │    │    ├── columns: "$1":8 "$2":9
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8,9)
      │    │    └── ($1, $2)
      │    └── filters (true)
      └── filters (true)

exec-ddl
DROP INDEX partial_idx
----

opt no-stable-folds expect=GenerateParameterizedJoin
SELECT * FROM t WHERE t = now()
----
project
 ├── columns: k:1!null i:2 s:3 b:4 t:5!null
 ├── stable
 ├── key: (1)
 ├── fd: ()-->(5), (1)-->(2-4)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2 s:3 b:4 t:5!null column8:8!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── stable
      ├── key: (1)
      ├── fd: ()-->(5,8), (1)-->(2-4), (5)==(8), (8)==(5)
      ├── inner-join (lookup t@t_t_idx)
      │    ├── columns: k:1!null t:5!null column8:8!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8] = [5]
      │    ├── stable
      │    ├── key: (1)
      │    ├── fd: ()-->(5,8), (5)==(8), (8)==(5)
      │    ├── values
      │    │    ├── columns: column8:8
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── stable
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8)
      │    │    └── (now(),)
      │    └── filters (true)
      └── filters (true)

opt no-stable-folds expect=GenerateParameterizedJoin
SELECT * FROM t WHERE i = $1 AND t = now()
----
project
 ├── columns: k:1!null i:2!null s:3 b:4 t:5!null
 ├── stable, has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2,5), (1)-->(3,4)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3 b:4 t:5!null "$1":8!null column9:9!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── stable, has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2,5,8,9), (1)-->(3,4), (2)==(8), (8)==(2), (5)==(9), (9)==(5)
      ├── inner-join (lookup t@t_i_t_idx)
      │    ├── columns: k:1!null i:2!null t:5!null "$1":8!null column9:9!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8 9] = [2 5]
      │    ├── stable, has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(2,5,8,9), (2)==(8), (8)==(2), (5)==(9), (9)==(5)
      │    ├── values
      │    │    ├── columns: "$1":8 column9:9
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── stable, has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8,9)
      │    │    └── ($1, now())
      │    └── filters (true)
      └── filters (true)

opt no-stable-folds expect=GenerateParameterizedJoin
SELECT * FROM t WHERE i = $1 AND t > now()
----
project
 ├── columns: k:1!null i:2!null s:3 b:4 t:5!null
 ├── stable, has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2), (1)-->(3-5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3 b:4 t:5!null "$1":8!null column9:9!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── stable, has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2,8,9), (1)-->(3-5), (2)==(8), (8)==(2)
      ├── inner-join (lookup t@t_i_t_idx)
      │    ├── columns: k:1!null i:2!null t:5!null "$1":8!null column9:9!null
      │    ├── flags: disallow merge join
      │    ├── lookup expression
      │    │    └── filters
      │    │         ├── t:5 > column9:9 [outer=(5,9), constraints=(/5: (/NULL - ]; /9: (/NULL - ])]
      │    │         └── "$1":8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
      │    ├── stable, has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(2,8,9), (1)-->(5), (2)==(8), (8)==(2)
      │    ├── values
      │    │    ├── columns: "$1":8 column9:9
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── stable, has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8,9)
      │    │    └── ($1, now())
      │    └── filters (true)
      └── filters (true)

opt no-stable-folds expect=GenerateParameterizedJoin
SELECT * FROM t WHERE i = $1 AND t = now() + $2
----
project
 ├── columns: k:1!null i:2!null s:3 b:4 t:5!null
 ├── stable, has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2,5), (1)-->(3,4)
 └── project
      ├── columns: k:1!null i:2!null s:3 b:4 t:5!null "$1":8!null column9:9 "$2":10
      ├── stable, has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2,5,8-10), (1)-->(3,4), (2)==(8), (8)==(2)
      └── inner-join (lookup t)
           ├── columns: k:1!null i:2!null s:3 b:4 t:5!null "$1":8!null column9:9 "$2":10 column11:11!null
           ├── key columns: [1] = [1]
           ├── lookup columns are key
           ├── stable, has-placeholder
           ├── key: (1)
           ├── fd: ()-->(2,5,8-11), (1)-->(3,4), (2)==(8), (8)==(2), (5)==(11), (11)==(5)
           ├── inner-join (lookup t@t_i_t_idx)
           │    ├── columns: k:1!null i:2!null t:5!null "$1":8!null column9:9 "$2":10 column11:11!null
           │    ├── flags: disallow merge join
           │    ├── key columns: [8 11] = [2 5]
           │    ├── stable, has-placeholder
           │    ├── key: (1)
           │    ├── fd: ()-->(2,5,8-11), (2)==(8), (8)==(2), (5)==(11), (11)==(5)
           │    ├── project
           │    │    ├── columns: column11:11 "$1":8 column9:9 "$2":10
           │    │    ├── cardinality: [1 - 1]
           │    │    ├── stable, has-placeholder
           │    │    ├── key: ()
           │    │    ├── fd: ()-->(8-11)
           │    │    ├── values
           │    │    │    ├── columns: "$1":8 column9:9 "$2":10
           │    │    │    ├── cardinality: [1 - 1]
           │    │    │    ├── stable, has-placeholder
           │    │    │    ├── key: ()
           │    │    │    ├── fd: ()-->(8-10)
           │    │    │    └── ($1, now(), $2)
           │    │    └── projections
           │    │         └── column9:9 + "$2":10 [as=column11:11, outer=(9,10), stable]
           │    └── filters (true)
           └── filters (true)

opt no-stable-folds expect=GenerateParameterizedJoin
SELECT * FROM t WHERE i = $1 AND t = now() + '1 hr'::INTERVAL
----
project
 ├── columns: k:1!null i:2!null s:3 b:4 t:5!null
 ├── stable, has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2,5), (1)-->(3,4)
 └── project
      ├── columns: k:1!null i:2!null s:3 b:4 t:5!null "$1":8!null column9:9
      ├── stable, has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2,5,8,9), (1)-->(3,4), (2)==(8), (8)==(2)
      └── inner-join (lookup t)
           ├── columns: k:1!null i:2!null s:3 b:4 t:5!null "$1":8!null column9:9 column10:10!null
           ├── key columns: [1] = [1]
           ├── lookup columns are key
           ├── stable, has-placeholder
           ├── key: (1)
           ├── fd: ()-->(2,5,8-10), (1)-->(3,4), (2)==(8), (8)==(2), (5)==(10), (10)==(5)
           ├── inner-join (lookup t@t_i_t_idx)
           │    ├── columns: k:1!null i:2!null t:5!null "$1":8!null column9:9 column10:10!null
           │    ├── flags: disallow merge join
           │    ├── key columns: [8 10] = [2 5]
           │    ├── stable, has-placeholder
           │    ├── key: (1)
           │    ├── fd: ()-->(2,5,8-10), (2)==(8), (8)==(2), (5)==(10), (10)==(5)
           │    ├── project
           │    │    ├── columns: column10:10 "$1":8 column9:9
           │    │    ├── cardinality: [1 - 1]
           │    │    ├── stable, has-placeholder
           │    │    ├── key: ()
           │    │    ├── fd: ()-->(8-10)
           │    │    ├── values
           │    │    │    ├── columns: "$1":8 column9:9
           │    │    │    ├── cardinality: [1 - 1]
           │    │    │    ├── stable, has-placeholder
           │    │    │    ├── key: ()
           │    │    │    ├── fd: ()-->(8,9)
           │    │    │    └── ($1, now())
           │    │    └── projections
           │    │         └── column9:9 + '01:00:00' [as=column10:10, outer=(9), stable]
           │    └── filters (true)
           └── filters (true)

# TODO(mgartner): Apply the rule to stable, non-leaf expressions.
opt no-stable-folds
SELECT * FROM t WHERE t = '2024-01-01 12:00:00'::TIMESTAMP::TIMESTAMPTZ
----
select
 ├── columns: k:1!null i:2 s:3 b:4 t:5!null
 ├── stable
 ├── key: (1)
 ├── fd: ()-->(5), (1)-->(2-4)
 ├── scan t
 │    ├── columns: k:1!null i:2 s:3 b:4 t:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      └── t:5 = '2024-01-01 12:00:00'::TIMESTAMPTZ [outer=(5), stable, constraints=(/5: (/NULL - ]), fd=()-->(5)]

# A stable function is not included in the Values expression if it has
# arguments.
# TODO(mgartner): We should be able to relax this restriction as long as all the
# arguments are constants or placeholders.
opt no-stable-folds expect=GenerateParameterizedJoin
SELECT * FROM t WHERE i = $1 AND s = quote_literal(1::INT)
----
project
 ├── columns: k:1!null i:2!null s:3!null b:4 t:5
 ├── stable, has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4,5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3!null b:4 t:5 "$1":8!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── stable, has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2,3,8), (1)-->(4,5), (2)==(8), (8)==(2)
      ├── inner-join (lookup t@t_i_s_b_idx)
      │    ├── columns: k:1!null i:2!null s:3!null b:4 "$1":8!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8] = [2]
      │    ├── stable, has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(2,3,8), (1)-->(4), (2)==(8), (8)==(2)
      │    ├── values
      │    │    ├── columns: "$1":8
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8)
      │    │    └── ($1,)
      │    └── filters
      │         └── s:3 = quote_literal(1) [outer=(3), stable, constraints=(/3: (/NULL - ]), fd=()-->(3)]
      └── filters (true)

# A stable function is not included in the Values expression if its arguments
# reference a column from the table. This would create an illegal outer column
# reference in a non-apply-join.
opt no-stable-folds expect=GenerateParameterizedJoin
SELECT * FROM t WHERE i = $1 AND s = quote_literal(i)
----
project
 ├── columns: k:1!null i:2!null s:3!null b:4 t:5
 ├── stable, has-placeholder
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4,5)
 └── inner-join (lookup t)
      ├── columns: k:1!null i:2!null s:3!null b:4 t:5 "$1":8!null
      ├── key columns: [1] = [1]
      ├── lookup columns are key
      ├── stable, has-placeholder
      ├── key: (1)
      ├── fd: ()-->(2,3,8), (1)-->(4,5), (2)==(8), (8)==(2)
      ├── inner-join (lookup t@t_i_s_b_idx)
      │    ├── columns: k:1!null i:2!null s:3!null b:4 "$1":8!null
      │    ├── flags: disallow merge join
      │    ├── key columns: [8] = [2]
      │    ├── stable, has-placeholder
      │    ├── key: (1)
      │    ├── fd: ()-->(2,3,8), (1)-->(4), (2)==(8), (8)==(2)
      │    ├── values
      │    │    ├── columns: "$1":8
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── has-placeholder
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8)
      │    │    └── ($1,)
      │    └── filters
      │         └── s:3 = quote_literal(i:2) [outer=(2,3), stable, constraints=(/3: (/NULL - ]), fd=(2)-->(3)]
      └── filters (true)

# The rule does not match if there are no placeholders or stable expressions in
# the filters.
opt expect-not=GenerateParameterizedJoin
SELECT * FROM t WHERE i = 1 AND s = 'foo'
----
index-join t
 ├── columns: k:1!null i:2!null s:3!null b:4 t:5
 ├── key: (1)
 ├── fd: ()-->(2,3), (1)-->(4,5)
 └── scan t@t_i_s_b_idx
      ├── columns: k:1!null i:2!null s:3!null b:4
      ├── constraint: /2/3/4/1: [/1/'foo' - /1/'foo']
      ├── key: (1)
      └── fd: ()-->(2,3), (1)-->(4)

# The rule does not match if generic optimizations are disabled.
opt set=(plan_cache_mode=force_custom_plan) expect-not=GenerateParameterizedJoin
SELECT * FROM t WHERE k = $1 AND s = quote_literal(1::INT)
----
select
 ├── columns: k:1!null i:2 s:3!null b:4 t:5
 ├── cardinality: [0 - 1]
 ├── has-placeholder
 ├── key: ()
 ├── fd: ()-->(1-5)
 ├── scan t
 │    ├── columns: k:1!null i:2 s:3 b:4 t:5
 │    ├── key: (1)
 │    └── fd: (1)-->(2-5)
 └── filters
      ├── k:1 = $1 [outer=(1), constraints=(/1: (/NULL - ]), fd=()-->(1)]
      └── s:3 = e'\'1\'' [outer=(3), constraints=(/3: [/e'\'1\'' - /e'\'1\'']; tight), fd=()-->(3)]
