exec-ddl
CREATE TABLE a (k INT PRIMARY KEY, i INT, s STRING, d DECIMAL NOT NULL)
----

exec-ddl
CREATE TABLE b (x INT, z INT NOT NULL)
----

exec-ddl
CREATE TABLE g (k INT PRIMARY KEY, geom GEOMETRY, INVERTED INDEX (geom))
----

opt
SELECT k, x FROM a INNER JOIN b ON k=x WHERE d=1.0
----
project
 ├── columns: k:1!null x:7!null
 ├── immutable
 ├── stats: [rows=99]
 ├── cost: 2181.765
 ├── fd: (1)==(7), (7)==(1)
 └── inner-join (hash)
      ├── columns: k:1!null d:4!null x:7!null
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
      ├── immutable
      ├── stats: [rows=99, distinct(1)=10, null(1)=0, distinct(7)=10, null(7)=0]
      ├── cost: 2180.755
      ├── fd: ()-->(4), (1)==(7), (7)==(1)
      ├── scan b
      │    ├── columns: x:7
      │    ├── stats: [rows=1000, distinct(7)=100, null(7)=10]
      │    └── cost: 1068.42
      ├── select
      │    ├── columns: k:1!null d:4!null
      │    ├── immutable
      │    ├── stats: [rows=10, distinct(1)=10, null(1)=0, distinct(4)=1, null(4)=0]
      │    ├── cost: 1098.65
      │    ├── key: (1)
      │    ├── fd: ()-->(4)
      │    ├── scan a
      │    │    ├── columns: k:1!null d:4!null
      │    │    ├── stats: [rows=1000, distinct(1)=1000, null(1)=0, distinct(4)=100, null(4)=0]
      │    │    ├── cost: 1088.62
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(4)
      │    └── filters
      │         └── d:4 = 1.0 [outer=(4), immutable, constraints=(/4: [/1.0 - /1.0]; tight), fd=()-->(4)]
      └── filters
           └── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

# Verify that we pick merge join if we force it.
opt
SELECT k, x FROM a INNER MERGE JOIN b ON k=x
----
inner-join (merge)
 ├── columns: k:1!null x:7!null
 ├── flags: force merge join
 ├── left ordering: +1
 ├── right ordering: +7
 ├── stats: [rows=990, distinct(1)=99, null(1)=0, distinct(7)=99, null(7)=0]
 ├── cost: 2406.34194
 ├── fd: (1)==(7), (7)==(1)
 ├── scan a
 │    ├── columns: k:1!null
 │    ├── stats: [rows=1000, distinct(1)=1000, null(1)=0]
 │    ├── cost: 1078.52
 │    ├── key: (1)
 │    └── ordering: +1
 ├── sort
 │    ├── columns: x:7
 │    ├── stats: [rows=1000, distinct(7)=100, null(7)=10]
 │    ├── cost: 1297.90194
 │    ├── ordering: +7
 │    └── scan b
 │         ├── columns: x:7
 │         ├── stats: [rows=1000, distinct(7)=100, null(7)=10]
 │         └── cost: 1068.42
 └── filters (true)

# Verify that we pick lookup join if we force it. Note that lookup join is only
# possible if b is the left table.
opt
SELECT k, x FROM b INNER LOOKUP JOIN a ON k=x
----
inner-join (lookup a)
 ├── columns: k:6!null x:1!null
 ├── flags: force lookup join (into right side)
 ├── key columns: [1] = [6]
 ├── lookup columns are key
 ├── stats: [rows=990, distinct(1)=99, null(1)=0, distinct(6)=99, null(6)=0]
 ├── cost: 7111.84
 ├── fd: (1)==(6), (6)==(1)
 ├── scan b
 │    ├── columns: x:1
 │    ├── stats: [rows=1000, distinct(1)=100, null(1)=10]
 │    └── cost: 1068.42
 └── filters (true)


# Verify that if we force lookup join but one isn't possible, the hash join has
# huge cost (this will result in an error if we try to execbuild the result).
opt
SELECT k, x FROM a INNER LOOKUP JOIN b ON k=x
----
inner-join (hash)
 ├── columns: k:1!null x:7!null
 ├── flags: force lookup join (into right side)
 ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
 ├── stats: [rows=990, distinct(1)=99, null(1)=0, distinct(7)=99, null(7)=0]
 ├── cost: 1e+100
 ├── cost-flags: huge-cost-penalty
 ├── fd: (1)==(7), (7)==(1)
 ├── scan a
 │    ├── columns: k:1!null
 │    ├── stats: [rows=1000, distinct(1)=1000, null(1)=0]
 │    ├── cost: 1078.52
 │    └── key: (1)
 ├── scan b
 │    ├── columns: x:7
 │    ├── stats: [rows=1000, distinct(7)=100, null(7)=10]
 │    └── cost: 1068.42
 └── filters
      └── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

# Verify that if we specify a straight join, huge cost is given to 
# commuted joins so that non-commuted joins are picked as 
# the best plan by the optimizer.
exploretrace rule=CommuteLeftJoin format=(hide-all,show-cost)
SELECT k, x FROM b LEFT STRAIGHT JOIN a ON k=x
----
----
================================================================================
CommuteLeftJoin
================================================================================
Source expression:
  left-join (hash)
   ├── flags: disallow hash join (store left side) and lookup join (into left side) and inverted join (into left side)
   ├── cost: 2187.10625
   ├── scan b
   │    └── cost: 1068.42
   ├── scan a
   │    └── cost: 1078.52
   └── filters
        └── k = x

New expression 1 of 1:
  right-join (hash)
   ├── flags: disallow hash join (store right side) and lookup join (into right side) and inverted join (into right side)
   ├── cost: 1e+100
   ├── cost-flags: huge-cost-penalty
   ├── scan a
   │    └── cost: 1078.52
   ├── scan b
   │    └── cost: 1068.42
   └── filters
        └── k = x
----
----

# CommuteRightJoin is a normalization rule and thus does not
# show up in the exploretrace output. But we can verify that
# huge cost is given to commuted joins from one of the 
# source expressions (which was commuted).
exploretrace rule=CommuteLeftJoin format=(hide-all,show-cost)
SELECT k, x FROM b RIGHT STRAIGHT JOIN a ON k=x
----
----
================================================================================
CommuteLeftJoin
================================================================================
Source expression:
  left-join (hash)
   ├── flags: disallow hash join (store right side) and lookup join (into right side) and inverted join (into right side)
   ├── cost: 1e+100
   ├── cost-flags: huge-cost-penalty
   ├── scan a
   │    └── cost: 1078.52
   ├── scan b
   │    └── cost: 1068.42
   └── filters
        └── k = x

New expression 1 of 1:
  right-join (hash)
   ├── flags: disallow hash join (store left side) and lookup join (into left side) and inverted join (into left side)
   ├── cost: 2187.10625
   ├── scan b
   │    └── cost: 1068.42
   ├── scan a
   │    └── cost: 1078.52
   └── filters
        └── k = x
----
----

# Verify that we pick inverted join if we force it.
opt
SELECT * FROM g AS g1 INNER INVERTED JOIN g AS g2 ON ST_Contains(g1.geom, g2.geom)
----
inner-join (lookup g [as=g2])
 ├── columns: k:1!null geom:2!null k:6!null geom:7!null
 ├── key columns: [11] = [6]
 ├── lookup columns are key
 ├── immutable
 ├── stats: [rows=9801]
 ├── cost: 1101976.47
 ├── key: (1,6)
 ├── fd: (1)-->(2), (6)-->(7)
 ├── inner-join (inverted g@g_geom_idx,inverted [as=g2])
 │    ├── columns: g1.k:1!null g1.geom:2 g2.k:11!null
 │    ├── flags: force inverted join (into right side)
 │    ├── inverted-expr
 │    │    └── st_contains(g1.geom:2, g2.geom:12)
 │    ├── stats: [rows=10000, distinct(11)=999.957, null(11)=0]
 │    ├── cost: 41472.44
 │    ├── key: (1,11)
 │    ├── fd: (1)-->(2)
 │    ├── scan g [as=g1]
 │    │    ├── columns: g1.k:1!null g1.geom:2
 │    │    ├── stats: [rows=1000, distinct(2)=100, null(2)=10]
 │    │    ├── cost: 1068.42
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2)
 │    └── filters (true)
 └── filters
      └── st_contains(g1.geom:2, g2.geom:7) [outer=(2,7), immutable, constraints=(/2: (/NULL - ]; /7: (/NULL - ])]

# Verify that if we force inverted join but one isn't possible, the hash join has
# huge cost (this will result in an error if we try to execbuild the result).
opt
SELECT * FROM g AS g1 INNER INVERTED JOIN g AS g2 ON ST_Contains(g1.geom, g2.geom) OR g1.k = 5
----
inner-join (cross)
 ├── columns: k:1!null geom:2 k:6!null geom:7
 ├── flags: force inverted join (into right side)
 ├── immutable
 ├── stats: [rows=333333.3]
 ├── cost: 1e+100
 ├── cost-flags: huge-cost-penalty
 ├── key: (1,6)
 ├── fd: (1)-->(2), (6)-->(7)
 ├── scan g [as=g1]
 │    ├── columns: g1.k:1!null g1.geom:2
 │    ├── stats: [rows=1000, distinct(1)=1000, null(1)=0]
 │    ├── cost: 1068.42
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── scan g [as=g2]
 │    ├── columns: g2.k:6!null g2.geom:7
 │    ├── stats: [rows=1000, distinct(6)=1000, null(6)=0]
 │    ├── cost: 1068.42
 │    ├── key: (6)
 │    └── fd: (6)-->(7)
 └── filters
      └── st_contains(g1.geom:2, g2.geom:7) OR (g1.k:1 = 5) [outer=(1,2,7), immutable]


exec-ddl
ALTER TABLE a INJECT STATISTICS '[
  {
    "columns": ["k"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 100000,
    "distinct_count": 100000,
    "avg_size": 3
  }
]'
----

exec-ddl
ALTER TABLE b INJECT STATISTICS '[
  {
    "columns": ["x"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 10000,
    "distinct_count": 1000,
    "avg_size": 2
  }
]'
----

# Lookup join with no limit hint.
opt
SELECT * FROM a JOIN b ON k=z WHERE x > 0 AND x <= 5000
----
inner-join (lookup a)
 ├── columns: k:1!null i:2 s:3 d:4!null x:7!null z:8!null
 ├── key columns: [8] = [1]
 ├── lookup columns are key
 ├── stats: [rows=10000, distinct(1)=1000, null(1)=0, distinct(8)=1000, null(8)=0]
 ├── cost: 71382.47
 ├── fd: (1)-->(2-4), (1)==(8), (8)==(1)
 ├── select
 │    ├── columns: x:7!null z:8!null
 │    ├── stats: [rows=10000, distinct(7)=1000, null(7)=0, distinct(8)=1000, null(8)=0]
 │    ├── cost: 10528.45
 │    ├── scan b
 │    │    ├── columns: x:7 z:8!null
 │    │    ├── stats: [rows=10000, distinct(7)=1000, null(7)=0, distinct(8)=1000, null(8)=0]
 │    │    └── cost: 10428.42
 │    └── filters
 │         └── (x:7 > 0) AND (x:7 <= 5000) [outer=(7), constraints=(/7: [/1 - /5000]; tight)]
 └── filters (true)

# With the limit hint, the cost of the lookup join is reduced.
opt
SELECT * FROM a JOIN b ON k=z WHERE x > 0 AND x <= 5000 LIMIT 6000
----
limit
 ├── columns: k:1!null i:2 s:3 d:4!null x:7!null z:8!null
 ├── cardinality: [0 - 6000]
 ├── stats: [rows=6000]
 ├── cost: 42892.08
 ├── fd: (1)-->(2-4), (1)==(8), (8)==(1)
 ├── inner-join (lookup a)
 │    ├── columns: k:1!null i:2 s:3 d:4!null x:7!null z:8!null
 │    ├── key columns: [8] = [1]
 │    ├── lookup columns are key
 │    ├── stats: [rows=10000, distinct(1)=1000, null(1)=0, distinct(8)=1000, null(8)=0]
 │    ├── cost: 42832.07
 │    ├── fd: (1)-->(2-4), (1)==(8), (8)==(1)
 │    ├── limit hint: 6000.00
 │    ├── select
 │    │    ├── columns: x:7!null z:8!null
 │    │    ├── stats: [rows=10000, distinct(7)=1000, null(7)=0, distinct(8)=1000, null(8)=0]
 │    │    ├── cost: 6318.05
 │    │    ├── limit hint: 6000.00
 │    │    ├── scan b
 │    │    │    ├── columns: x:7 z:8!null
 │    │    │    ├── stats: [rows=10000, distinct(7)=1000, null(7)=0, distinct(8)=1000, null(8)=0]
 │    │    │    ├── cost: 6258.02
 │    │    │    └── limit hint: 6000.00
 │    │    └── filters
 │    │         └── (x:7 > 0) AND (x:7 <= 5000) [outer=(7), constraints=(/7: [/1 - /5000]; tight)]
 │    └── filters (true)
 └── 6000

# The limit hint for the lookup join input will be rounded up to the nearest
# multiple of the batch size, so the cost of the lookup join here is the same
# as the test case above.
opt
SELECT * FROM a JOIN b ON k=z WHERE x > 0 AND x <= 5000 LIMIT 5950
----
limit
 ├── columns: k:1!null i:2 s:3 d:4!null x:7!null z:8!null
 ├── cardinality: [0 - 5950]
 ├── stats: [rows=5950]
 ├── cost: 42891.58
 ├── fd: (1)-->(2-4), (1)==(8), (8)==(1)
 ├── inner-join (lookup a)
 │    ├── columns: k:1!null i:2 s:3 d:4!null x:7!null z:8!null
 │    ├── key columns: [8] = [1]
 │    ├── lookup columns are key
 │    ├── stats: [rows=10000, distinct(1)=1000, null(1)=0, distinct(8)=1000, null(8)=0]
 │    ├── cost: 42832.07
 │    ├── fd: (1)-->(2-4), (1)==(8), (8)==(1)
 │    ├── limit hint: 5950.00
 │    ├── select
 │    │    ├── columns: x:7!null z:8!null
 │    │    ├── stats: [rows=10000, distinct(7)=1000, null(7)=0, distinct(8)=1000, null(8)=0]
 │    │    ├── cost: 6318.05
 │    │    ├── limit hint: 6000.00
 │    │    ├── scan b
 │    │    │    ├── columns: x:7 z:8!null
 │    │    │    ├── stats: [rows=10000, distinct(7)=1000, null(7)=0, distinct(8)=1000, null(8)=0]
 │    │    │    ├── cost: 6258.02
 │    │    │    └── limit hint: 6000.00
 │    │    └── filters
 │    │         └── (x:7 > 0) AND (x:7 <= 5000) [outer=(7), constraints=(/7: [/1 - /5000]; tight)]
 │    └── filters (true)
 └── 5950

# Test case where the best plan is a lookup join only if the rows processed are
# also scaled correctly according to the limit hint (#48791).
exec-ddl
CREATE TABLE wallet (
    id bigserial primary key,
    name text not null,
    gender int,
    email text,
    first_name text,
    last_name text,
    creation_date timestamp not null,
    situation int,
    balance decimal not null,
    is_blocked bool,
    INDEX (name),
    INDEX (situation),
    INDEX (is_blocked),
    INDEX (balance)
);
----

exec-ddl
CREATE TABLE transaction (
    id bigserial primary key,
    sender_id bigint,
    receiver_id bigint,
    amount decimal not null,
    creation_date timestamp not null,
    last_update timestamp,
    schedule_date timestamp,
    status int,
    comment text,
    linked_trans_id bigint,
    c1 text,
    c2 text,
    c3 text,
    INDEX (sender_id),
    INDEX (receiver_id),
    INDEX (linked_trans_id)
);
----

opt
SELECT * FROM transaction t
JOIN wallet AS s on t.sender_id = s.id
JOIN wallet AS r on t.receiver_id = r.id
limit 10;
----
limit
 ├── columns: id:1!null sender_id:2!null receiver_id:3!null amount:4!null creation_date:5!null last_update:6 schedule_date:7 status:8 comment:9 linked_trans_id:10 c1:11 c2:12 c3:13 id:16!null name:17!null gender:18 email:19 first_name:20 last_name:21 creation_date:22!null situation:23 balance:24!null is_blocked:25 id:28!null name:29!null gender:30 email:31 first_name:32 last_name:33 creation_date:34!null situation:35 balance:36!null is_blocked:37
 ├── cardinality: [0 - 10]
 ├── stats: [rows=10]
 ├── cost: 2134.54
 ├── key: (1)
 ├── fd: (1)-->(2-13), (16)-->(17-25), (28)-->(29-37), (2)==(16), (16)==(2), (3)==(28), (28)==(3)
 ├── inner-join (lookup wallet [as=r])
 │    ├── columns: t.id:1!null sender_id:2!null receiver_id:3!null amount:4!null t.creation_date:5!null last_update:6 schedule_date:7 status:8 comment:9 linked_trans_id:10 c1:11 c2:12 c3:13 s.id:16!null s.name:17!null s.gender:18 s.email:19 s.first_name:20 s.last_name:21 s.creation_date:22!null s.situation:23 s.balance:24!null s.is_blocked:25 r.id:28!null r.name:29!null r.gender:30 r.email:31 r.first_name:32 r.last_name:33 r.creation_date:34!null r.situation:35 r.balance:36!null r.is_blocked:37
 │    ├── key columns: [3] = [28]
 │    ├── lookup columns are key
 │    ├── stats: [rows=980.1, distinct(3)=98.995, null(3)=0, distinct(28)=98.995, null(28)=0]
 │    ├── cost: 2134.43
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-13), (16)-->(17-25), (28)-->(29-37), (2)==(16), (16)==(2), (3)==(28), (28)==(3)
 │    ├── limit hint: 10.00
 │    ├── inner-join (lookup wallet [as=s])
 │    │    ├── columns: t.id:1!null sender_id:2!null receiver_id:3 amount:4!null t.creation_date:5!null last_update:6 schedule_date:7 status:8 comment:9 linked_trans_id:10 c1:11 c2:12 c3:13 s.id:16!null s.name:17!null s.gender:18 s.email:19 s.first_name:20 s.last_name:21 s.creation_date:22!null s.situation:23 s.balance:24!null s.is_blocked:25
 │    │    ├── key columns: [2] = [16]
 │    │    ├── lookup columns are key
 │    │    ├── stats: [rows=990, distinct(2)=99, null(2)=0, distinct(3)=99.995, null(3)=9.9, distinct(16)=99, null(16)=0]
 │    │    ├── cost: 1511.62
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2-13), (16)-->(17-25), (2)==(16), (16)==(2)
 │    │    ├── limit hint: 100.00
 │    │    ├── scan transaction [as=t]
 │    │    │    ├── columns: t.id:1!null sender_id:2 receiver_id:3 amount:4!null t.creation_date:5!null last_update:6 schedule_date:7 status:8 comment:9 linked_trans_id:10 c1:11 c2:12 c3:13
 │    │    │    ├── stats: [rows=1000, distinct(2)=100, null(2)=10, distinct(3)=100, null(3)=10]
 │    │    │    ├── cost: 270.02
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: (1)-->(2-13)
 │    │    │    └── limit hint: 200.00
 │    │    └── filters (true)
 │    └── filters (true)
 └── 10

exec-ddl
CREATE TABLE abc (a INT PRIMARY KEY, b INT, c INT, INDEX c_idx (c))
----

exec-ddl
ALTER TABLE abc INJECT STATISTICS '[
  {
    "columns": ["a"],
    "created_at": "2018-05-01 1:00:00.00000+00:00",
    "row_count": 500000000,
    "distinct_count": 500000000
  }
]'
----

# Check that we choose the index join when it makes sense.
opt
SELECT * FROM abc WHERE c = 1
----
index-join abc
 ├── columns: a:1!null b:2 c:3!null
 ├── stats: [rows=9.9505, distinct(3)=1, null(3)=0]
 ├── cost: 88.5890464
 ├── key: (1)
 ├── fd: ()-->(3), (1)-->(2)
 └── scan abc@c_idx
      ├── columns: a:1!null c:3!null
      ├── constraint: /3/1: [/1 - /1]
      ├── stats: [rows=9.9505, distinct(3)=1, null(3)=0]
      ├── cost: 28.3685202
      ├── key: (1)
      └── fd: ()-->(3)

# Regression test for #34810: make sure we pick the lookup join that uses
# all equality columns.

exec-ddl
CREATE TABLE abcde (
  a TEXT NOT NULL,
  b UUID NOT NULL,
  c UUID NOT NULL,
  d VARCHAR(255) NOT NULL,
  e TEXT NOT NULL,
  UNIQUE INDEX idx_abd (a, b, d) STORING (c),
  UNIQUE INDEX idx_abcd (a, b, c, d)
)
----

exec-ddl
ALTER TABLE abcde INJECT STATISTICS '[
  {
    "columns": ["a"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 250000,
    "distinct_count": 1
  },
  {
    "columns": ["b"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 250000,
    "distinct_count": 2
  },
  {
    "columns": ["d"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 250000,
    "distinct_count": 125000
  }
]'
----

exec-ddl
CREATE TABLE wxyz (
  w TEXT NOT NULL,
  x UUID NOT NULL,
  y UUID NOT NULL,
  z TEXT NOT NULL
)
----

exec-ddl
ALTER TABLE wxyz INJECT STATISTICS '[
  {
    "columns": ["w"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 100,
    "distinct_count": 1
  },
  {
    "columns": ["x"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 100,
    "distinct_count": 1
  },
  {
    "columns": ["y"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 100,
    "distinct_count": 25
  }
]'
----

opt
SELECT w, x, y, z
FROM wxyz
INNER JOIN abcde
ON w = a AND x = b AND y = c
WHERE w = 'foo' AND x = '2AB23800-06B1-4E19-A3BB-DF3768B808D2'
----
project
 ├── columns: w:1!null x:2!null y:3!null z:4!null
 ├── stats: [rows=500.4888]
 ├── cost: 3193.02489
 ├── fd: ()-->(1,2)
 └── inner-join (lookup abcde@idx_abcd)
      ├── columns: w:1!null x:2!null y:3!null z:4!null a:8!null b:9!null c:10!null
      ├── key columns: [1 2 3] = [8 9 10]
      ├── stats: [rows=500.4888, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(3)=25, null(3)=0, distinct(8)=1, null(8)=0, distinct(9)=1, null(9)=0, distinct(10)=25, null(10)=0]
      ├── cost: 3188
      ├── fd: ()-->(1,2,8,9), (1)==(8), (8)==(1), (2)==(9), (9)==(2), (3)==(10), (10)==(3)
      ├── select
      │    ├── columns: w:1!null x:2!null y:3!null z:4!null
      │    ├── stats: [rows=100, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(3)=25, null(3)=0]
      │    ├── cost: 138.96
      │    ├── fd: ()-->(1,2)
      │    ├── scan wxyz
      │    │    ├── columns: w:1!null x:2!null y:3!null z:4!null
      │    │    ├── stats: [rows=100, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(3)=25, null(3)=0, distinct(4)=10, null(4)=0]
      │    │    └── cost: 137.92
      │    └── filters
      │         ├── w:1 = 'foo' [outer=(1), constraints=(/1: [/'foo' - /'foo']; tight), fd=()-->(1)]
      │         └── x:2 = '2ab23800-06b1-4e19-a3bb-df3768b808d2' [outer=(2), constraints=(/2: [/'2ab23800-06b1-4e19-a3bb-df3768b808d2' - /'2ab23800-06b1-4e19-a3bb-df3768b808d2']; tight), fd=()-->(2)]
      └── filters
           ├── a:8 = 'foo' [outer=(8), constraints=(/8: [/'foo' - /'foo']; tight), fd=()-->(8)]
           └── b:9 = '2ab23800-06b1-4e19-a3bb-df3768b808d2' [outer=(9), constraints=(/9: [/'2ab23800-06b1-4e19-a3bb-df3768b808d2' - /'2ab23800-06b1-4e19-a3bb-df3768b808d2']; tight), fd=()-->(9)]

# Also for 34810: make sure the cost adjustment works when the estimated row
# count is tiny.
exec-ddl
CREATE TABLE wxyzijklmn (
  w TEXT NOT NULL,
  x UUID NOT NULL,
  y UUID NOT NULL,
  z TEXT NOT NULL,
  i INT,
  j INT,
  k INT,
  l INT,
  m INT,
  n INT
)
----

exec-ddl
ALTER TABLE wxyzijklmn INJECT STATISTICS '[
  {
    "columns": ["w"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 10000,
    "distinct_count": 1
  },
  {
    "columns": ["x"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 1
  },
  {
    "columns": ["y"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 25
  },
  {
    "columns": ["i"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 10000
  },
  {
    "columns": ["j"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 10000
  },
  {
    "columns": ["k"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 10000
  },
  {
    "columns": ["l"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 10000
  },
  {
    "columns": ["m"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 10000
  },
  {
    "columns": ["n"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 10000
  }
]'
----

opt
SELECT w, x, y, z
FROM wxyzijklmn
INNER JOIN abcde
ON w = a AND x = b AND y = c
WHERE w = 'foo' AND x = '2AB23800-06B1-4E19-A3BB-DF3768B808D2' AND (i,j,k,l,m,n)=(1,2,3,4,5,6)
----
project
 ├── columns: w:1!null x:2!null y:3!null z:4!null
 ├── stats: [rows=0.25]
 ├── cost: 12235.3275
 ├── fd: ()-->(1,2)
 └── inner-join (lookup abcde@idx_abcd)
      ├── columns: w:1!null x:2!null y:3!null z:4!null i:5!null j:6!null k:7!null l:8!null m:9!null n:10!null a:14!null b:15!null c:16!null
      ├── key columns: [1 2 3] = [14 15 16]
      ├── stats: [rows=0.25, distinct(1)=2e-06, null(1)=0, distinct(2)=2e-06, null(2)=0, distinct(3)=2e-06, null(3)=0, distinct(14)=2e-06, null(14)=0, distinct(15)=2e-06, null(15)=0, distinct(16)=2e-06, null(16)=0]
      ├── cost: 12235.305
      ├── fd: ()-->(1,2,5-10,14,15), (1)==(14), (14)==(1), (2)==(15), (15)==(2), (3)==(16), (16)==(3)
      ├── select
      │    ├── columns: w:1!null x:2!null y:3!null z:4!null i:5!null j:6!null k:7!null l:8!null m:9!null n:10!null
      │    ├── stats: [rows=2e-06, distinct(1)=2e-06, null(1)=0, distinct(2)=2e-06, null(2)=0, distinct(3)=2e-06, null(3)=0, distinct(5)=2e-06, null(5)=0, distinct(6)=2e-06, null(6)=0, distinct(7)=2e-06, null(7)=0, distinct(8)=2e-06, null(8)=0, distinct(9)=2e-06, null(9)=0, distinct(10)=2e-06, null(10)=0, distinct(5-10)=2e-06, null(5-10)=0]
      │    ├── cost: 12230.22
      │    ├── fd: ()-->(1,2,5-10)
      │    ├── scan wxyzijklmn
      │    │    ├── columns: w:1!null x:2!null y:3!null z:4!null i:5 j:6 k:7 l:8 m:9 n:10
      │    │    ├── stats: [rows=10000, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(3)=25, null(3)=0, distinct(4)=1000, null(4)=0, distinct(5)=10000, null(5)=0, distinct(6)=10000, null(6)=0, distinct(7)=10000, null(7)=0, distinct(8)=10000, null(8)=0, distinct(9)=10000, null(9)=0, distinct(10)=10000, null(10)=0, distinct(5-10)=10000, null(5-10)=0]
      │    │    └── cost: 12130.12
      │    └── filters
      │         ├── w:1 = 'foo' [outer=(1), constraints=(/1: [/'foo' - /'foo']; tight), fd=()-->(1)]
      │         ├── x:2 = '2ab23800-06b1-4e19-a3bb-df3768b808d2' [outer=(2), constraints=(/2: [/'2ab23800-06b1-4e19-a3bb-df3768b808d2' - /'2ab23800-06b1-4e19-a3bb-df3768b808d2']; tight), fd=()-->(2)]
      │         ├── i:5 = 1 [outer=(5), constraints=(/5: [/1 - /1]; tight), fd=()-->(5)]
      │         ├── j:6 = 2 [outer=(6), constraints=(/6: [/2 - /2]; tight), fd=()-->(6)]
      │         ├── k:7 = 3 [outer=(7), constraints=(/7: [/3 - /3]; tight), fd=()-->(7)]
      │         ├── l:8 = 4 [outer=(8), constraints=(/8: [/4 - /4]; tight), fd=()-->(8)]
      │         ├── m:9 = 5 [outer=(9), constraints=(/9: [/5 - /5]; tight), fd=()-->(9)]
      │         └── n:10 = 6 [outer=(10), constraints=(/10: [/6 - /6]; tight), fd=()-->(10)]
      └── filters
           ├── a:14 = 'foo' [outer=(14), constraints=(/14: [/'foo' - /'foo']; tight), fd=()-->(14)]
           └── b:15 = '2ab23800-06b1-4e19-a3bb-df3768b808d2' [outer=(15), constraints=(/15: [/'2ab23800-06b1-4e19-a3bb-df3768b808d2' - /'2ab23800-06b1-4e19-a3bb-df3768b808d2']; tight), fd=()-->(15)]

exec-ddl
DROP TABLE abcde
----

exec-ddl
DROP TABLE wxyz
----

exec-ddl
CREATE TABLE abcde (
  a TEXT NOT NULL,
  b UUID NOT NULL,
  c UUID NOT NULL,
  d VARCHAR(255) NOT NULL,
  e TEXT NOT NULL,
  CONSTRAINT "primary" PRIMARY KEY (a, b, c),
  UNIQUE INDEX idx_abd (a, b, d),
  UNIQUE INDEX idx_abcd (a, b, c, d)
)
----

exec-ddl
ALTER TABLE abcde INJECT STATISTICS '[
  {
    "columns": ["a"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 250000,
    "distinct_count": 1
  },
  {
    "columns": ["b"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 250000,
    "distinct_count": 2
  },
  {
    "columns": ["d"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 250000,
    "distinct_count": 125000
  }
]'
----

exec-ddl
CREATE TABLE wxyz (
  w TEXT NOT NULL,
  x UUID NOT NULL,
  y UUID NOT NULL,
  z TEXT NOT NULL,
  CONSTRAINT "primary" PRIMARY KEY (w, x, y),
  CONSTRAINT "foreign" FOREIGN KEY (w, x, y) REFERENCES abcde (a, b, c)
)
----

exec-ddl
ALTER TABLE wxyz INJECT STATISTICS '[
  {
    "columns": ["w"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 10000,
    "distinct_count": 1
  },
  {
    "columns": ["x"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 1
  },
  {
    "columns": ["y"],
    "created_at": "2019-02-08 04:10:40.119954+00:00",
    "row_count": 10000,
    "distinct_count": 2500
  }
]'
----

# Regression test for #34811. Ensure the soft limit propagation causes us to
# select a lookup join.
opt
SELECT w, x, y, z
FROM wxyz
INNER JOIN abcde
ON w = a AND x = b AND y = c
WHERE w = 'foo' AND x = '2AB23800-06B1-4E19-A3BB-DF3768B808D2'
ORDER BY d
LIMIT 10
----
project
 ├── columns: w:1!null x:2!null y:3!null z:4!null  [hidden: d:10!null]
 ├── cardinality: [0 - 10]
 ├── stats: [rows=10]
 ├── cost: 613.96172
 ├── key: (10)
 ├── fd: ()-->(1,2), (3)-->(4,10), (10)-->(3,4)
 ├── ordering: +10 opt(1,2) [actual: +10]
 └── limit
      ├── columns: w:1!null x:2!null y:3!null z:4!null a:7!null b:8!null c:9!null d:10!null
      ├── internal-ordering: +10 opt(1,2,7,8)
      ├── cardinality: [0 - 10]
      ├── stats: [rows=10]
      ├── cost: 613.85172
      ├── key: (9)
      ├── fd: ()-->(1,2,7,8), (3)-->(4), (9)-->(10), (10)-->(9), (1)==(7), (7)==(1), (2)==(8), (8)==(2), (3)==(9), (9)==(3)
      ├── ordering: +10 opt(1,2,7,8) [actual: +10]
      ├── inner-join (lookup wxyz)
      │    ├── columns: w:1!null x:2!null y:3!null z:4!null a:7!null b:8!null c:9!null d:10!null
      │    ├── key columns: [7 8 9] = [1 2 3]
      │    ├── lookup columns are key
      │    ├── stats: [rows=50048.88, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(3)=2500, null(3)=0, distinct(7)=1, null(7)=0, distinct(8)=1, null(8)=0, distinct(9)=2500, null(9)=0]
      │    ├── cost: 613.74172
      │    ├── key: (9)
      │    ├── fd: ()-->(1,2,7,8), (3)-->(4), (9)-->(10), (10)-->(9), (1)==(7), (7)==(1), (2)==(8), (8)==(2), (3)==(9), (9)==(3)
      │    ├── ordering: +10 opt(1,2,7,8) [actual: +10]
      │    ├── limit hint: 10.00
      │    ├── scan abcde@idx_abd
      │    │    ├── columns: a:7!null b:8!null c:9!null d:10!null
      │    │    ├── constraint: /7/8/10: [/'foo'/'2ab23800-06b1-4e19-a3bb-df3768b808d2' - /'foo'/'2ab23800-06b1-4e19-a3bb-df3768b808d2']
      │    │    ├── stats: [rows=125000, distinct(7)=1, null(7)=0, distinct(8)=1, null(8)=0, distinct(9)=24975.6, null(9)=0]
      │    │    ├── cost: 126.02
      │    │    ├── key: (9)
      │    │    ├── fd: ()-->(7,8), (9)-->(10), (10)-->(9)
      │    │    ├── ordering: +10 opt(7,8) [actual: +10]
      │    │    └── limit hint: 100.00
      │    └── filters
      │         ├── w:1 = 'foo' [outer=(1), constraints=(/1: [/'foo' - /'foo']; tight), fd=()-->(1)]
      │         └── x:2 = '2ab23800-06b1-4e19-a3bb-df3768b808d2' [outer=(2), constraints=(/2: [/'2ab23800-06b1-4e19-a3bb-df3768b808d2' - /'2ab23800-06b1-4e19-a3bb-df3768b808d2']; tight), fd=()-->(2)]
      └── 10

exec-ddl
CREATE TABLE p (p INT PRIMARY KEY)
----

exec-ddl
CREATE TABLE c (c INT PRIMARY KEY, p INT REFERENCES p(p));
----

opt
INSERT INTO c VALUES (1,1), (2,2)
----
insert c
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => c.p:2
 ├── input binding: &1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 ├── stats: [rows=0]
 ├── cost: 16.12
 ├── values
 │    ├── columns: column1:5!null column2:6!null
 │    ├── cardinality: [2 - 2]
 │    ├── stats: [rows=2, distinct(6)=2, null(6)=0]
 │    ├── cost: 0.03
 │    ├── (1, 1)
 │    └── (2, 2)
 └── f-k-checks
      └── f-k-checks-item: c(p) -> p(p)
           └── anti-join (lookup p)
                ├── columns: p:7!null
                ├── key columns: [7] = [8]
                ├── lookup columns are key
                ├── cardinality: [0 - 2]
                ├── stats: [rows=1e-10]
                ├── cost: 16.08
                ├── with-scan &1
                │    ├── columns: p:7!null
                │    ├── mapping:
                │    │    └──  column2:6 => p:7
                │    ├── cardinality: [2 - 2]
                │    ├── stats: [rows=2, distinct(7)=2, null(7)=0]
                │    └── cost: 0.01
                └── filters (true)

# The cost should be much smaller with this hint.
opt set=prefer_lookup_joins_for_fks=true
INSERT INTO c VALUES (1,1), (2,2)
----
insert c
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => c.p:2
 ├── input binding: &1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 ├── stats: [rows=0]
 ├── cost: 4.06001206
 ├── values
 │    ├── columns: column1:5!null column2:6!null
 │    ├── cardinality: [2 - 2]
 │    ├── stats: [rows=2, distinct(6)=2, null(6)=0]
 │    ├── cost: 0.03
 │    ├── (1, 1)
 │    └── (2, 2)
 └── f-k-checks
      └── f-k-checks-item: c(p) -> p(p)
           └── anti-join (lookup p)
                ├── columns: p:7!null
                ├── flags: prefer lookup join (into right side)
                ├── key columns: [7] = [8]
                ├── lookup columns are key
                ├── cardinality: [0 - 2]
                ├── stats: [rows=1e-10]
                ├── cost: 4.02001206
                ├── with-scan &1
                │    ├── columns: p:7!null
                │    ├── mapping:
                │    │    └──  column2:6 => p:7
                │    ├── cardinality: [2 - 2]
                │    ├── stats: [rows=2, distinct(7)=2, null(7)=0]
                │    └── cost: 0.01
                └── filters (true)

# Verify that we prefer lookup join for the FK check when inserting a single
# row, even if the other table is very small. See #41701.
exec-ddl
ALTER TABLE p INJECT STATISTICS '[
  {
    "columns": ["p"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 2,
    "distinct_count": 2
  }
]'
----

opt
INSERT INTO c VALUES (1, 1)
----
insert c
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:5 => c:1
 │    └── column2:6 => c.p:2
 ├── input binding: &1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 ├── stats: [rows=0]
 ├── cost: 10.08
 ├── values
 │    ├── columns: column1:5!null column2:6!null
 │    ├── cardinality: [1 - 1]
 │    ├── stats: [rows=1, distinct(6)=1, null(6)=0]
 │    ├── cost: 0.02
 │    ├── key: ()
 │    ├── fd: ()-->(5,6)
 │    └── (1, 1)
 └── f-k-checks
      └── f-k-checks-item: c(p) -> p(p)
           └── anti-join (lookup p)
                ├── columns: p:7!null
                ├── key columns: [7] = [8]
                ├── lookup columns are key
                ├── cardinality: [0 - 1]
                ├── stats: [rows=1e-10]
                ├── cost: 10.05
                ├── key: ()
                ├── fd: ()-->(7)
                ├── with-scan &1
                │    ├── columns: p:7!null
                │    ├── mapping:
                │    │    └──  column2:6 => p:7
                │    ├── cardinality: [1 - 1]
                │    ├── stats: [rows=1, distinct(7)=1, null(7)=0]
                │    ├── cost: 0.01
                │    ├── key: ()
                │    └── fd: ()-->(7)
                └── filters (true)

# Avoid performing a lookup join with virtual tables if the filter is
# not extremely selective.

opt
SELECT
  a.attname,
  a.atttypid,
  t.typbasetype,
  t.typtype
FROM
  pg_catalog.pg_attribute AS a
  JOIN pg_catalog.pg_type AS t ON (a.atttypid = t.oid)
WHERE
  a.attname IN ('descriptor_id', 'descriptor_name')
----
project
 ├── columns: attname:3!null atttypid:4!null typbasetype:53 typtype:35
 ├── stats: [rows=198]
 ├── cost: 2928.23877
 └── inner-join (merge)
      ├── columns: attname:3!null atttypid:4!null oid:29!null typtype:35 typbasetype:53
      ├── left ordering: +29
      ├── right ordering: +4
      ├── stats: [rows=198, distinct(4)=17.2927, null(4)=0, distinct(29)=17.2927, null(29)=0]
      ├── cost: 2926.23877
      ├── fd: (4)==(29), (29)==(4)
      ├── scan pg_type@pg_type_oid_idx [as=t]
      │    ├── columns: oid:29!null typtype:35 typbasetype:53
      │    ├── stats: [rows=1000, distinct(29)=100, null(29)=0]
      │    ├── cost: 1481.52
      │    └── ordering: +29
      ├── sort
      │    ├── columns: attname:3!null atttypid:4
      │    ├── stats: [rows=20, distinct(3)=2, null(3)=0, distinct(4)=18.2927, null(4)=0.2]
      │    ├── cost: 1433.49877
      │    ├── ordering: +4
      │    └── select
      │         ├── columns: attname:3!null atttypid:4
      │         ├── stats: [rows=20, distinct(3)=2, null(3)=0, distinct(4)=18.2927, null(4)=0.2]
      │         ├── cost: 1430.95
      │         ├── scan pg_attribute [as=a]
      │         │    ├── columns: attname:3 atttypid:4
      │         │    ├── stats: [rows=1000, distinct(3)=100, null(3)=10, distinct(4)=100, null(4)=10]
      │         │    └── cost: 1420.92
      │         └── filters
      │              └── attname:3 IN ('descriptor_id', 'descriptor_name') [outer=(3), constraints=(/3: [/'descriptor_id' - /'descriptor_id'] [/'descriptor_name' - /'descriptor_name']; tight)]
      └── filters (true)


# A lookup join should be performed with a virtual table if the filter
# is very selective.
opt
SELECT
  a.attname,
  a.atttypid,
  t.typbasetype,
  t.typtype
FROM
  pg_catalog.pg_attribute AS a
  JOIN pg_catalog.pg_type AS t ON (a.atttypid = t.oid)
WHERE
  a.attname = 'descriptor_id'
----
project
 ├── columns: attname:3!null atttypid:4!null typbasetype:53 typtype:35
 ├── stats: [rows=99]
 ├── cost: 2869.62001
 ├── fd: ()-->(3)
 └── inner-join (lookup pg_type@pg_type_oid_idx [as=t])
      ├── columns: attname:3!null atttypid:4!null oid:29!null typtype:35 typbasetype:53
      ├── key columns: [4] = [29]
      ├── stats: [rows=99, distinct(4)=8.56179, null(4)=0, distinct(29)=8.56179, null(29)=0]
      ├── cost: 2868.61001
      ├── fd: ()-->(3), (4)==(29), (29)==(4)
      ├── select
      │    ├── columns: attname:3!null atttypid:4
      │    ├── stats: [rows=10, distinct(3)=1, null(3)=0, distinct(4)=9.56179, null(4)=0.1]
      │    ├── cost: 1430.95
      │    ├── fd: ()-->(3)
      │    ├── scan pg_attribute [as=a]
      │    │    ├── columns: attname:3 atttypid:4
      │    │    ├── stats: [rows=1000, distinct(3)=100, null(3)=10, distinct(4)=100, null(4)=10]
      │    │    └── cost: 1420.92
      │    └── filters
      │         └── attname:3 = 'descriptor_id' [outer=(3), constraints=(/3: [/'descriptor_id' - /'descriptor_id']; tight), fd=()-->(3)]
      └── filters (true)

exec-ddl
CREATE TABLE e (k INT PRIMARY KEY, i INT, s STRING, d DECIMAL NOT NULL, f FLOAT, INDEX id (i, d), INDEX iss (i, s))
----

exec-ddl
ALTER TABLE e INJECT STATISTICS '[
  {
    "columns": ["k"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 100000,
    "distinct_count": 100000,
    "avg_size": 3
  },
  {
    "columns": ["i"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 100000,
    "distinct_count": 100000,
    "avg_size": 5
  },
  {
    "columns": ["s"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 100000,
    "distinct_count": 100000,
    "avg_size": 11
  },
  {
    "columns": ["d"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 100000,
    "distinct_count": 100000,
    "avg_size": 2
  },
  {
    "columns": ["f"],
    "created_at": "2019-02-08 04:10:40.001179+00:00",
    "row_count": 100000,
    "distinct_count": 100000,
    "avg_size": 7
  }
]'
----

# Index join cost is based on avg_size.
opt
SELECT i, f FROM e WHERE i = 3;
----
index-join e
 ├── columns: i:2!null f:5
 ├── stats: [rows=1.00001, distinct(2)=1, null(2)=0]
 ├── cost: 25.1825714
 ├── fd: ()-->(2)
 └── scan e@id
      ├── columns: k:1!null i:2!null
      ├── constraint: /2/4/1: [/3 - /3]
      ├── stats: [rows=1.00001, distinct(2)=1, null(2)=0]
      ├── cost: 19.0650105
      ├── key: (1)
      └── fd: ()-->(2)

# The number of spans generated for each lookup should contribute to the cost
# of the lookup join (see #79797).

exec-ddl
CREATE TABLE small (
  k INT PRIMARY KEY,
  a INT
)
----

exec-ddl
CREATE TABLE big (
  k INT PRIMARY KEY,
  a INT,
  INDEX (a)
)
----

opt format=(show-stats,show-cost)
SELECT * FROM small INNER LOOKUP JOIN big ON small.k = big.k AND big.a = ANY ARRAY[1, 2, 3, 4, 5, 6]
----
inner-join (lookup big)
 ├── columns: k:1!null a:2 k:5!null a:6!null
 ├── flags: force lookup join (into right side)
 ├── key columns: [1] = [5]
 ├── lookup columns are key
 ├── stats: [rows=60, distinct(1)=60, null(1)=0, distinct(5)=60, null(5)=0]
 ├── cost: 7122.45
 ├── key: (5)
 ├── fd: (1)-->(2), (5)-->(6), (1)==(5), (5)==(1)
 ├── scan small
 │    ├── columns: small.k:1!null small.a:2
 │    ├── stats: [rows=1000, distinct(1)=1000, null(1)=0]
 │    ├── cost: 1068.42
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters
      └── big.a:6 IN (1, 2, 3, 4, 5, 6) [outer=(6), constraints=(/6: [/1 - /1] [/2 - /2] [/3 - /3] [/4 - /4] [/5 - /5] [/6 - /6]; tight)]

opt format=(show-stats,show-cost)
SELECT * FROM small INNER LOOKUP JOIN big@big_a_idx ON small.k = big.k AND big.a = ANY ARRAY[1, 2, 3, 4, 5, 6]
----
inner-join (lookup big@big_a_idx)
 ├── columns: k:1!null a:2 k:5!null a:6!null
 ├── flags: force lookup join (into right side)
 ├── lookup expression
 │    └── filters
 │         ├── big.a:6 IN (1, 2, 3, 4, 5, 6) [outer=(6), constraints=(/6: [/1 - /1] [/2 - /2] [/3 - /3] [/4 - /4] [/5 - /5] [/6 - /6]; tight)]
 │         └── small.k:1 = big.k:5 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
 ├── lookup columns are key
 ├── stats: [rows=60, distinct(1)=60, null(1)=0, distinct(5)=60, null(5)=0]
 ├── cost: 25217.44
 ├── key: (5)
 ├── fd: (1)-->(2), (5)-->(6), (1)==(5), (5)==(1)
 ├── scan small
 │    ├── columns: small.k:1!null small.a:2
 │    ├── stats: [rows=1000, distinct(1)=1000, null(1)=0]
 │    ├── cost: 1068.42
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters (true)
