# LogicTest: local

statement ok
CREATE TABLE onecolumn (x INT); INSERT INTO onecolumn(x) VALUES (44), (NULL), (42)

statement ok
CREATE TABLE twocolumn (x INT, y INT); INSERT INTO twocolumn(x, y) VALUES (44,51), (NULL,52), (42,53), (45,45)

## Simple test cases for inner, left, right, and outer joins

query T
EXPLAIN SELECT * FROM onecolumn JOIN twocolumn USING(x)
----
distribution: local
vectorized: true
·
• hash join
│ equality: (x) = (x)
│
├── • scan
│     missing stats
│     table: onecolumn@onecolumn_pkey
│     spans: FULL SCAN
│
└── • scan
      missing stats
      table: twocolumn@twocolumn_pkey
      spans: FULL SCAN

query T
EXPLAIN SELECT * FROM twocolumn AS a JOIN twocolumn AS b ON a.x = b.y
----
distribution: local
vectorized: true
·
• hash join
│ equality: (x) = (y)
│
├── • scan
│     missing stats
│     table: twocolumn@twocolumn_pkey
│     spans: FULL SCAN
│
└── • scan
      missing stats
      table: twocolumn@twocolumn_pkey
      spans: FULL SCAN

query T
EXPLAIN SELECT * FROM twocolumn AS a JOIN twocolumn AS b ON a.x = 44
----
distribution: local
vectorized: true
·
• cross join
│
├── • scan
│     missing stats
│     table: twocolumn@twocolumn_pkey
│     spans: FULL SCAN
│
└── • filter
    │ filter: x = 44
    │
    └── • scan
          missing stats
          table: twocolumn@twocolumn_pkey
          spans: FULL SCAN

query T
EXPLAIN SELECT * FROM onecolumn AS a JOIN twocolumn AS b ON ((a.x)) = ((b.y))
----
distribution: local
vectorized: true
·
• hash join
│ equality: (x) = (y)
│
├── • scan
│     missing stats
│     table: onecolumn@onecolumn_pkey
│     spans: FULL SCAN
│
└── • scan
      missing stats
      table: twocolumn@twocolumn_pkey
      spans: FULL SCAN

query T
EXPLAIN SELECT * FROM onecolumn JOIN twocolumn ON onecolumn.x = twocolumn.y
----
distribution: local
vectorized: true
·
• hash join
│ equality: (x) = (y)
│
├── • scan
│     missing stats
│     table: onecolumn@onecolumn_pkey
│     spans: FULL SCAN
│
└── • scan
      missing stats
      table: twocolumn@twocolumn_pkey
      spans: FULL SCAN

query T
EXPLAIN SELECT * FROM
  onecolumn
  CROSS JOIN twocolumn
  JOIN onecolumn AS a (b) ON a.b = twocolumn.x
  JOIN twocolumn AS c (d, e) ON a.b = c.d AND c.d = onecolumn.x
LIMIT 1
----
distribution: local
vectorized: true
·
• limit
│ count: 1
│
└── • hash join
    │ equality: (x) = (x)
    │
    ├── • hash join
    │   │ equality: (x) = (x)
    │   │
    │   ├── • scan
    │   │     missing stats
    │   │     table: onecolumn@onecolumn_pkey
    │   │     spans: FULL SCAN
    │   │
    │   └── • scan
    │         missing stats
    │         table: twocolumn@twocolumn_pkey
    │         spans: FULL SCAN
    │
    └── • hash join
        │ equality: (x) = (x)
        │
        ├── • scan
        │     missing stats
        │     table: onecolumn@onecolumn_pkey
        │     spans: FULL SCAN
        │
        └── • scan
              missing stats
              table: twocolumn@twocolumn_pkey
              spans: FULL SCAN

# The following queries verify that only the necessary columns are scanned.
query T
EXPLAIN (VERBOSE) SELECT a.x, b.y FROM twocolumn AS a, twocolumn AS b
----
distribution: local
vectorized: true
·
• cross join (inner)
│ columns: (x, y)
│ estimated row count: 1,000,000 (missing stats)
│
├── • scan
│     columns: (x)
│     estimated row count: 1,000 (missing stats)
│     table: twocolumn@twocolumn_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (y)
      estimated row count: 1,000 (missing stats)
      table: twocolumn@twocolumn_pkey
      spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT b.y FROM (twocolumn AS a JOIN twocolumn AS b USING(x))
----
distribution: local
vectorized: true
·
• project
│ columns: (y)
│
└── • hash join (inner)
    │ columns: (x, x, y)
    │ estimated row count: 9,801 (missing stats)
    │ equality: (x) = (x)
    │
    ├── • scan
    │     columns: (x)
    │     estimated row count: 1,000 (missing stats)
    │     table: twocolumn@twocolumn_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (x, y)
          estimated row count: 1,000 (missing stats)
          table: twocolumn@twocolumn_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT b.y FROM (twocolumn AS a JOIN twocolumn AS b ON a.x = b.x)
----
distribution: local
vectorized: true
·
• project
│ columns: (y)
│
└── • hash join (inner)
    │ columns: (x, x, y)
    │ estimated row count: 9,801 (missing stats)
    │ equality: (x) = (x)
    │
    ├── • scan
    │     columns: (x)
    │     estimated row count: 1,000 (missing stats)
    │     table: twocolumn@twocolumn_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (x, y)
          estimated row count: 1,000 (missing stats)
          table: twocolumn@twocolumn_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT a.x FROM (twocolumn AS a JOIN twocolumn AS b ON a.x < b.y)
----
distribution: local
vectorized: true
·
• project
│ columns: (x)
│
└── • cross join (inner)
    │ columns: (x, y)
    │ estimated row count: 326,700 (missing stats)
    │ pred: x < y
    │
    ├── • scan
    │     columns: (x)
    │     estimated row count: 1,000 (missing stats)
    │     table: twocolumn@twocolumn_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (y)
          estimated row count: 1,000 (missing stats)
          table: twocolumn@twocolumn_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT * FROM (SELECT x, 2 two FROM onecolumn) NATURAL FULL JOIN (SELECT x, y+1 plus1 FROM twocolumn)
----
distribution: local
vectorized: true
·
• render
│ columns: (x, two, plus1)
│ render x: COALESCE(x, x)
│ render two: two
│ render plus1: plus1
│
└── • hash join (full outer)
    │ columns: (two, x, plus1, x)
    │ estimated row count: 10,000 (missing stats)
    │ equality: (x) = (x)
    │
    ├── • render
    │   │ columns: (two, x)
    │   │ render two: 2
    │   │ render x: x
    │   │
    │   └── • scan
    │         columns: (x)
    │         estimated row count: 1,000 (missing stats)
    │         table: onecolumn@onecolumn_pkey
    │         spans: FULL SCAN
    │
    └── • render
        │ columns: (plus1, x)
        │ render plus1: y + 1
        │ render x: x
        │
        └── • scan
              columns: (x, y)
              estimated row count: 1,000 (missing stats)
              table: twocolumn@twocolumn_pkey
              spans: FULL SCAN

# Ensure that the ordering information for the result of joins is sane. (#12037)
query T
EXPLAIN (VERBOSE) SELECT * FROM (SELECT * FROM (VALUES (9, 1), (8, 2)) AS a (u, k) ORDER BY k)
                  INNER JOIN (VALUES (1, 1), (2, 2)) AS b (k, w) USING (k) ORDER BY u
----
distribution: local
vectorized: true
·
• sort
│ columns: (k, u, w)
│ estimated row count: 2
│ order: +column1
│
└── • project
    │ columns: (column1, column2, column2)
    │
    └── • hash join (inner)
        │ columns: (column1, column2, column1, column2)
        │ estimated row count: 2
        │ equality: (column2) = (column1)
        │
        ├── • values
        │     columns: (column1, column2)
        │     size: 2 columns, 2 rows
        │     row 0, expr 0: 9
        │     row 0, expr 1: 1
        │     row 1, expr 0: 8
        │     row 1, expr 1: 2
        │
        └── • values
              columns: (column1, column2)
              size: 2 columns, 2 rows
              row 0, expr 0: 1
              row 0, expr 1: 1
              row 1, expr 0: 2
              row 1, expr 1: 2

# Ensure that large cross-joins are optimized somehow (#10633)
statement ok
CREATE TABLE customers(id INT PRIMARY KEY NOT NULL); CREATE TABLE orders(id INT, cust INT REFERENCES customers(id))

query T
EXPLAIN (VERBOSE) SELECT
       NULL::text  AS pktable_cat,
       pkn.nspname AS pktable_schem,
       pkc.relname AS pktable_name,
       pka.attname AS pkcolumn_name,
       NULL::text  AS fktable_cat,
       fkn.nspname AS fktable_schem,
       fkc.relname AS fktable_name,
       fka.attname AS fkcolumn_name,
       pos.n       AS key_seq,
       CASE con.confupdtype
            WHEN 'c' THEN 0
            WHEN 'n' THEN 2
            WHEN 'd' THEN 4
            WHEN 'r' THEN 1
            WHEN 'a' THEN 3
            ELSE NULL
       END AS update_rule,
       CASE con.confdeltype
            WHEN 'c' THEN 0
            WHEN 'n' THEN 2
            WHEN 'd' THEN 4
            WHEN 'r' THEN 1
            WHEN 'a' THEN 3
            ELSE NULL
       END          AS delete_rule,
       con.conname  AS fk_name,
       pkic.relname AS pk_name,
       CASE
            WHEN con.condeferrable
            AND      con.condeferred THEN 5
            WHEN con.condeferrable THEN 6
            ELSE 7
       END AS deferrability
  FROM     pg_catalog.pg_namespace pkn,
       pg_catalog.pg_class pkc,
       pg_catalog.pg_attribute pka,
       pg_catalog.pg_namespace fkn,
       pg_catalog.pg_class fkc,
       pg_catalog.pg_attribute fka,
       pg_catalog.pg_constraint con,
       pg_catalog.generate_series(1, 32) pos(n),
       pg_catalog.pg_depend dep,
       pg_catalog.pg_class pkic
  WHERE    pkn.oid = pkc.relnamespace
  AND      pkc.oid = pka.attrelid
  AND      pka.attnum = con.confkey[pos.n]
  AND      con.confrelid = pkc.oid
  AND      fkn.oid = fkc.relnamespace
  AND      fkc.oid = fka.attrelid
  AND      fka.attnum = con.conkey[pos.n]
  AND      con.conrelid = fkc.oid
  AND      con.contype = 'f'
  AND      con.oid = dep.objid
  AND      pkic.oid = dep.refobjid
  AND      pkic.relkind = 'i'
  AND      fkn.nspname = 'public'
  AND      fkc.relname = 'orders'
  ORDER BY pkn.nspname,
           pkc.relname,
           con.conname,
           pos.n
----
distribution: local
vectorized: true
·
• project
│ columns: (pktable_cat, pktable_schem, pktable_name, pkcolumn_name, fktable_cat, fktable_schem, fktable_name, fkcolumn_name, key_seq, update_rule, delete_rule, fk_name, pk_name, deferrability)
│
└── • sort
    │ columns: (pktable_cat, update_rule, delete_rule, deferrability, nspname, relname, attname, nspname, relname, attname, conname, generate_series, relname)
    │ estimated row count: 110,908 (missing stats)
    │ order: +nspname,+relname,+conname,+generate_series
    │
    └── • render
        │ columns: (pktable_cat, update_rule, delete_rule, deferrability, nspname, relname, attname, nspname, relname, attname, conname, generate_series, relname)
        │ render pktable_cat: CAST(NULL AS STRING)
        │ render update_rule: CASE confupdtype WHEN 'c' THEN 0 WHEN 'n' THEN 2 WHEN 'd' THEN 4 WHEN 'r' THEN 1 WHEN 'a' THEN 3 ELSE CAST(NULL AS INT8) END
        │ render delete_rule: CASE confdeltype WHEN 'c' THEN 0 WHEN 'n' THEN 2 WHEN 'd' THEN 4 WHEN 'r' THEN 1 WHEN 'a' THEN 3 ELSE CAST(NULL AS INT8) END
        │ render deferrability: CASE WHEN condeferrable AND condeferred THEN 5 WHEN condeferrable THEN 6 ELSE 7 END
        │ render nspname: nspname
        │ render relname: relname
        │ render attname: attname
        │ render nspname: nspname
        │ render relname: relname
        │ render attname: attname
        │ render conname: conname
        │ render generate_series: generate_series
        │ render relname: relname
        │
        └── • hash join (inner)
            │ columns: (oid, nspname, oid, relname, relnamespace, attrelid, attname, attnum, attrelid, attname, attnum, objid, refobjid, oid, relname, relkind, oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey, oid, relname, relnamespace, oid, nspname, generate_series)
            │ estimated row count: 110,908 (missing stats)
            │ equality: (oid) = (attrelid)
            │
            ├── • hash join (inner)
            │   │ columns: (oid, nspname, oid, relname, relnamespace)
            │   │ estimated row count: 9,801 (missing stats)
            │   │ equality: (oid) = (relnamespace)
            │   │
            │   ├── • virtual table
            │   │     columns: (oid, nspname)
            │   │     estimated row count: 1,000 (missing stats)
            │   │     table: pg_namespace@primary
            │   │
            │   └── • virtual table
            │         columns: (oid, relname, relnamespace)
            │         estimated row count: 1,000 (missing stats)
            │         table: pg_class@primary
            │
            └── • hash join (inner)
                │ columns: (attrelid, attname, attnum, attrelid, attname, attnum, objid, refobjid, oid, relname, relkind, oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey, oid, relname, relnamespace, oid, nspname, generate_series)
                │ estimated row count: 1,779 (missing stats)
                │ equality: (attrelid) = (oid)
                │ pred: attnum = conkey[generate_series]
                │
                ├── • virtual table
                │     columns: (attrelid, attname, attnum)
                │     estimated row count: 1,000 (missing stats)
                │     table: pg_attribute@primary
                │
                └── • cross join (inner)
                    │ columns: (attrelid, attname, attnum, objid, refobjid, oid, relname, relkind, oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey, oid, relname, relnamespace, oid, nspname, generate_series)
                    │ estimated row count: 539 (missing stats)
                    │ pred: attnum = confkey[generate_series]
                    │
                    ├── • hash join (inner)
                    │   │ columns: (attrelid, attname, attnum, objid, refobjid, oid, relname, relkind, oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey, oid, relname, relnamespace, oid, nspname)
                    │   │ estimated row count: 163 (missing stats)
                    │   │ equality: (attrelid) = (confrelid)
                    │   │
                    │   ├── • virtual table
                    │   │     columns: (attrelid, attname, attnum)
                    │   │     estimated row count: 1,000 (missing stats)
                    │   │     table: pg_attribute@primary
                    │   │
                    │   └── • hash join (inner)
                    │       │ columns: (objid, refobjid, oid, relname, relkind, oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey, oid, relname, relnamespace, oid, nspname)
                    │       │ estimated row count: 17 (missing stats)
                    │       │ equality: (objid) = (oid)
                    │       │
                    │       ├── • hash join (inner)
                    │       │   │ columns: (objid, refobjid, oid, relname, relkind)
                    │       │   │ estimated row count: 99 (missing stats)
                    │       │   │ equality: (refobjid) = (oid)
                    │       │   │
                    │       │   ├── • virtual table
                    │       │   │     columns: (objid, refobjid)
                    │       │   │     estimated row count: 1,000 (missing stats)
                    │       │   │     table: pg_depend@primary
                    │       │   │
                    │       │   └── • filter
                    │       │       │ columns: (oid, relname, relkind)
                    │       │       │ estimated row count: 10 (missing stats)
                    │       │       │ filter: relkind = 'i'
                    │       │       │
                    │       │       └── • virtual table
                    │       │             columns: (oid, relname, relkind)
                    │       │             estimated row count: 1,000 (missing stats)
                    │       │             table: pg_class@primary
                    │       │
                    │       └── • hash join (inner)
                    │           │ columns: (oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey, oid, relname, relnamespace, oid, nspname)
                    │           │ estimated row count: 11 (missing stats)
                    │           │ equality: (relnamespace) = (oid)
                    │           │
                    │           ├── • virtual table lookup join (inner)
                    │           │   │ columns: (oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey, oid, relname, relnamespace)
                    │           │   │ estimated row count: 10 (missing stats)
                    │           │   │ table: pg_class@pg_class_oid_idx
                    │           │   │ equality: (conrelid) = (oid)
                    │           │   │ pred: relname = 'orders'
                    │           │   │
                    │           │   └── • filter
                    │           │       │ columns: (oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey)
                    │           │       │ estimated row count: 10 (missing stats)
                    │           │       │ filter: contype = 'f'
                    │           │       │
                    │           │       └── • virtual table
                    │           │             columns: (oid, conname, contype, condeferrable, condeferred, conrelid, confrelid, confupdtype, confdeltype, conkey, confkey)
                    │           │             estimated row count: 1,000 (missing stats)
                    │           │             table: pg_constraint@primary
                    │           │
                    │           └── • filter
                    │               │ columns: (oid, nspname)
                    │               │ estimated row count: 10 (missing stats)
                    │               │ filter: nspname = 'public'
                    │               │
                    │               └── • virtual table
                    │                     columns: (oid, nspname)
                    │                     estimated row count: 1,000 (missing stats)
                    │                     table: pg_namespace@primary
                    │
                    └── • project set
                        │ columns: (generate_series)
                        │ estimated row count: 10
                        │ render 0: generate_series(1, 32)
                        │
                        └── • emptyrow
                              columns: ()

# Ensure that left joins on non-null foreign keys turn into inner joins
statement ok
CREATE TABLE cards(id INT PRIMARY KEY, cust INT NOT NULL REFERENCES customers(id), INDEX (cust))

query T
EXPLAIN SELECT * FROM cards LEFT OUTER JOIN customers ON customers.id = cards.cust
----
distribution: local
vectorized: true
·
• render
│
└── • scan
      missing stats
      table: cards@cards_pkey
      spans: FULL SCAN

# Tests for filter propagation through joins.

statement ok
CREATE TABLE square (n INT PRIMARY KEY, sq INT)

statement ok
CREATE TABLE pairs (a INT, b INT)

# The filter expression becomes an equality constraint.
query T
EXPLAIN SELECT * FROM pairs, square WHERE pairs.b = square.n
----
distribution: local
vectorized: true
·
• hash join
│ equality: (b) = (n)
│ right cols are key
│
├── • scan
│     missing stats
│     table: pairs@pairs_pkey
│     spans: FULL SCAN
│
└── • scan
      missing stats
      table: square@square_pkey
      spans: FULL SCAN

# The filter expression becomes an ON predicate.
query T
EXPLAIN (VERBOSE) SELECT * FROM pairs, square WHERE pairs.a + pairs.b = square.sq
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, n, sq)
│
└── • hash join (inner)
    │ columns: (column14, a, b, n, sq)
    │ estimated row count: 990 (missing stats)
    │ equality: (column14) = (sq)
    │
    ├── • render
    │   │ columns: (column14, a, b)
    │   │ render column14: a + b
    │   │ render a: a
    │   │ render b: b
    │   │
    │   └── • scan
    │         columns: (a, b)
    │         estimated row count: 1,000 (missing stats)
    │         table: pairs@pairs_pkey
    │         spans: FULL SCAN
    │
    └── • scan
          columns: (n, sq)
          estimated row count: 1,000 (missing stats)
          table: square@square_pkey
          spans: FULL SCAN

# Query similar to the one above, but the filter refers to a rendered
# expression and can't "break through".
query T
EXPLAIN (VERBOSE) SELECT a, b, n, sq FROM (SELECT a, b, a * b / 2 AS div, n, sq FROM pairs, square) WHERE div = sq
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, n, sq)
│
└── • filter
    │ columns: (div, a, b, n, sq)
    │ estimated row count: 990 (missing stats)
    │ filter: div = sq
    │
    └── • render
        │ columns: (div, a, b, n, sq)
        │ render div: (a * b) / 2
        │ render a: a
        │ render b: b
        │ render n: n
        │ render sq: sq
        │
        └── • cross join (inner)
            │ columns: (a, b, n, sq)
            │ estimated row count: 1,000,000 (missing stats)
            │
            ├── • scan
            │     columns: (a, b)
            │     estimated row count: 1,000 (missing stats)
            │     table: pairs@pairs_pkey
            │     spans: FULL SCAN
            │
            └── • scan
                  columns: (n, sq)
                  estimated row count: 1,000 (missing stats)
                  table: square@square_pkey
                  spans: FULL SCAN

# The filter expression must stay on top of the outer join.
query T
EXPLAIN (VERBOSE) SELECT * FROM pairs FULL OUTER JOIN square ON pairs.a + pairs.b = square.sq
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, n, sq)
│
└── • hash join (full outer)
    │ columns: (column14, a, b, n, sq)
    │ estimated row count: 1,000 (missing stats)
    │ equality: (column14) = (sq)
    │
    ├── • render
    │   │ columns: (column14, a, b)
    │   │ render column14: a + b
    │   │ render a: a
    │   │ render b: b
    │   │
    │   └── • scan
    │         columns: (a, b)
    │         estimated row count: 1,000 (missing stats)
    │         table: pairs@pairs_pkey
    │         spans: FULL SCAN
    │
    └── • scan
          columns: (n, sq)
          estimated row count: 1,000 (missing stats)
          table: square@square_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT * FROM pairs FULL OUTER JOIN square ON pairs.a + pairs.b = square.sq WHERE pairs.b%2 <> square.sq%2
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, n, sq)
│
└── • filter
    │ columns: (column14, a, b, n, sq)
    │ estimated row count: 333 (missing stats)
    │ filter: (b % 2) != (sq % 2)
    │
    └── • hash join (full outer)
        │ columns: (column14, a, b, n, sq)
        │ estimated row count: 1,000 (missing stats)
        │ equality: (column14) = (sq)
        │
        ├── • render
        │   │ columns: (column14, a, b)
        │   │ render column14: a + b
        │   │ render a: a
        │   │ render b: b
        │   │
        │   └── • scan
        │         columns: (a, b)
        │         estimated row count: 1,000 (missing stats)
        │         table: pairs@pairs_pkey
        │         spans: FULL SCAN
        │
        └── • scan
              columns: (n, sq)
              estimated row count: 1,000 (missing stats)
              table: square@square_pkey
              spans: FULL SCAN

# Filter propagation through outer joins.

query T
EXPLAIN (VERBOSE)
SELECT *
  FROM (SELECT * FROM pairs LEFT JOIN square ON b = sq AND a > 1 AND n < 6)
 WHERE b > 1 AND (n IS NULL OR n > 1) AND (n IS NULL OR a  < sq)
----
distribution: local
vectorized: true
·
• filter
│ columns: (a, b, n, sq)
│ estimated row count: 116 (missing stats)
│ filter: ((n IS NULL) OR (n > 1)) AND ((n IS NULL) OR (a < sq))
│
└── • hash join (left outer)
    │ columns: (a, b, n, sq)
    │ estimated row count: 1,037 (missing stats)
    │ equality: (b) = (sq)
    │ pred: a > 1
    │
    ├── • filter
    │   │ columns: (a, b)
    │   │ estimated row count: 333 (missing stats)
    │   │ filter: b > 1
    │   │
    │   └── • scan
    │         columns: (a, b)
    │         estimated row count: 1,000 (missing stats)
    │         table: pairs@pairs_pkey
    │         spans: FULL SCAN
    │
    └── • filter
        │ columns: (n, sq)
        │ estimated row count: 311 (missing stats)
        │ filter: sq > 1
        │
        └── • scan
              columns: (n, sq)
              estimated row count: 333 (missing stats)
              table: square@square_pkey
              spans: -/6

query T
EXPLAIN (VERBOSE)
SELECT *
  FROM (SELECT * FROM pairs RIGHT JOIN square ON b = sq AND a > 1 AND n < 6)
 WHERE (a IS NULL OR a > 2) AND n > 1 AND (a IS NULL OR a < sq)
----
distribution: local
vectorized: true
·
• filter
│ columns: (a, b, n, sq)
│ estimated row count: 44 (missing stats)
│ filter: ((a IS NULL) OR (a > 2)) AND ((a IS NULL) OR (a < sq))
│
└── • hash join (left outer)
    │ columns: (n, sq, a, b)
    │ estimated row count: 377 (missing stats)
    │ equality: (sq) = (b)
    │ pred: n < 6
    │
    ├── • scan
    │     columns: (n, sq)
    │     estimated row count: 333 (missing stats)
    │     table: square@square_pkey
    │     spans: /2-
    │
    └── • filter
        │ columns: (a, b)
        │ estimated row count: 333 (missing stats)
        │ filter: a > 1
        │
        └── • scan
              columns: (a, b)
              estimated row count: 1,000 (missing stats)
              table: pairs@pairs_pkey
              spans: FULL SCAN

# The simpler plan for an inner join, to compare.
query T
EXPLAIN (VERBOSE)
SELECT *
  FROM (SELECT * FROM pairs JOIN square ON b = sq AND a > 1 AND n < 6)
 WHERE (a IS NULL OR a > 2) AND n > 1 AND (a IS NULL OR a < sq)
----
distribution: local
vectorized: true
·
• hash join (inner)
│ columns: (a, b, n, sq)
│ estimated row count: 6 (missing stats)
│ equality: (b) = (sq)
│
├── • filter
│   │ columns: (a, b)
│   │ estimated row count: 113 (missing stats)
│   │ filter: ((a > 1) AND ((a IS NULL) OR (a > 2))) AND ((a IS NULL) OR (a < b))
│   │
│   └── • scan
│         columns: (a, b)
│         estimated row count: 1,000 (missing stats)
│         table: pairs@pairs_pkey
│         spans: FULL SCAN
│
└── • scan
      columns: (n, sq)
      estimated row count: 4 (missing stats)
      table: square@square_pkey
      spans: /2-/6
      parallel


statement ok
CREATE TABLE t1 (col1 INT, x INT, col2 INT, y INT)

statement ok
CREATE TABLE t2 (col3 INT, y INT, x INT, col4 INT)

query T
EXPLAIN (VERBOSE) SELECT x FROM t1 NATURAL JOIN (SELECT * FROM t2)
----
distribution: local
vectorized: true
·
• project
│ columns: (x)
│
└── • hash join (inner)
    │ columns: (x, y, y, x)
    │ estimated row count: 96 (missing stats)
    │ equality: (x, y) = (x, y)
    │
    ├── • scan
    │     columns: (x, y)
    │     estimated row count: 1,000 (missing stats)
    │     table: t1@t1_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (y, x)
          estimated row count: 1,000 (missing stats)
          table: t2@t2_pkey
          spans: FULL SCAN

# Tests for merge join ordering information.
statement ok
CREATE TABLE pkBA (a INT, b INT, c INT, d INT, PRIMARY KEY(b,a))

statement ok
CREATE TABLE pkBC (a INT, b INT, c INT, d INT, PRIMARY KEY(b,c))

statement ok
CREATE TABLE pkBAC (a INT, b INT, c INT, d INT, PRIMARY KEY(b,a,c))

statement ok
CREATE TABLE pkBAD (a INT, b INT, c INT, d INT, PRIMARY KEY(b,a,d))

query T
EXPLAIN (VERBOSE) SELECT * FROM pkBA AS l JOIN pkBC AS r ON l.a = r.a AND l.b = r.b AND l.c = r.c
----
distribution: local
vectorized: true
·
• hash join (inner)
│ columns: (a, b, c, d, a, b, c, d)
│ estimated row count: 1 (missing stats)
│ equality: (a, b, c) = (a, b, c)
│ left cols are key
│ right cols are key
│
├── • scan
│     columns: (a, b, c, d)
│     estimated row count: 1,000 (missing stats)
│     table: pkba@pkba_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (a, b, c, d)
      estimated row count: 1,000 (missing stats)
      table: pkbc@pkbc_pkey
      spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT * FROM pkBA NATURAL JOIN pkBAD
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, c, d)
│
└── • merge join (inner)
    │ columns: (a, b, c, d, a, b, c, d)
    │ estimated row count: 0 (missing stats)
    │ equality: (b, a, d, c) = (b, a, d, c)
    │ left cols are key
    │ right cols are key
    │ merge ordering: +"(b=b)",+"(a=a)",+"(d=d)",+"(c=c)"
    │
    ├── • scan
    │     columns: (a, b, c, d)
    │     ordering: +b,+a,+d
    │     estimated row count: 1,000 (missing stats)
    │     table: pkbad@pkbad_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (a, b, c, d)
          ordering: +b,+a
          estimated row count: 1,000 (missing stats)
          table: pkba@pkba_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT * FROM pkBAC AS l JOIN pkBAC AS r USING(a, b)
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, c, d, c, d)
│
└── • merge join (inner)
    │ columns: (a, b, c, d, a, b, c, d)
    │ estimated row count: 100 (missing stats)
    │ equality: (b, a) = (b, a)
    │ merge ordering: +"(b=b)",+"(a=a)"
    │
    ├── • scan
    │     columns: (a, b, c, d)
    │     ordering: +b,+a
    │     estimated row count: 1,000 (missing stats)
    │     table: pkbac@pkbac_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (a, b, c, d)
          ordering: +b,+a
          estimated row count: 1,000 (missing stats)
          table: pkbac@pkbac_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT * FROM pkBAC AS l JOIN pkBAD AS r ON l.c = r.d AND l.a = r.a AND l.b = r.b
----
distribution: local
vectorized: true
·
• merge join (inner)
│ columns: (a, b, c, d, a, b, c, d)
│ estimated row count: 1 (missing stats)
│ equality: (b, a, c) = (b, a, d)
│ left cols are key
│ right cols are key
│ merge ordering: +"(b=b)",+"(a=a)",+"(c=d)"
│
├── • scan
│     columns: (a, b, c, d)
│     ordering: +b,+a,+c
│     estimated row count: 1,000 (missing stats)
│     table: pkbac@pkbac_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (a, b, c, d)
      ordering: +b,+a,+d
      estimated row count: 1,000 (missing stats)
      table: pkbad@pkbad_pkey
      spans: FULL SCAN

# Tests with joins with merged columns of collated string type.
statement ok
CREATE TABLE str1 (a INT PRIMARY KEY, s STRING COLLATE en_u_ks_level1)

statement ok
CREATE TABLE str2 (a INT PRIMARY KEY, s STRING COLLATE en_u_ks_level1)

query T
EXPLAIN (VERBOSE) SELECT s, str1.s, str2.s FROM str1 INNER JOIN str2 USING(s)
----
distribution: local
vectorized: true
·
• project
│ columns: (s, s, s)
│
└── • hash join (inner)
    │ columns: (s, s)
    │ estimated row count: 9,801 (missing stats)
    │ equality: (s) = (s)
    │
    ├── • scan
    │     columns: (s)
    │     estimated row count: 1,000 (missing stats)
    │     table: str1@str1_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (s)
          estimated row count: 1,000 (missing stats)
          table: str2@str2_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT s, str1.s, str2.s FROM str1 LEFT OUTER JOIN str2 USING(s)
----
distribution: local
vectorized: true
·
• project
│ columns: (s, s, s)
│
└── • hash join (left outer)
    │ columns: (s, s)
    │ estimated row count: 10,000 (missing stats)
    │ equality: (s) = (s)
    │
    ├── • scan
    │     columns: (s)
    │     estimated row count: 1,000 (missing stats)
    │     table: str1@str1_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (s)
          estimated row count: 1,000 (missing stats)
          table: str2@str2_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT s, str1.s, str2.s FROM str1 RIGHT OUTER JOIN str2 USING(s)
----
distribution: local
vectorized: true
·
• render
│ columns: (s, s, s)
│ render s: COALESCE(s, s)
│ render s: s
│ render s: s
│
└── • hash join (left outer)
    │ columns: (s, s)
    │ estimated row count: 10,000 (missing stats)
    │ equality: (s) = (s)
    │
    ├── • scan
    │     columns: (s)
    │     estimated row count: 1,000 (missing stats)
    │     table: str2@str2_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (s)
          estimated row count: 1,000 (missing stats)
          table: str1@str1_pkey
          spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT s, str1.s, str2.s FROM str1 FULL OUTER JOIN str2 USING(s)
----
distribution: local
vectorized: true
·
• render
│ columns: (s, s, s)
│ render s: COALESCE(s, s)
│ render s: s
│ render s: s
│
└── • hash join (full outer)
    │ columns: (s, s)
    │ estimated row count: 10,000 (missing stats)
    │ equality: (s) = (s)
    │
    ├── • scan
    │     columns: (s)
    │     estimated row count: 1,000 (missing stats)
    │     table: str1@str1_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (s)
          estimated row count: 1,000 (missing stats)
          table: str2@str2_pkey
          spans: FULL SCAN

# Verify that we resolve the merged column a to str2.a but use IFNULL for
# column s which is a collated string.
query T
EXPLAIN (VERBOSE) SELECT * FROM str1 RIGHT OUTER JOIN str2 USING(a, s)
----
distribution: local
vectorized: true
·
• render
│ columns: (a, s)
│ render s: COALESCE(s, s)
│ render a: a
│
└── • merge join (left outer)
    │ columns: (a, s, a, s)
    │ estimated row count: 1,000 (missing stats)
    │ equality: (a, s) = (a, s)
    │ left cols are key
    │ right cols are key
    │ merge ordering: +"(a=a)",+"(s=s)"
    │
    ├── • scan
    │     columns: (a, s)
    │     ordering: +a
    │     estimated row count: 1,000 (missing stats)
    │     table: str2@str2_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (a, s)
          ordering: +a
          estimated row count: 1,000 (missing stats)
          table: str1@str1_pkey
          spans: FULL SCAN


statement ok
CREATE TABLE xyu (x INT, y INT, u INT, PRIMARY KEY(x,y,u))

statement ok
CREATE TABLE xyv (x INT, y INT, v INT, PRIMARY KEY(x,y,v))

query T
EXPLAIN (VERBOSE) SELECT * FROM xyu INNER JOIN xyv USING(x, y) WHERE x > 2
----
distribution: local
vectorized: true
·
• project
│ columns: (x, y, u, v)
│
└── • merge join (inner)
    │ columns: (x, y, u, x, y, v)
    │ estimated row count: 34 (missing stats)
    │ equality: (x, y) = (x, y)
    │ merge ordering: +"(x=x)",+"(y=y)"
    │
    ├── • scan
    │     columns: (x, y, u)
    │     ordering: +x,+y
    │     estimated row count: 333 (missing stats)
    │     table: xyu@xyu_pkey
    │     spans: /3-
    │
    └── • scan
          columns: (x, y, v)
          ordering: +x,+y
          estimated row count: 333 (missing stats)
          table: xyv@xyv_pkey
          spans: /3-

query T
EXPLAIN (VERBOSE) SELECT * FROM xyu LEFT OUTER JOIN xyv USING(x, y) WHERE x > 2
----
distribution: local
vectorized: true
·
• project
│ columns: (x, y, u, v)
│
└── • merge join (left outer)
    │ columns: (x, y, u, x, y, v)
    │ estimated row count: 333 (missing stats)
    │ equality: (x, y) = (x, y)
    │ merge ordering: +"(x=x)",+"(y=y)"
    │
    ├── • scan
    │     columns: (x, y, u)
    │     ordering: +x,+y
    │     estimated row count: 333 (missing stats)
    │     table: xyu@xyu_pkey
    │     spans: /3-
    │
    └── • scan
          columns: (x, y, v)
          ordering: +x,+y
          estimated row count: 333 (missing stats)
          table: xyv@xyv_pkey
          spans: /3-

query T
EXPLAIN (VERBOSE) SELECT * FROM xyu RIGHT OUTER JOIN xyv USING(x, y) WHERE x > 2
----
distribution: local
vectorized: true
·
• project
│ columns: (x, y, u, v)
│
└── • merge join (left outer)
    │ columns: (x, y, v, x, y, u)
    │ estimated row count: 333 (missing stats)
    │ equality: (x, y) = (x, y)
    │ merge ordering: +"(x=x)",+"(y=y)"
    │
    ├── • scan
    │     columns: (x, y, v)
    │     ordering: +x,+y
    │     estimated row count: 333 (missing stats)
    │     table: xyv@xyv_pkey
    │     spans: /3-
    │
    └── • scan
          columns: (x, y, u)
          ordering: +x,+y
          estimated row count: 333 (missing stats)
          table: xyu@xyu_pkey
          spans: /3-

query T
EXPLAIN (VERBOSE) SELECT * FROM xyu FULL OUTER JOIN xyv USING(x, y) WHERE x > 2
----
distribution: local
vectorized: true
·
• filter
│ columns: (x, y, u, v)
│ estimated row count: 633 (missing stats)
│ filter: x > 2
│
└── • render
    │ columns: (x, y, u, v)
    │ render x: COALESCE(x, x)
    │ render y: COALESCE(y, y)
    │ render u: u
    │ render v: v
    │
    └── • merge join (full outer)
        │ columns: (x, y, u, x, y, v)
        │ estimated row count: 1,900 (missing stats)
        │ equality: (x, y) = (x, y)
        │ merge ordering: +"(x=x)",+"(y=y)"
        │
        ├── • scan
        │     columns: (x, y, u)
        │     ordering: +x,+y
        │     estimated row count: 1,000 (missing stats)
        │     table: xyu@xyu_pkey
        │     spans: FULL SCAN
        │
        └── • scan
              columns: (x, y, v)
              ordering: +x,+y
              estimated row count: 1,000 (missing stats)
              table: xyv@xyv_pkey
              spans: FULL SCAN

# Verify that we transfer constraints between the two sides.
query T
EXPLAIN (VERBOSE) SELECT * FROM xyu INNER JOIN xyv ON xyu.x = xyv.x AND xyu.y = xyv.y WHERE xyu.x = 1 AND xyu.y < 10
----
distribution: local
vectorized: true
·
• merge join (inner)
│ columns: (x, y, u, x, y, v)
│ estimated row count: 9 (missing stats)
│ equality: (y, x) = (y, x)
│ merge ordering: +"(y=y)",+"(x=x)"
│
├── • scan
│     columns: (x, y, u)
│     ordering: +y
│     estimated row count: 9 (missing stats)
│     table: xyu@xyu_pkey
│     spans: /1-/1/10
│
└── • scan
      columns: (x, y, v)
      ordering: +y
      estimated row count: 9 (missing stats)
      table: xyv@xyv_pkey
      spans: /1-/1/10

query T
EXPLAIN (VERBOSE) SELECT * FROM xyu INNER JOIN xyv ON xyu.x = xyv.x AND xyu.y = xyv.y AND xyu.x = 1 AND xyu.y < 10
----
distribution: local
vectorized: true
·
• merge join (inner)
│ columns: (x, y, u, x, y, v)
│ estimated row count: 9 (missing stats)
│ equality: (y, x) = (y, x)
│ merge ordering: +"(y=y)",+"(x=x)"
│
├── • scan
│     columns: (x, y, u)
│     ordering: +y
│     estimated row count: 9 (missing stats)
│     table: xyu@xyu_pkey
│     spans: /1-/1/10
│
└── • scan
      columns: (x, y, v)
      ordering: +y
      estimated row count: 9 (missing stats)
      table: xyv@xyv_pkey
      spans: /1-/1/10

query T
EXPLAIN (VERBOSE) SELECT * FROM xyu LEFT OUTER JOIN xyv ON xyu.x = xyv.x AND xyu.y = xyv.y AND xyu.x = 1 AND xyu.y < 10
----
distribution: local
vectorized: true
·
• merge join (left outer)
│ columns: (x, y, u, x, y, v)
│ estimated row count: 1,000 (missing stats)
│ equality: (x, y) = (x, y)
│ merge ordering: +"(x=x)",+"(y=y)"
│
├── • scan
│     columns: (x, y, u)
│     ordering: +x,+y
│     estimated row count: 1,000 (missing stats)
│     table: xyu@xyu_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (x, y, v)
      ordering: +y
      estimated row count: 9 (missing stats)
      table: xyv@xyv_pkey
      spans: /1-/1/10

query T
EXPLAIN (VERBOSE) SELECT * FROM xyu RIGHT OUTER JOIN xyv ON xyu.x = xyv.x AND xyu.y = xyv.y AND xyu.x = 1 AND xyu.y < 10
----
distribution: local
vectorized: true
·
• merge join (left outer)
│ columns: (x, y, u, x, y, v)
│ estimated row count: 1,000 (missing stats)
│ equality: (x, y) = (x, y)
│ merge ordering: +"(x=x)",+"(y=y)"
│
├── • scan
│     columns: (x, y, v)
│     ordering: +x,+y
│     estimated row count: 1,000 (missing stats)
│     table: xyv@xyv_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (x, y, u)
      ordering: +y
      estimated row count: 9 (missing stats)
      table: xyu@xyu_pkey
      spans: /1-/1/10


# Test OUTER joins that are run in the distSQL merge joiner

query T
EXPLAIN (VERBOSE) SELECT * FROM (SELECT * FROM xyu ORDER BY x, y) AS xyu LEFT OUTER JOIN (SELECT * FROM xyv ORDER BY x, y) AS xyv USING(x, y) WHERE x > 2
----
distribution: local
vectorized: true
·
• project
│ columns: (x, y, u, v)
│
└── • merge join (left outer)
    │ columns: (x, y, u, x, y, v)
    │ estimated row count: 333 (missing stats)
    │ equality: (x, y) = (x, y)
    │ merge ordering: +"(x=x)",+"(y=y)"
    │
    ├── • scan
    │     columns: (x, y, u)
    │     ordering: +x,+y
    │     estimated row count: 333 (missing stats)
    │     table: xyu@xyu_pkey
    │     spans: /3-
    │
    └── • scan
          columns: (x, y, v)
          ordering: +x,+y
          estimated row count: 333 (missing stats)
          table: xyv@xyv_pkey
          spans: /3-

query T
EXPLAIN (VERBOSE) SELECT * FROM (SELECT * FROM xyu ORDER BY x, y) AS xyu RIGHT OUTER JOIN (SELECT * FROM xyv ORDER BY x, y) AS xyv USING(x, y) WHERE x > 2
----
distribution: local
vectorized: true
·
• project
│ columns: (x, y, u, v)
│
└── • merge join (left outer)
    │ columns: (x, y, v, x, y, u)
    │ estimated row count: 333 (missing stats)
    │ equality: (x, y) = (x, y)
    │ merge ordering: +"(x=x)",+"(y=y)"
    │
    ├── • scan
    │     columns: (x, y, v)
    │     ordering: +x,+y
    │     estimated row count: 333 (missing stats)
    │     table: xyv@xyv_pkey
    │     spans: /3-
    │
    └── • scan
          columns: (x, y, u)
          ordering: +x,+y
          estimated row count: 333 (missing stats)
          table: xyu@xyu_pkey
          spans: /3-

query T
EXPLAIN (VERBOSE) SELECT * FROM (SELECT * FROM xyu ORDER BY x, y) AS xyu FULL OUTER JOIN (SELECT * FROM xyv ORDER BY x, y) AS xyv USING(x, y) WHERE x > 2
----
distribution: local
vectorized: true
·
• filter
│ columns: (x, y, u, v)
│ estimated row count: 633 (missing stats)
│ filter: x > 2
│
└── • render
    │ columns: (x, y, u, v)
    │ render x: COALESCE(x, x)
    │ render y: COALESCE(y, y)
    │ render u: u
    │ render v: v
    │
    └── • merge join (full outer)
        │ columns: (x, y, u, x, y, v)
        │ estimated row count: 1,900 (missing stats)
        │ equality: (x, y) = (x, y)
        │ merge ordering: +"(x=x)",+"(y=y)"
        │
        ├── • scan
        │     columns: (x, y, u)
        │     ordering: +x,+y
        │     estimated row count: 1,000 (missing stats)
        │     table: xyu@xyu_pkey
        │     spans: FULL SCAN
        │
        └── • scan
              columns: (x, y, v)
              ordering: +x,+y
              estimated row count: 1,000 (missing stats)
              table: xyv@xyv_pkey
              spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT * FROM (SELECT * FROM xyu ORDER BY x, y) AS xyu LEFT OUTER JOIN (SELECT * FROM xyv ORDER BY x, y) AS xyv ON xyu.x = xyv.x AND xyu.y = xyv.y AND xyu.x = 1 AND xyu.y < 10
----
distribution: local
vectorized: true
·
• merge join (left outer)
│ columns: (x, y, u, x, y, v)
│ estimated row count: 1,000 (missing stats)
│ equality: (x, y) = (x, y)
│ merge ordering: +"(x=x)",+"(y=y)"
│
├── • scan
│     columns: (x, y, u)
│     ordering: +x,+y
│     estimated row count: 1,000 (missing stats)
│     table: xyu@xyu_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (x, y, v)
      ordering: +y
      estimated row count: 9 (missing stats)
      table: xyv@xyv_pkey
      spans: /1-/1/10

query T
EXPLAIN (VERBOSE) SELECT * FROM xyu RIGHT OUTER JOIN (SELECT * FROM xyv ORDER BY x, y) AS xyv ON xyu.x = xyv.x AND xyu.y = xyv.y AND xyu.x = 1 AND xyu.y < 10
----
distribution: local
vectorized: true
·
• merge join (left outer)
│ columns: (x, y, u, x, y, v)
│ estimated row count: 1,000 (missing stats)
│ equality: (x, y) = (x, y)
│ merge ordering: +"(x=x)",+"(y=y)"
│
├── • scan
│     columns: (x, y, v)
│     ordering: +x,+y
│     estimated row count: 1,000 (missing stats)
│     table: xyv@xyv_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (x, y, u)
      ordering: +y
      estimated row count: 9 (missing stats)
      table: xyu@xyu_pkey
      spans: /1-/1/10

# Regression test for #20472: break up tuple inequalities.
query T
EXPLAIN (VERBOSE) SELECT * FROM xyu JOIN xyv USING(x, y) WHERE (x, y, u) > (1, 2, 3)
----
distribution: local
vectorized: true
·
• project
│ columns: (x, y, u, v)
│
└── • merge join (inner)
    │ columns: (x, y, v, x, y, u)
    │ estimated row count: 33 (missing stats)
    │ equality: (x, y) = (x, y)
    │ merge ordering: +"(x=x)",+"(y=y)"
    │
    ├── • scan
    │     columns: (x, y, v)
    │     ordering: +x,+y
    │     estimated row count: 1,000 (missing stats)
    │     table: xyv@xyv_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (x, y, u)
          ordering: +x,+y
          estimated row count: 333 (missing stats)
          table: xyu@xyu_pkey
          spans: /1/2/4-


# Regression test for #20765/27431.
# We push a filter on an equality column to both sides of a left or right outer
# join.

statement ok
CREATE TABLE l (a INT PRIMARY KEY, b1 INT, FAMILY (a))

statement ok
CREATE TABLE r (a INT PRIMARY KEY, b2 INT, FAMILY (a))

query T
EXPLAIN (VERBOSE) SELECT * FROM l LEFT OUTER JOIN r USING(a) WHERE a = 3;
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b1, b2)
│
└── • merge join (left outer)
    │ columns: (a, b1, a, b2)
    │ estimated row count: 1 (missing stats)
    │ equality: (a) = (a)
    │ left cols are key
    │ right cols are key
    │ merge ordering: +"(a=a)"
    │
    ├── • scan
    │     columns: (a, b1)
    │     estimated row count: 1 (missing stats)
    │     table: l@l_pkey
    │     spans: /3/0
    │
    └── • scan
          columns: (a, b2)
          estimated row count: 1 (missing stats)
          table: r@r_pkey
          spans: /3/0

query T
EXPLAIN (VERBOSE) SELECT * FROM l LEFT OUTER JOIN r ON l.a = r.a WHERE l.a = 3;
----
distribution: local
vectorized: true
·
• merge join (left outer)
│ columns: (a, b1, a, b2)
│ estimated row count: 1 (missing stats)
│ equality: (a) = (a)
│ left cols are key
│ right cols are key
│ merge ordering: +"(a=a)"
│
├── • scan
│     columns: (a, b1)
│     estimated row count: 1 (missing stats)
│     table: l@l_pkey
│     spans: /3/0
│
└── • scan
      columns: (a, b2)
      estimated row count: 1 (missing stats)
      table: r@r_pkey
      spans: /3/0

query T
EXPLAIN (VERBOSE) SELECT * FROM l RIGHT OUTER JOIN r USING(a) WHERE a = 3;
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b1, b2)
│
└── • merge join (left outer)
    │ columns: (a, b2, a, b1)
    │ estimated row count: 1 (missing stats)
    │ equality: (a) = (a)
    │ left cols are key
    │ right cols are key
    │ merge ordering: +"(a=a)"
    │
    ├── • scan
    │     columns: (a, b2)
    │     estimated row count: 1 (missing stats)
    │     table: r@r_pkey
    │     spans: /3/0
    │
    └── • scan
          columns: (a, b1)
          estimated row count: 1 (missing stats)
          table: l@l_pkey
          spans: /3/0

query T
EXPLAIN (VERBOSE) SELECT * FROM l RIGHT OUTER JOIN r ON l.a = r.a WHERE r.a = 3;
----
distribution: local
vectorized: true
·
• merge join (left outer)
│ columns: (a, b1, a, b2)
│ estimated row count: 1 (missing stats)
│ equality: (a) = (a)
│ left cols are key
│ right cols are key
│ merge ordering: +"(a=a)"
│
├── • scan
│     columns: (a, b2)
│     estimated row count: 1 (missing stats)
│     table: r@r_pkey
│     spans: /3/0
│
└── • scan
      columns: (a, b1)
      estimated row count: 1 (missing stats)
      table: l@l_pkey
      spans: /3/0

# Regression tests for #21243
statement ok
CREATE TABLE abcdef (
  a INT NOT NULL,
  b INT NOT NULL,
  c INT NOT NULL,
  d INT NOT NULL,
  e INT NULL,
  f INT NULL,
  PRIMARY KEY (a ASC, b ASC, c DESC, d ASC)
)

statement ok
CREATE TABLE abg (
  a INT NOT NULL,
  b INT NOT NULL,
  g INT NULL,
  PRIMARY KEY (a ASC, b ASC)
);

query T
EXPLAIN SELECT * FROM abcdef join (select * from abg) USING (a,b) WHERE ((a,b)>(1,2) OR ((a,b)=(1,2) AND c < 6) OR ((a,b,c)=(1,2,6) AND d > 8))
----
distribution: local
vectorized: true
·
• merge join
│ equality: (a, b) = (a, b)
│ left cols are key
│
├── • scan
│     missing stats
│     table: abg@abg_pkey
│     spans: FULL SCAN
│
└── • scan
      missing stats
      table: abcdef@abcdef_pkey
      spans: [/1/2/6/9 - ]

# Regression tests for mixed-type equality columns (#22514).
statement ok
CREATE TABLE foo (
  a INT,
  b INT,
  c FLOAT,
  d FLOAT
)

statement ok
CREATE TABLE bar (
  a INT,
  b FLOAT,
  c FLOAT,
  d INT
)

# Only a and c can be equality columns.
query T
EXPLAIN (VERBOSE) SELECT * FROM foo NATURAL JOIN bar
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, c, d)
│
└── • hash join (inner)
    │ columns: (a, b, c, d, a, b, c, d)
    │ estimated row count: 0 (missing stats)
    │ equality: (a, c) = (a, c)
    │ pred: (b = b) AND (d = d)
    │
    ├── • scan
    │     columns: (a, b, c, d)
    │     estimated row count: 1,000 (missing stats)
    │     table: foo@foo_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (a, b, c, d)
          estimated row count: 1,000 (missing stats)
          table: bar@bar_pkey
          spans: FULL SCAN

# b can't be an equality column.
query T
EXPLAIN (VERBOSE) SELECT * FROM foo JOIN bar USING (b)
----
distribution: local
vectorized: true
·
• project
│ columns: (b, a, c, d, a, c, d)
│
└── • cross join (inner)
    │ columns: (a, b, c, d, a, b, c, d)
    │ estimated row count: 9,801 (missing stats)
    │ pred: b = b
    │
    ├── • scan
    │     columns: (a, b, c, d)
    │     estimated row count: 1,000 (missing stats)
    │     table: foo@foo_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (a, b, c, d)
          estimated row count: 1,000 (missing stats)
          table: bar@bar_pkey
          spans: FULL SCAN

# Only a can be an equality column.
query T
EXPLAIN (VERBOSE) SELECT * FROM foo JOIN bar USING (a, b)
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, c, d, c, d)
│
└── • hash join (inner)
    │ columns: (a, b, c, d, a, b, c, d)
    │ estimated row count: 96 (missing stats)
    │ equality: (a) = (a)
    │ pred: b = b
    │
    ├── • scan
    │     columns: (a, b, c, d)
    │     estimated row count: 1,000 (missing stats)
    │     table: foo@foo_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (a, b, c, d)
          estimated row count: 1,000 (missing stats)
          table: bar@bar_pkey
          spans: FULL SCAN

# Only a and c can be equality columns.
query T
EXPLAIN (VERBOSE) SELECT * FROM foo JOIN bar USING (a, b, c)
----
distribution: local
vectorized: true
·
• project
│ columns: (a, b, c, d, d)
│
└── • hash join (inner)
    │ columns: (a, b, c, d, a, b, c, d)
    │ estimated row count: 1 (missing stats)
    │ equality: (a, c) = (a, c)
    │ pred: b = b
    │
    ├── • scan
    │     columns: (a, b, c, d)
    │     estimated row count: 1,000 (missing stats)
    │     table: foo@foo_pkey
    │     spans: FULL SCAN
    │
    └── • scan
          columns: (a, b, c, d)
          estimated row count: 1,000 (missing stats)
          table: bar@bar_pkey
          spans: FULL SCAN

# b can't be an equality column.
query T
EXPLAIN (VERBOSE) SELECT * FROM foo JOIN bar ON foo.b = bar.b
----
distribution: local
vectorized: true
·
• cross join (inner)
│ columns: (a, b, c, d, a, b, c, d)
│ estimated row count: 9,801 (missing stats)
│ pred: b = b
│
├── • scan
│     columns: (a, b, c, d)
│     estimated row count: 1,000 (missing stats)
│     table: foo@foo_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (a, b, c, d)
      estimated row count: 1,000 (missing stats)
      table: bar@bar_pkey
      spans: FULL SCAN

# Only a can be an equality column.
query T
EXPLAIN (VERBOSE) SELECT * FROM foo JOIN bar ON foo.a = bar.a AND foo.b = bar.b
----
distribution: local
vectorized: true
·
• hash join (inner)
│ columns: (a, b, c, d, a, b, c, d)
│ estimated row count: 96 (missing stats)
│ equality: (a) = (a)
│ pred: b = b
│
├── • scan
│     columns: (a, b, c, d)
│     estimated row count: 1,000 (missing stats)
│     table: foo@foo_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (a, b, c, d)
      estimated row count: 1,000 (missing stats)
      table: bar@bar_pkey
      spans: FULL SCAN

query T
EXPLAIN (VERBOSE) SELECT * FROM foo, bar WHERE foo.b = bar.b
----
distribution: local
vectorized: true
·
• cross join (inner)
│ columns: (a, b, c, d, a, b, c, d)
│ estimated row count: 9,801 (missing stats)
│ pred: b = b
│
├── • scan
│     columns: (a, b, c, d)
│     estimated row count: 1,000 (missing stats)
│     table: foo@foo_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (a, b, c, d)
      estimated row count: 1,000 (missing stats)
      table: bar@bar_pkey
      spans: FULL SCAN

# Only a can be an equality column.
query T
EXPLAIN (VERBOSE) SELECT * FROM foo, bar WHERE foo.a = bar.a AND foo.b = bar.b
----
distribution: local
vectorized: true
·
• hash join (inner)
│ columns: (a, b, c, d, a, b, c, d)
│ estimated row count: 96 (missing stats)
│ equality: (a) = (a)
│ pred: b = b
│
├── • scan
│     columns: (a, b, c, d)
│     estimated row count: 1,000 (missing stats)
│     table: foo@foo_pkey
│     spans: FULL SCAN
│
└── • scan
      columns: (a, b, c, d)
      estimated row count: 1,000 (missing stats)
      table: bar@bar_pkey
      spans: FULL SCAN

# Only a and c can be equality columns.
query T
EXPLAIN SELECT * FROM foo JOIN bar USING (a,b) WHERE foo.c = bar.c AND foo.d = bar.d
----
distribution: local
vectorized: true
·
• hash join
│ equality: (a, c) = (a, c)
│ pred: (b = b) AND (d = d)
│
├── • scan
│     missing stats
│     table: foo@foo_pkey
│     spans: FULL SCAN
│
└── • scan
      missing stats
      table: bar@bar_pkey
      spans: FULL SCAN

# Zigzag join tests.
statement ok
CREATE TABLE zigzag (
  a INT PRIMARY KEY,
  b INT,
  c FLOAT,
  d FLOAT,
  INDEX b_idx(b),
  INDEX c_idx(c)
)

# No zigzag join should be planned if there is a hint or enable_zigzag_join is
# false.
query T
EXPLAIN SELECT a,b,c FROM zigzag@{NO_ZIGZAG_JOIN} WHERE b = 5 AND c = 6.0
----
distribution: local
vectorized: true
·
• filter
│ filter: c = 6.0
│
└── • index join
    │ table: zigzag@zigzag_pkey
    │
    └── • scan
          missing stats
          table: zigzag@b_idx
          spans: [/5 - /5]

statement ok
SET enable_zigzag_join = false

query T
EXPLAIN SELECT a,b,c FROM zigzag WHERE b = 5 AND c = 6.0
----
distribution: local
vectorized: true
·
• filter
│ filter: c = 6.0
│
└── • index join
    │ table: zigzag@zigzag_pkey
    │
    └── • scan
          missing stats
          table: zigzag@b_idx
          spans: [/5 - /5]

# Enable zigzag joins.
statement ok
SET enable_zigzag_join = true

# Simple zigzag case - fixed columns, output cols from indexes only.
query T
EXPLAIN SELECT a,b,c FROM zigzag WHERE b = 5 AND c = 6.0
----
distribution: local
vectorized: true
·
• zigzag join
  pred: (b = 5) AND (c = 6.0)
  left table: zigzag@b_idx
  left columns: (a, b)
  left fixed values: 1 column
  right table: zigzag@c_idx
  right columns: (a, c)
  right fixed values: 1 column


# Zigzag join nested inside a lookup.
query T
EXPLAIN SELECT a,b,c,d FROM zigzag WHERE b = 5 AND c = 6.0
----
distribution: local
vectorized: true
·
• lookup join
│ table: zigzag@zigzag_pkey
│ equality: (a) = (a)
│ equality cols are key
│
└── • zigzag join
      pred: (b = 5) AND (c = 6.0)
      left table: zigzag@b_idx
      left columns: (a, b)
      left fixed values: 1 column
      right table: zigzag@c_idx
      right columns: (a, c)
      right fixed values: 1 column

# Zigzag join nested inside a lookup, with an on condition on lookup join.
query T
EXPLAIN SELECT a,b,c,d FROM zigzag WHERE b = 5 AND c = 6.0 AND d > 4
----
distribution: local
vectorized: true
·
• lookup join
│ table: zigzag@zigzag_pkey
│ equality: (a) = (a)
│ equality cols are key
│ pred: d > 4.0
│
└── • zigzag join
      pred: (b = 5) AND (c = 6.0)
      left table: zigzag@b_idx
      left columns: (a, b)
      left fixed values: 1 column
      right table: zigzag@c_idx
      right columns: (a, c)
      right fixed values: 1 column


# Regression test for part of #34695.
statement ok
CREATE TABLE zigzag2 (
  a INT,
  b INT,
  c INT,
  d INT,
  UNIQUE INDEX a_b_idx(a, b),
  INDEX c_idx(c)
)

# Check a value which is equated to NULL.

query T
EXPLAIN SELECT * FROM zigzag2 WHERE a = 1 AND b = 2 AND c IS NULL
----
distribution: local
vectorized: true
·
• filter
│ filter: c IS NULL
│
└── • index join
    │ table: zigzag2@zigzag2_pkey
    │
    └── • scan
          missing stats
          table: zigzag2@a_b_idx
          spans: [/1/2 - /1/2]

# Reset zigzag joins to its default setting.
statement ok
RESET enable_zigzag_join

# Test that we can force a merge join.
query T
EXPLAIN SELECT * FROM onecolumn INNER MERGE JOIN twocolumn USING(x)
----
distribution: local
vectorized: true
·
• merge join
│ equality: (x) = (x)
│
├── • sort
│   │ order: +x
│   │
│   └── • scan
│         missing stats
│         table: onecolumn@onecolumn_pkey
│         spans: FULL SCAN
│
└── • sort
    │ order: +x
    │
    └── • scan
          missing stats
          table: twocolumn@twocolumn_pkey
          spans: FULL SCAN

# Test that we can force a merge join using the NATURAL syntax.
query T
EXPLAIN SELECT * FROM onecolumn NATURAL INNER MERGE JOIN twocolumn
----
distribution: local
vectorized: true
·
• merge join
│ equality: (x) = (x)
│
├── • sort
│   │ order: +x
│   │
│   └── • scan
│         missing stats
│         table: onecolumn@onecolumn_pkey
│         spans: FULL SCAN
│
└── • sort
    │ order: +x
    │
    └── • scan
          missing stats
          table: twocolumn@twocolumn_pkey
          spans: FULL SCAN

# Test that we can force a merge join using the CROSS syntax.
query T
EXPLAIN SELECT * FROM onecolumn CROSS MERGE JOIN twocolumn WHERE onecolumn.x = twocolumn.x
----
distribution: local
vectorized: true
·
• merge join
│ equality: (x) = (x)
│
├── • sort
│   │ order: +x
│   │
│   └── • scan
│         missing stats
│         table: onecolumn@onecolumn_pkey
│         spans: FULL SCAN
│
└── • sort
    │ order: +x
    │
    └── • scan
          missing stats
          table: twocolumn@twocolumn_pkey
          spans: FULL SCAN

statement error LOOKUP can only be used with INNER or LEFT joins
EXPLAIN SELECT * FROM onecolumn RIGHT LOOKUP JOIN twocolumn USING(x)

statement error could not produce a query plan conforming to the LOOKUP JOIN hint
EXPLAIN SELECT * FROM onecolumn INNER LOOKUP JOIN twocolumn USING(x)

statement error could not produce a query plan conforming to the MERGE JOIN hint
EXPLAIN SELECT * FROM onecolumn INNER MERGE JOIN twocolumn ON onecolumn.x > twocolumn.y

# Test that we can force a hash join (instead of merge join).
query T
EXPLAIN SELECT * FROM cards LEFT OUTER HASH JOIN customers ON customers.id = cards.cust
----
distribution: local
vectorized: true
·
• render
│
└── • scan
      missing stats
      table: cards@cards_pkey
      spans: FULL SCAN

# Test that STRAIGHT JOIN is not as strong a hint as LOOKUP JOIN when exploring.

statement ok
CREATE TABLE t119035 (
  k INT PRIMARY KEY,
  a INT,
  b INT,
  INDEX (a, b)
)

query T
EXPLAIN (OPT, MEMO)
SELECT * FROM (VALUES (1, 10), (2, 20), (3, 30)) as v(x, y)
INNER STRAIGHT JOIN t119035 ON a > x
----
memo (optimized, ~11KB, required=[presentation: info:10] [distribution: test])
 ├── G1: (explain G2 [presentation: x:1,y:2,k:3,a:4,b:5] [distribution: test])
 │    └── [presentation: info:10] [distribution: test]
 │         ├── best: (explain G2="[presentation: x:1,y:2,k:3,a:4,b:5] [distribution: test]" [presentation: x:1,y:2,k:3,a:4,b:5] [distribution: test])
 │         └── cost: 1196.99
 ├── G2: (inner-join G3 G4 G5)
 │    ├── [presentation: x:1,y:2,k:3,a:4,b:5] [distribution: test]
 │    │    ├── best: (inner-join G3="[distribution: test]" G4="[distribution: test]" G5)
 │    │    └── cost: 1196.97
 │    └── []
 │         ├── best: (inner-join G3 G4 G5)
 │         └── cost: 1196.97
 ├── G3: (values G6 id=v1)
 │    ├── [distribution: test]
 │    │    ├── best: (values G6 id=v1)
 │    │    └── cost: 0.04
 │    └── []
 │         ├── best: (values G6 id=v1)
 │         └── cost: 0.04
 ├── G4: (scan t119035,cols=(3-5)) (scan t119035@t119035_a_b_idx,cols=(3-5))
 │    ├── [distribution: test]
 │    │    ├── best: (scan t119035,cols=(3-5))
 │    │    └── cost: 1149.22
 │    └── []
 │         ├── best: (scan t119035,cols=(3-5))
 │         └── cost: 1149.22
 ├── G5: (filters G7)
 ├── G6: (scalar-list G8 G9 G10)
 ├── G7: (gt G11 G12)
 ├── G8: (tuple G13)
 ├── G9: (tuple G14)
 ├── G10: (tuple G15)
 ├── G11: (variable a)
 ├── G12: (variable column1)
 ├── G13: (scalar-list G16 G17)
 ├── G14: (scalar-list G18 G19)
 ├── G15: (scalar-list G20 G21)
 ├── G16: (const 1)
 ├── G17: (const 10)
 ├── G18: (const 2)
 ├── G19: (const 20)
 ├── G20: (const 3)
 └── G21: (const 30)
inner-join (cross)
 ├── flags: disallow hash join (store left side) and lookup join (into left side) and inverted join (into left side)
 ├── values
 │    ├── (1, 10)
 │    ├── (2, 20)
 │    └── (3, 30)
 ├── scan t119035
 └── filters
      └── a > column1

query T
EXPLAIN (OPT, MEMO)
SELECT * FROM (VALUES (1, 10), (2, 20), (3, 30)) as v(x, y)
INNER LOOKUP JOIN t119035 ON a > x
----
memo (optimized, ~11KB, required=[presentation: info:10] [distribution: test])
 ├── G1: (explain G2 [presentation: x:1,y:2,k:3,a:4,b:5] [distribution: test])
 │    └── [presentation: info:10] [distribution: test]
 │         ├── best: (explain G2="[presentation: x:1,y:2,k:3,a:4,b:5] [distribution: test]" [presentation: x:1,y:2,k:3,a:4,b:5] [distribution: test])
 │         └── cost: 2172.81
 ├── G2: (inner-join G3 G4 G5) (lookup-join G3 G6 t119035@t119035_a_b_idx,outCols=(1-5))
 │    ├── [presentation: x:1,y:2,k:3,a:4,b:5] [distribution: test]
 │    │    ├── best: (lookup-join G3="[distribution: test]" G6 t119035@t119035_a_b_idx,outCols=(1-5))
 │    │    └── cost: 2172.79
 │    └── []
 │         ├── best: (lookup-join G3 G6 t119035@t119035_a_b_idx,outCols=(1-5))
 │         └── cost: 2172.79
 ├── G3: (values G7 id=v1)
 │    ├── [distribution: test]
 │    │    ├── best: (values G7 id=v1)
 │    │    └── cost: 0.04
 │    └── []
 │         ├── best: (values G7 id=v1)
 │         └── cost: 0.04
 ├── G4: (scan t119035,cols=(3-5)) (scan t119035@t119035_a_b_idx,cols=(3-5))
 │    ├── [distribution: test]
 │    │    ├── best: (scan t119035,cols=(3-5))
 │    │    └── cost: 1149.22
 │    └── []
 │         ├── best: (scan t119035,cols=(3-5))
 │         └── cost: 1149.22
 ├── G5: (filters G8)
 ├── G6: (filters)
 ├── G7: (scalar-list G9 G10 G11)
 ├── G8: (gt G12 G13)
 ├── G9: (tuple G14)
 ├── G10: (tuple G15)
 ├── G11: (tuple G16)
 ├── G12: (variable a)
 ├── G13: (variable column1)
 ├── G14: (scalar-list G17 G18)
 ├── G15: (scalar-list G19 G20)
 ├── G16: (scalar-list G21 G22)
 ├── G17: (const 1)
 ├── G18: (const 10)
 ├── G19: (const 2)
 ├── G20: (const 20)
 ├── G21: (const 3)
 └── G22: (const 30)
inner-join (lookup t119035@t119035_a_b_idx)
 ├── flags: force lookup join (into right side)
 ├── values
 │    ├── (1, 10)
 │    ├── (2, 20)
 │    └── (3, 30)
 └── filters (true)
