exec-ddl
CREATE TABLE a (x INT PRIMARY KEY, y INT, s STRING, d DECIMAL NOT NULL, UNIQUE (s DESC, d))
----
TABLE a
 ├── x int not null
 ├── y int
 ├── s string
 ├── d decimal not null
 ├── INDEX primary
 │    └── x int not null
 └── INDEX secondary
      ├── s string desc
      ├── d decimal not null
      └── x int not null (storing)

exec-ddl
CREATE TABLE b (x INT, z INT NOT NULL)
----
TABLE b
 ├── x int
 ├── z int not null
 ├── rowid int not null (hidden)
 └── INDEX primary
      └── rowid int not null (hidden)

exec-ddl
ALTER TABLE a INJECT STATISTICS '[
  {
    "columns": ["x"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 5000
  },
  {
    "columns": ["y"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 5000,
    "distinct_count": 400
  }
]'
----

exec-ddl
ALTER TABLE b INJECT STATISTICS '[
  {
    "columns": ["x"],
    "created_at": "2018-01-01 1:00:00.00000+00:00",
    "row_count": 10000,
    "distinct_count": 500
  },
  {
    "columns": ["z"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 10000,
    "distinct_count": 100
  },
  {
    "columns": ["rowid"],
    "created_at": "2018-01-01 1:30:00.00000+00:00",
    "row_count": 10000,
    "distinct_count": 10000
  }
]'
----

norm
SELECT * FROM a JOIN b ON true
----
inner-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int) z:6(int!null)
 ├── stats: [rows=50000000]
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null)
 │    └── stats: [rows=10000]
 └── true [type=bool]

norm colstat=1 colstat=2 colstat=3 colstat=4 colstat=5 colstat=6 colstat=(2,5,6)
SELECT * FROM a JOIN b ON true
----
inner-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int) z:6(int!null)
 ├── stats: [rows=50000000, distinct(1)=5000, distinct(2)=400, distinct(3)=500, distinct(4)=500, distinct(5)=500, distinct(6)=100, distinct(2,5,6)=4000000]
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000, distinct(1)=5000, distinct(2)=400, distinct(3)=500, distinct(4)=500]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null)
 │    └── stats: [rows=10000, distinct(5)=500, distinct(6)=100, distinct(5,6)=10000]
 └── true [type=bool]

norm
SELECT * FROM a JOIN b ON false
----
values
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) x:5(int) z:6(int)
 ├── cardinality: [0 - 0]
 ├── stats: [rows=0]
 ├── key: ()
 └── fd: ()-->(1-6)

build
SELECT *, rowid FROM a INNER JOIN b ON a.x=b.x
----
inner-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int!null) z:6(int!null) rowid:7(int!null)
 ├── stats: [rows=10000, distinct(1)=500, distinct(5)=500]
 ├── key: (7)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6), (1)==(5), (5)==(1)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000, distinct(1)=5000]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null) rowid:7(int!null)
 │    ├── stats: [rows=10000, distinct(5)=500]
 │    ├── key: (7)
 │    └── fd: (7)-->(5,6)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]

build
SELECT *, rowid FROM a LEFT JOIN b ON a.x=b.x
----
left-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int) z:6(int) rowid:7(int)
 ├── stats: [rows=10000, distinct(5)=500]
 ├── key: (1,7)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000, distinct(1)=5000]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null) rowid:7(int!null)
 │    ├── stats: [rows=10000, distinct(5)=500]
 │    ├── key: (7)
 │    └── fd: (7)-->(5,6)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]

build
SELECT *, rowid FROM a RIGHT JOIN b ON a.x=b.x
----
right-join
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) x:5(int) z:6(int!null) rowid:7(int!null)
 ├── stats: [rows=10000, distinct(1)=500]
 ├── key: (1,7)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000, distinct(1)=5000]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null) rowid:7(int!null)
 │    ├── stats: [rows=10000, distinct(5)=500]
 │    ├── key: (7)
 │    └── fd: (7)-->(5,6)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]

build
SELECT *, rowid FROM a FULL JOIN b ON a.x=b.x
----
full-join
 ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) x:5(int) z:6(int) rowid:7(int)
 ├── stats: [rows=10000]
 ├── key: (1,7)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000, distinct(1)=5000]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null) rowid:7(int!null)
 │    ├── stats: [rows=10000, distinct(5)=500]
 │    ├── key: (7)
 │    └── fd: (7)-->(5,6)
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]

build
SELECT * FROM a, b
----
project
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int) z:6(int!null)
 ├── stats: [rows=50000000]
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 └── inner-join
      ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) b.x:5(int) z:6(int!null) rowid:7(int!null)
      ├── stats: [rows=50000000]
      ├── key: (1,7)
      ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
      ├── scan a
      │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
      │    ├── stats: [rows=5000]
      │    ├── key: (1)
      │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
      ├── scan b
      │    ├── columns: b.x:5(int) z:6(int!null) rowid:7(int!null)
      │    ├── stats: [rows=10000]
      │    ├── key: (7)
      │    └── fd: (7)-->(5,6)
      └── true [type=bool]

build
SELECT * FROM a, a AS a
----
inner-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int!null) y:6(int) s:7(string) d:8(decimal!null)
 ├── stats: [rows=25000000]
 ├── key: (1,5)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (5)-->(6-8), (7,8)~~>(5,6)
 ├── scan a
 │    ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null)
 │    ├── stats: [rows=5000]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan a
 │    ├── columns: a.x:5(int!null) a.y:6(int) a.s:7(string) a.d:8(decimal!null)
 │    ├── stats: [rows=5000]
 │    ├── key: (5)
 │    └── fd: (5)-->(6-8), (7,8)~~>(5,6)
 └── true [type=bool]

build
SELECT * FROM a, b WHERE b.z = 5
----
project
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int) z:6(int!null)
 ├── stats: [rows=500000]
 ├── fd: ()-->(6), (1)-->(2-4), (3,4)~~>(1,2)
 └── select
      ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) b.x:5(int) z:6(int!null) rowid:7(int!null)
      ├── stats: [rows=500000, distinct(6)=1]
      ├── key: (1,7)
      ├── fd: ()-->(6), (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5)
      ├── inner-join
      │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) b.x:5(int) z:6(int!null) rowid:7(int!null)
      │    ├── stats: [rows=50000000, distinct(6)=100]
      │    ├── key: (1,7)
      │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
      │    ├── scan a
      │    │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
      │    │    ├── stats: [rows=5000]
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
      │    ├── scan b
      │    │    ├── columns: b.x:5(int) z:6(int!null) rowid:7(int!null)
      │    │    ├── stats: [rows=10000, distinct(6)=100]
      │    │    ├── key: (7)
      │    │    └── fd: (7)-->(5,6)
      │    └── true [type=bool]
      └── filters [type=bool, outer=(6), constraints=(/6: [/5 - /5]; tight), fd=()-->(6)]
           └── z = 5 [type=bool, outer=(6), constraints=(/6: [/5 - /5]; tight)]

# Force calculation of the distinct count for the column set spanning both
# tables in the join.
build
SELECT sum(b.z), a.x, b.z FROM a, b GROUP BY a.x, b.z
----
group-by
 ├── columns: sum:8(decimal) x:1(int!null) z:6(int!null)
 ├── grouping columns: a.x:1(int!null) z:6(int!null)
 ├── stats: [rows=500000, distinct(1,6)=500000]
 ├── key: (1,6)
 ├── fd: (1,6)-->(8)
 ├── project
 │    ├── columns: a.x:1(int!null) z:6(int!null)
 │    ├── stats: [rows=50000000, distinct(1,6)=500000]
 │    └── inner-join
 │         ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) b.x:5(int) z:6(int!null) rowid:7(int!null)
 │         ├── stats: [rows=50000000, distinct(1,6)=500000]
 │         ├── key: (1,7)
 │         ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(5,6)
 │         ├── scan a
 │         │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │         │    ├── stats: [rows=5000, distinct(1)=5000]
 │         │    ├── key: (1)
 │         │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │         ├── scan b
 │         │    ├── columns: b.x:5(int) z:6(int!null) rowid:7(int!null)
 │         │    ├── stats: [rows=10000, distinct(6)=100]
 │         │    ├── key: (7)
 │         │    └── fd: (7)-->(5,6)
 │         └── true [type=bool]
 └── aggregations [outer=(6)]
      └── sum [type=decimal, outer=(6)]
           └── variable: z [type=int, outer=(6)]

# Join selectivity: 1/max(distinct(a.x), distinct(b.x)) = 1/5000.
norm
SELECT sum(b.z), a.x, b.z FROM a, b WHERE a.x=b.x GROUP BY a.x, b.z
----
group-by
 ├── columns: sum:8(decimal) x:1(int!null) z:6(int!null)
 ├── grouping columns: a.x:1(int!null) z:6(int!null)
 ├── stats: [rows=10000, distinct(1,6)=10000]
 ├── key: (1,6)
 ├── fd: (1,6)-->(8)
 ├── inner-join
 │    ├── columns: a.x:1(int!null) b.x:5(int!null) z:6(int!null)
 │    ├── stats: [rows=10000, distinct(1)=500, distinct(5)=500, distinct(1,6)=10000]
 │    ├── fd: (1)==(5), (5)==(1)
 │    ├── scan a
 │    │    ├── columns: a.x:1(int!null)
 │    │    ├── stats: [rows=5000, distinct(1)=5000]
 │    │    └── key: (1)
 │    ├── scan b
 │    │    ├── columns: b.x:5(int) z:6(int!null)
 │    │    └── stats: [rows=10000, distinct(5)=500, distinct(6)=100]
 │    └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
 │         └── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
 └── aggregations [outer=(6)]
      └── sum [type=decimal, outer=(6)]
           └── variable: z [type=int, outer=(6)]

# Semi-join.
norm
SELECT * FROM a WHERE EXISTS (SELECT * FROM b WHERE a.x=b.x)
----
semi-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── stats: [rows=5000]
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null)
 │    └── stats: [rows=10000, distinct(5)=500]
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]

# Anti-join.
norm
SELECT * FROM a WHERE NOT EXISTS (SELECT * FROM b WHERE a.x=b.x)
----
anti-join
 ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 ├── stats: [rows=5000]
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null)
 │    └── stats: [rows=10000, distinct(5)=500]
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]

# Multiple equality conditions.
norm
SELECT * FROM a JOIN b ON a.x=b.x AND a.y=b.z
----
inner-join
 ├── columns: x:1(int!null) y:2(int!null) s:3(string) d:4(decimal!null) x:5(int!null) z:6(int!null)
 ├── stats: [rows=25, distinct(1)=25, distinct(2)=25, distinct(5)=25, distinct(6)=25]
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (1)==(5), (5)==(1), (2)==(6), (6)==(2)
 ├── scan a
 │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=5000, distinct(1)=5000, distinct(2)=400]
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null)
 │    └── stats: [rows=10000, distinct(5)=500, distinct(6)=100]
 └── filters [type=bool, outer=(1,2,5,6), constraints=(/1: (/NULL - ]; /2: (/NULL - ]; /5: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(5), (5)==(1), (2)==(6), (6)==(2)]
      ├── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      └── y = z [type=bool, outer=(2,6), constraints=(/2: (/NULL - ]; /6: (/NULL - ])]

# Equality condition + extra filters.
norm
SELECT * FROM a JOIN b ON a.x=b.x AND a.y+b.z=5 AND a.y > 0 AND a.y < 300
----
inner-join
 ├── columns: x:1(int!null) y:2(int!null) s:3(string) d:4(decimal!null) x:5(int!null) z:6(int!null)
 ├── stats: [rows=3333.33333, distinct(1)=500, distinct(5)=500]
 ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (1)==(5), (5)==(1)
 ├── select
 │    ├── columns: a.x:1(int!null) y:2(int!null) s:3(string) d:4(decimal!null)
 │    ├── stats: [rows=3737.5, distinct(1)=3737.5, distinct(2)=299]
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    ├── scan a
 │    │    ├── columns: a.x:1(int!null) y:2(int) s:3(string) d:4(decimal!null)
 │    │    ├── stats: [rows=5000, distinct(1)=5000, distinct(2)=400]
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2-4), (3,4)~~>(1,2)
 │    └── filters [type=bool, outer=(2), constraints=(/2: [/1 - /299]; tight)]
 │         ├── y > 0 [type=bool, outer=(2), constraints=(/2: [/1 - ]; tight)]
 │         └── y < 300 [type=bool, outer=(2), constraints=(/2: (/NULL - /299]; tight)]
 ├── scan b
 │    ├── columns: b.x:5(int) z:6(int!null)
 │    └── stats: [rows=10000, distinct(5)=500]
 └── filters [type=bool, outer=(1,2,5,6), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      ├── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      └── (y + z) = 5 [type=bool, outer=(2,6)]

# Force column statistics calculation for semi-join.
norm
SELECT count(*)
FROM (SELECT * FROM a WHERE EXISTS (SELECT * FROM b WHERE a.x=b.x AND a.y+b.z=5)) AS a
GROUP BY a.y
----
project
 ├── columns: count:8(int)
 ├── stats: [rows=400]
 └── group-by
      ├── columns: y:2(int) count_rows:8(int)
      ├── grouping columns: y:2(int)
      ├── stats: [rows=400, distinct(2)=400]
      ├── key: (2)
      ├── fd: (2)-->(8)
      ├── semi-join
      │    ├── columns: a.x:1(int!null) y:2(int)
      │    ├── stats: [rows=5000, distinct(2)=400]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: a.x:1(int!null) y:2(int)
      │    │    ├── stats: [rows=5000, distinct(2)=400]
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan b
      │    │    ├── columns: b.x:5(int) z:6(int!null)
      │    │    └── stats: [rows=10000, distinct(5)=500]
      │    └── filters [type=bool, outer=(1,2,5,6), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      │         ├── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      │         └── (y + z) = 5 [type=bool, outer=(2,6)]
      └── aggregations
           └── count-rows [type=int]

# Force column statistics calculation for anti-join.
norm
SELECT count(*)
FROM (SELECT * FROM a WHERE NOT EXISTS (SELECT * FROM b WHERE a.x=b.x AND a.y+b.z=5)) AS a
GROUP BY a.y
----
project
 ├── columns: count:8(int)
 ├── stats: [rows=400]
 └── group-by
      ├── columns: y:2(int) count_rows:8(int)
      ├── grouping columns: y:2(int)
      ├── stats: [rows=400, distinct(2)=400]
      ├── key: (2)
      ├── fd: (2)-->(8)
      ├── anti-join
      │    ├── columns: a.x:1(int!null) y:2(int)
      │    ├── stats: [rows=5000, distinct(2)=400]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: a.x:1(int!null) y:2(int)
      │    │    ├── stats: [rows=5000, distinct(2)=400]
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan b
      │    │    ├── columns: b.x:5(int) z:6(int!null)
      │    │    └── stats: [rows=10000, distinct(5)=500]
      │    └── filters [type=bool, outer=(1,2,5,6), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      │         ├── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      │         └── (y + z) = 5 [type=bool, outer=(2,6)]
      └── aggregations
           └── count-rows [type=int]

# Force column statistics calculation for left join.
norm
SELECT count(*)
FROM (SELECT * FROM a LEFT OUTER JOIN b ON a.x=b.x AND a.y+b.z=5) AS a
GROUP BY a.y
----
project
 ├── columns: count:8(int)
 ├── stats: [rows=400]
 └── group-by
      ├── columns: y:2(int) count_rows:8(int)
      ├── grouping columns: y:2(int)
      ├── stats: [rows=400, distinct(2)=400]
      ├── key: (2)
      ├── fd: (2)-->(8)
      ├── left-join
      │    ├── columns: a.x:1(int!null) y:2(int) b.x:5(int) z:6(int)
      │    ├── stats: [rows=5000, distinct(2)=400, distinct(5)=500]
      │    ├── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: a.x:1(int!null) y:2(int)
      │    │    ├── stats: [rows=5000, distinct(1)=5000, distinct(2)=400]
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan b
      │    │    ├── columns: b.x:5(int) z:6(int!null)
      │    │    └── stats: [rows=10000, distinct(5)=500]
      │    └── filters [type=bool, outer=(1,2,5,6), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      │         ├── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      │         └── (y + z) = 5 [type=bool, outer=(2,6)]
      └── aggregations
           └── count-rows [type=int]

# Force column statistics calculation for right join.
norm
SELECT count(*)
FROM (SELECT * FROM a RIGHT OUTER JOIN b ON a.x=b.x AND a.y+b.z=5) AS a
GROUP BY a.y
----
project
 ├── columns: count:8(int)
 ├── stats: [rows=399.903879]
 └── group-by
      ├── columns: y:2(int) count_rows:8(int)
      ├── grouping columns: y:2(int)
      ├── stats: [rows=399.903879, distinct(2)=399.903879]
      ├── key: (2)
      ├── fd: (2)-->(8)
      ├── right-join
      │    ├── columns: a.x:1(int) y:2(int) b.x:5(int) z:6(int!null)
      │    ├── stats: [rows=10000, distinct(1)=500, distinct(2)=399.903879]
      │    ├── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: a.x:1(int!null) y:2(int)
      │    │    ├── stats: [rows=5000, distinct(1)=5000, distinct(2)=400]
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan b
      │    │    ├── columns: b.x:5(int) z:6(int!null)
      │    │    └── stats: [rows=10000, distinct(5)=500]
      │    └── filters [type=bool, outer=(1,2,5,6), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      │         ├── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      │         └── (y + z) = 5 [type=bool, outer=(2,6)]
      └── aggregations
           └── count-rows [type=int]

# Force column statistics calculation for outer join.
norm
SELECT count(*)
FROM (SELECT * FROM a FULL OUTER JOIN b ON a.x=b.x AND a.y+b.z=5) AS a
GROUP BY a.y
----
project
 ├── columns: count:8(int)
 ├── stats: [rows=400]
 └── group-by
      ├── columns: y:2(int) count_rows:8(int)
      ├── grouping columns: y:2(int)
      ├── stats: [rows=400, distinct(2)=400]
      ├── key: (2)
      ├── fd: (2)-->(8)
      ├── full-join
      │    ├── columns: a.x:1(int) y:2(int) b.x:5(int) z:6(int)
      │    ├── stats: [rows=11666.6667, distinct(2)=400]
      │    ├── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: a.x:1(int!null) y:2(int)
      │    │    ├── stats: [rows=5000, distinct(1)=5000, distinct(2)=400]
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan b
      │    │    ├── columns: b.x:5(int) z:6(int!null)
      │    │    └── stats: [rows=10000, distinct(5)=500]
      │    └── filters [type=bool, outer=(1,2,5,6), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      │         ├── a.x = b.x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      │         └── (y + z) = 5 [type=bool, outer=(2,6)]
      └── aggregations
           └── count-rows [type=int]

exec-ddl
CREATE TABLE uvw (u INT, v INT, w INT)
----
TABLE uvw
 ├── u int
 ├── v int
 ├── w int
 ├── rowid int not null (hidden)
 └── INDEX primary
      └── rowid int not null (hidden)

exec-ddl
CREATE TABLE xyz (x INT, y INT, z INT)
----
TABLE xyz
 ├── x int
 ├── y int
 ├── z int
 ├── rowid int not null (hidden)
 └── INDEX primary
      └── rowid int not null (hidden)

# Verify that two equivalent formulations of a join lead to similar statistics.
# In the first case, x=10 is pushed down; in the second case it is part of the
# ON condition. The latter formulation happens in practice when we convert to
# lookup join (we incorporate the filter back into the ON condition).

norm disable=(PushFilterIntoJoinLeftAndRight,PushFilterIntoJoinLeft,PushFilterIntoJoinRight,MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT * FROM (SELECT * FROM uvw WHERE w=1) JOIN (SELECT * FROM xyz WHERE x=10) ON u=x
----
inner-join
 ├── columns: u:1(int!null) v:2(int) w:3(int!null) x:5(int!null) y:6(int) z:7(int)
 ├── stats: [rows=10.4582901, distinct(1)=1, distinct(5)=1]
 ├── fd: ()-->(1,3,5), (1)==(5), (5)==(1)
 ├── select
 │    ├── columns: u:1(int) v:2(int) w:3(int!null)
 │    ├── stats: [rows=10, distinct(1)=9.5617925, distinct(3)=1]
 │    ├── fd: ()-->(3)
 │    ├── scan uvw
 │    │    ├── columns: u:1(int) v:2(int) w:3(int)
 │    │    └── stats: [rows=1000, distinct(1)=100, distinct(3)=100]
 │    └── filters [type=bool, outer=(3), constraints=(/3: [/1 - /1]; tight), fd=()-->(3)]
 │         └── w = 1 [type=bool, outer=(3), constraints=(/3: [/1 - /1]; tight)]
 ├── select
 │    ├── columns: x:5(int!null) y:6(int) z:7(int)
 │    ├── stats: [rows=10, distinct(5)=1]
 │    ├── fd: ()-->(5)
 │    ├── scan xyz
 │    │    ├── columns: x:5(int) y:6(int) z:7(int)
 │    │    └── stats: [rows=1000, distinct(5)=100]
 │    └── filters [type=bool, outer=(5), constraints=(/5: [/10 - /10]; tight), fd=()-->(5)]
 │         └── x = 10 [type=bool, outer=(5), constraints=(/5: [/10 - /10]; tight)]
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]
      └── u = x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]

norm disable=(PushFilterIntoJoinLeftAndRight,PushFilterIntoJoinLeft,PushFilterIntoJoinRight,MapFilterIntoJoinLeft,MapFilterIntoJoinRight)
SELECT * FROM (SELECT * FROM uvw WHERE w=1) JOIN xyz ON u=x AND x=10
----
inner-join
 ├── columns: u:1(int!null) v:2(int) w:3(int!null) x:5(int!null) y:6(int) z:7(int)
 ├── stats: [rows=10.4582901, distinct(1)=1, distinct(5)=1]
 ├── fd: ()-->(1,3,5), (1)==(5), (5)==(1)
 ├── select
 │    ├── columns: u:1(int) v:2(int) w:3(int!null)
 │    ├── stats: [rows=10, distinct(1)=9.5617925, distinct(3)=1]
 │    ├── fd: ()-->(3)
 │    ├── scan uvw
 │    │    ├── columns: u:1(int) v:2(int) w:3(int)
 │    │    └── stats: [rows=1000, distinct(1)=100, distinct(3)=100]
 │    └── filters [type=bool, outer=(3), constraints=(/3: [/1 - /1]; tight), fd=()-->(3)]
 │         └── w = 1 [type=bool, outer=(3), constraints=(/3: [/1 - /1]; tight)]
 ├── scan xyz
 │    ├── columns: x:5(int) y:6(int) z:7(int)
 │    └── stats: [rows=1000, distinct(5)=100]
 └── filters [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: [/10 - /10]), fd=()-->(1,5), (1)==(5), (5)==(1)]
      ├── u = x [type=bool, outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ])]
      └── x = 10 [type=bool, outer=(5), constraints=(/5: [/10 - /10]; tight)]
