exec-ddl
CREATE TABLE a (k INT PRIMARY KEY, i INT, f FLOAT, s STRING)
----

exec-ddl
CREATE TABLE xy (x INT PRIMARY KEY, y INT)
----

exec-ddl
CREATE TABLE abcde (
    a INT PRIMARY KEY,
    b INT,
    c INT,
    d INT,
    e INT,
    UNIQUE INDEX bc (b, c)
)
----

exec-ddl
CREATE TABLE mutation (
    a INT PRIMARY KEY,
    b INT,
    c INT,
    "d:write-only" INT,
    "e:delete-only" INT,
    UNIQUE INDEX "idx1:write-only" (b, d),
    INDEX "idx2:delete-only" (e)
)
----

exec-ddl
CREATE TABLE family (
    a INT PRIMARY KEY,
    b INT,
    c INT,
    d INT,
    e INT,
    FAMILY (a, b),
    FAMILY (c, d),
    FAMILY (e),
    INDEX (d)
)
----

exec-ddl
CREATE TABLE partial_indexes (
    a INT PRIMARY KEY,
    b INT,
    c STRING,
    d INT,
    FAMILY (a),
    FAMILY (b),
    FAMILY (c),
    FAMILY (d),
    INDEX (c) WHERE b > 1
)
----

exec-ddl
CREATE TABLE multi_col_inv_idx (
    a INT PRIMARY KEY,
    b INT,
    c STRING,
    j JSON,
    FAMILY (a),
    FAMILY (b),
    FAMILY (c),
    FAMILY (j),
    INVERTED INDEX (b, c, j)
)
----

exec-ddl
CREATE TABLE uniq (
  k INT PRIMARY KEY,
  v INT,
  w INT UNIQUE WITHOUT INDEX,
  x INT,
  y INT,
  z INT UNIQUE,
  UNIQUE WITHOUT INDEX (x, y),
  FAMILY (k),
  FAMILY (v),
  FAMILY (w),
  FAMILY (x),
  FAMILY (y),
  FAMILY (z)
)
----

exec-ddl
CREATE TABLE uniq_partial (
  k INT PRIMARY KEY,
  v INT,
  w INT,
  x INT,
  UNIQUE WITHOUT INDEX (v) WHERE w > 0,
  FAMILY (k),
  FAMILY (v),
  FAMILY (w),
  FAMILY (x)
)
----

exec-ddl
CREATE TABLE uniq_fk_parent (
  k INT PRIMARY KEY,
  a INT UNIQUE WITHOUT INDEX,
  b INT,
  c INT,
  d INT,
  UNIQUE WITHOUT INDEX (b, c),
  FAMILY (a),
  FAMILY (b),
  FAMILY (c),
  FAMILY (d)
)
----

exec-ddl
CREATE TABLE uniq_fk_child (
  a INT REFERENCES uniq_fk_parent (a),
  b INT,
  c INT,
  d INT,
  FOREIGN KEY (b, c) REFERENCES uniq_fk_parent (b, c) ON UPDATE CASCADE,
  UNIQUE WITHOUT INDEX (c),
  FAMILY (a),
  FAMILY (b),
  FAMILY (c),
  FAMILY (d)
)
----

exec-ddl
CREATE TABLE computed (
    a INT PRIMARY KEY,
    b INT,
    c INT,
    d INT AS (c+1) STORED,
    "x:write-only" INT AS (c+10) STORED,
    FAMILY (a),
    FAMILY (b),
    FAMILY (c),
    FAMILY (d)
)
----

exec-ddl
CREATE TABLE virt (
  a INT PRIMARY KEY,
  b INT,
  v INT AS (a+b) VIRTUAL,
  FAMILY (a),
  FAMILY (b)
)
----

exec-ddl
CREATE TABLE virt_idx (
  a INT PRIMARY KEY,
  b INT,
  c INT,
  v INT AS (a+b) VIRTUAL,
  INDEX (v),
  FAMILY (a),
  FAMILY (b),
  FAMILY (c)
)
----

exec-ddl
CREATE TABLE virt_idx2 (
  a INT PRIMARY KEY,
  b INT,
  c INT,
  v INT AS (b+1) VIRTUAL,
  INDEX (v),
  FAMILY (a),
  FAMILY (b),
  FAMILY (c)
)
----

exec-ddl
CREATE TABLE virt_idx3 (
  a INT PRIMARY KEY,
  b INT,
  c INT,
  v INT AS (b+1) VIRTUAL,
  INDEX (c, v),
  FAMILY (a),
  FAMILY (b),
  FAMILY (c)
)
----

exec-ddl
CREATE TABLE virt_partial_idx (
    a INT PRIMARY KEY,
    b INT,
    v INT AS (b + 1) VIRTUAL,
    c INT,
    d INT,
    FAMILY (a),
    FAMILY (b),
    FAMILY (c),
    FAMILY (d),
    INDEX (c) WHERE v > 1
)
----

# --------------------------------------------------
# PruneProjectCols
# --------------------------------------------------

# Discard some of columns.
norm expect=PruneProjectCols
SELECT k1*2 FROM (SELECT k+1 AS k1, i+1 FROM a) a
----
project
 ├── columns: "?column?":9!null
 ├── immutable
 ├── scan a
 │    ├── columns: k:1!null
 │    └── key: (1)
 └── projections
      └── (k:1 + 1) * 2 [as="?column?":9, outer=(1), immutable]

# Use column values within computed column.
norm expect=PruneProjectCols
SELECT k+length(s) AS r FROM (SELECT i, k, s || 'foo' AS s FROM a) a
----
project
 ├── columns: r:8
 ├── immutable
 ├── scan a
 │    ├── columns: k:1!null a.s:4
 │    ├── key: (1)
 │    └── fd: (1)-->(4)
 └── projections
      └── k:1 + length(a.s:4 || 'foo') [as=r:8, outer=(1,4), immutable]

# Discard non-computed columns and keep computed column.
norm expect=PruneProjectCols
SELECT l, l*2, k FROM (SELECT length(s) l, * FROM a) a
----
project
 ├── columns: l:7 "?column?":8 k:1!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(7), (7)-->(8)
 ├── project
 │    ├── columns: l:7 k:1!null
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(7)
 │    ├── scan a
 │    │    ├── columns: k:1!null s:4
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(4)
 │    └── projections
 │         └── length(s:4) [as=l:7, outer=(4), immutable]
 └── projections
      └── l:7 * 2 [as="?column?":8, outer=(7), immutable]

# Compute column based on another computed column.
norm expect=PruneProjectCols
SELECT l*l AS r, k FROM (SELECT k, length(s) l, i FROM a) a
----
project
 ├── columns: r:8 k:1!null
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(8)
 ├── project
 │    ├── columns: l:7 k:1!null
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(7)
 │    ├── scan a
 │    │    ├── columns: k:1!null s:4
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(4)
 │    └── projections
 │         └── length(s:4) [as=l:7, outer=(4), immutable]
 └── projections
      └── l:7 * l:7 [as=r:8, outer=(7), immutable]

# --------------------------------------------------
# PruneScanCols
# --------------------------------------------------

# Project subset of columns.
norm expect=PruneScanCols
SELECT k FROM a
----
scan a
 ├── columns: k:1!null
 └── key: (1)

# Project subset of columns, some used in computed columns.
norm expect=PruneScanCols
SELECT k, k+1 AS r, i+1 AS s FROM a
----
project
 ├── columns: k:1!null r:7!null s:8
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(7,8)
 ├── scan a
 │    ├── columns: k:1!null i:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── projections
      ├── k:1 + 1 [as=r:7, outer=(1), immutable]
      └── i:2 + 1 [as=s:8, outer=(2), immutable]

# Use columns only in computed columns.
norm expect=PruneScanCols
SELECT k+i AS r FROM a
----
project
 ├── columns: r:7
 ├── immutable
 ├── scan a
 │    ├── columns: k:1!null i:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── projections
      └── k:1 + i:2 [as=r:7, outer=(1,2), immutable]

# Use no scan columns.
norm expect=PruneScanCols
SELECT 1 r FROM a
----
project
 ├── columns: r:7!null
 ├── fd: ()-->(7)
 ├── scan a
 └── projections
      └── 1 [as=r:7]

# --------------------------------------------------
# PruneSelectCols
# --------------------------------------------------

# Columns used only by projection or filter, but not both.
norm expect=PruneSelectCols
SELECT k FROM a WHERE i<5
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── select
      ├── columns: k:1!null i:2!null
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── scan a
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      └── filters
           └── i:2 < 5 [outer=(2), constraints=(/2: (/NULL - /4]; tight)]

# Columns used by both projection and filter.
norm expect=PruneSelectCols
SELECT k, i FROM a WHERE k=1 AND i<5
----
select
 ├── columns: k:1!null i:2!null
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(1,2)
 ├── scan a
 │    ├── columns: k:1!null i:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── filters
      ├── k:1 = 1 [outer=(1), constraints=(/1: [/1 - /1]; tight), fd=()-->(1)]
      └── i:2 < 5 [outer=(2), constraints=(/2: (/NULL - /4]; tight)]

# No needed select columns.
norm expect=PruneSelectCols
SELECT 1 r FROM a WHERE $1<'2000-01-01T02:00:00'::timestamp
----
project
 ├── columns: r:7!null
 ├── has-placeholder
 ├── fd: ()-->(7)
 ├── select
 │    ├── has-placeholder
 │    ├── scan a
 │    └── filters
 │         └── $1 < '2000-01-01 02:00:00'
 └── projections
      └── 1 [as=r:7]

# Select columns used in computed columns.
norm expect=PruneSelectCols
SELECT i-1 AS r, k*k AS t FROM a WHERE k+1<5 AND s||'o'='foo'
----
project
 ├── columns: r:7 t:8!null
 ├── immutable
 ├── select
 │    ├── columns: k:1!null i:2 s:4
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,4)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 s:4
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2,4)
 │    └── filters
 │         ├── k:1 < 4 [outer=(1), constraints=(/1: (/NULL - /3]; tight)]
 │         └── (s:4 || 'o') = 'foo' [outer=(4), immutable]
 └── projections
      ├── i:2 - 1 [as=r:7, outer=(2), immutable]
      └── k:1 * k:1 [as=t:8, outer=(1), immutable]

# Select nested in select.
norm expect=PruneSelectCols
SELECT i FROM (SELECT k, i, s, f/2.0 f FROM a WHERE k = 5) a2 WHERE i::float = f
----
project
 ├── columns: i:2
 ├── cardinality: [0 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(2)
 └── select
      ├── columns: i:2 f:7!null
      ├── cardinality: [0 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(2,7)
      ├── project
      │    ├── columns: f:7 i:2
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(2,7)
      │    ├── select
      │    │    ├── columns: k:1!null i:2 a.f:3
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(1-3)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null i:2 a.f:3
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2,3)
      │    │    └── filters
      │    │         └── k:1 = 5 [outer=(1), constraints=(/1: [/5 - /5]; tight), fd=()-->(1)]
      │    └── projections
      │         └── a.f:3 / 2.0 [as=f:7, outer=(3)]
      └── filters
           └── f:7 = i:2::FLOAT8 [outer=(2,7), immutable, constraints=(/7: (/NULL - ]), fd=(2)-->(7)]

# Detect PruneSelectCols and PushSelectIntoProject dependency cycle.
norm
SELECT f, f+1.1 AS r FROM (SELECT f, k FROM a GROUP BY f, k HAVING sum(k)=100) a
----
project
 ├── columns: f:3 r:8
 ├── immutable
 ├── fd: (3)-->(8)
 ├── select
 │    ├── columns: k:1!null f:3 sum:7!null
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: ()-->(7), (1)-->(3)
 │    ├── group-by (hash)
 │    │    ├── columns: k:1!null f:3 sum:7!null
 │    │    ├── grouping columns: k:1!null
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(3,7)
 │    │    ├── scan a
 │    │    │    ├── columns: k:1!null f:3
 │    │    │    ├── key: (1)
 │    │    │    └── fd: (1)-->(3)
 │    │    └── aggregations
 │    │         ├── sum [as=sum:7, outer=(1)]
 │    │         │    └── k:1
 │    │         └── const-agg [as=f:3, outer=(3)]
 │    │              └── f:3
 │    └── filters
 │         └── sum:7 = 100 [outer=(7), immutable, constraints=(/7: [/100 - /100]; tight), fd=()-->(7)]
 └── projections
      └── f:3 + 1.1 [as=r:8, outer=(3), immutable]

# --------------------------------------------------
# PruneLimitCols
# --------------------------------------------------

norm expect=PruneLimitCols
SELECT i FROM (SELECT i, s FROM a LIMIT 1)
----
limit
 ├── columns: i:2
 ├── cardinality: [0 - 1]
 ├── key: ()
 ├── fd: ()-->(2)
 ├── scan a
 │    ├── columns: i:2
 │    └── limit hint: 1.00
 └── 1

# The projection on top of Limit should trickle down and we shouldn't scan f.
norm expect=PruneLimitCols
SELECT k FROM (SELECT k, i, f FROM a ORDER BY i LIMIT 10)
----
project
 ├── columns: k:1!null
 ├── cardinality: [0 - 10]
 ├── key: (1)
 └── limit
      ├── columns: k:1!null i:2
      ├── internal-ordering: +2
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── sort
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    ├── ordering: +2
      │    ├── limit hint: 10.00
      │    └── scan a
      │         ├── columns: k:1!null i:2
      │         ├── key: (1)
      │         └── fd: (1)-->(2)
      └── 10

# We should scan k, i, s.
norm expect=PruneLimitCols
SELECT s FROM (SELECT k, i, f, s FROM a ORDER BY i, k LIMIT 10)
----
project
 ├── columns: s:4
 ├── cardinality: [0 - 10]
 └── limit
      ├── columns: k:1!null i:2 s:4
      ├── internal-ordering: +2,+1
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(2,4)
      ├── sort
      │    ├── columns: k:1!null i:2 s:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,4)
      │    ├── ordering: +2,+1
      │    ├── limit hint: 10.00
      │    └── scan a
      │         ├── columns: k:1!null i:2 s:4
      │         ├── key: (1)
      │         └── fd: (1)-->(2,4)
      └── 10

# We should scan k, i, s.
norm expect=PruneLimitCols
SELECT k, s FROM (SELECT k, i, f, s FROM a ORDER BY i, k LIMIT 10)
----
project
 ├── columns: k:1!null s:4
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(4)
 └── limit
      ├── columns: k:1!null i:2 s:4
      ├── internal-ordering: +2,+1
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(2,4)
      ├── sort
      │    ├── columns: k:1!null i:2 s:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,4)
      │    ├── ordering: +2,+1
      │    ├── limit hint: 10.00
      │    └── scan a
      │         ├── columns: k:1!null i:2 s:4
      │         ├── key: (1)
      │         └── fd: (1)-->(2,4)
      └── 10

# Project uses subset of Limit columns, but no additional Project should be
# introduced to tree, because it can't be pushed down to Scan.
norm
SELECT f, f*2.0 AS r FROM (SELECT f, s FROM a GROUP BY f, s LIMIT 5) a
----
project
 ├── columns: f:3 r:7
 ├── cardinality: [0 - 5]
 ├── immutable
 ├── fd: (3)-->(7)
 ├── limit
 │    ├── columns: f:3 s:4
 │    ├── cardinality: [0 - 5]
 │    ├── key: (3,4)
 │    ├── distinct-on
 │    │    ├── columns: f:3 s:4
 │    │    ├── grouping columns: f:3 s:4
 │    │    ├── key: (3,4)
 │    │    ├── limit hint: 5.00
 │    │    └── scan a
 │    │         ├── columns: f:3 s:4
 │    │         └── limit hint: 6.02
 │    └── 5
 └── projections
      └── f:3 * 2.0 [as=r:7, outer=(3), immutable]

# --------------------------------------------------
# PruneOffsetCols
# --------------------------------------------------

norm expect=PruneOffsetCols
SELECT f FROM (SELECT * FROM a OFFSET 1)
----
offset
 ├── columns: f:3
 ├── scan a
 │    └── columns: f:3
 └── 1

norm expect=PruneOffsetCols
SELECT k FROM (SELECT k, i, f FROM a ORDER BY i OFFSET 10)
----
project
 ├── columns: k:1!null
 ├── key: (1)
 └── offset
      ├── columns: k:1!null i:2
      ├── internal-ordering: +2
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── sort
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    ├── ordering: +2
      │    └── scan a
      │         ├── columns: k:1!null i:2
      │         ├── key: (1)
      │         └── fd: (1)-->(2)
      └── 10

# We should scan k, i, s.
norm expect=PruneOffsetCols
SELECT s FROM (SELECT k, i, f, s FROM a ORDER BY i, k OFFSET 10)
----
project
 ├── columns: s:4
 └── offset
      ├── columns: k:1!null i:2 s:4
      ├── internal-ordering: +2,+1
      ├── key: (1)
      ├── fd: (1)-->(2,4)
      ├── sort
      │    ├── columns: k:1!null i:2 s:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,4)
      │    ├── ordering: +2,+1
      │    └── scan a
      │         ├── columns: k:1!null i:2 s:4
      │         ├── key: (1)
      │         └── fd: (1)-->(2,4)
      └── 10

# We should scan k, i, s.
norm expect=PruneOffsetCols
SELECT k, s FROM (SELECT k, i, f, s FROM a ORDER BY i, k OFFSET 10)
----
project
 ├── columns: k:1!null s:4
 ├── key: (1)
 ├── fd: (1)-->(4)
 └── offset
      ├── columns: k:1!null i:2 s:4
      ├── internal-ordering: +2,+1
      ├── key: (1)
      ├── fd: (1)-->(2,4)
      ├── sort
      │    ├── columns: k:1!null i:2 s:4
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,4)
      │    ├── ordering: +2,+1
      │    └── scan a
      │         ├── columns: k:1!null i:2 s:4
      │         ├── key: (1)
      │         └── fd: (1)-->(2,4)
      └── 10

# Project uses subset of Offset columns, but no additional Project should be
# introduced to tree, because it can't be pushed down past Explain.
norm
SELECT tree, columns
FROM
(
    SELECT *
    FROM [ EXPLAIN (VERBOSE) SELECT * FROM a ]
    ORDER BY tree
    OFFSET 1
)
----
error (42703): column "tree" does not exist

# --------------------------------------------------
# PruneLimitCols + PruneOffsetCols
# --------------------------------------------------

norm expect=(PruneLimitCols,PruneOffsetCols)
SELECT k FROM (SELECT k, i, f FROM a ORDER BY i LIMIT 10 OFFSET 10)
----
project
 ├── columns: k:1!null
 ├── cardinality: [0 - 10]
 ├── key: (1)
 └── offset
      ├── columns: k:1!null i:2
      ├── internal-ordering: +2
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── limit
      │    ├── columns: k:1!null i:2
      │    ├── internal-ordering: +2
      │    ├── cardinality: [0 - 20]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    ├── ordering: +2
      │    ├── sort
      │    │    ├── columns: k:1!null i:2
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2)
      │    │    ├── ordering: +2
      │    │    ├── limit hint: 20.00
      │    │    └── scan a
      │    │         ├── columns: k:1!null i:2
      │    │         ├── key: (1)
      │    │         └── fd: (1)-->(2)
      │    └── 20
      └── 10

# We should scan k, i, s.
norm expect=(PruneLimitCols,PruneOffsetCols)
SELECT s FROM (SELECT k, i, f, s FROM a ORDER BY i, k LIMIT 10 OFFSET 10)
----
project
 ├── columns: s:4
 ├── cardinality: [0 - 10]
 └── offset
      ├── columns: k:1!null i:2 s:4
      ├── internal-ordering: +2,+1
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(2,4)
      ├── limit
      │    ├── columns: k:1!null i:2 s:4
      │    ├── internal-ordering: +2,+1
      │    ├── cardinality: [0 - 20]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,4)
      │    ├── ordering: +2,+1
      │    ├── sort
      │    │    ├── columns: k:1!null i:2 s:4
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2,4)
      │    │    ├── ordering: +2,+1
      │    │    ├── limit hint: 20.00
      │    │    └── scan a
      │    │         ├── columns: k:1!null i:2 s:4
      │    │         ├── key: (1)
      │    │         └── fd: (1)-->(2,4)
      │    └── 20
      └── 10

# We should scan k, i, s.
norm expect=(PruneLimitCols,PruneOffsetCols)
SELECT k, s FROM (SELECT k, i, f, s FROM a ORDER BY i, k LIMIT 10 OFFSET 10)
----
project
 ├── columns: k:1!null s:4
 ├── cardinality: [0 - 10]
 ├── key: (1)
 ├── fd: (1)-->(4)
 └── offset
      ├── columns: k:1!null i:2 s:4
      ├── internal-ordering: +2,+1
      ├── cardinality: [0 - 10]
      ├── key: (1)
      ├── fd: (1)-->(2,4)
      ├── limit
      │    ├── columns: k:1!null i:2 s:4
      │    ├── internal-ordering: +2,+1
      │    ├── cardinality: [0 - 20]
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,4)
      │    ├── ordering: +2,+1
      │    ├── sort
      │    │    ├── columns: k:1!null i:2 s:4
      │    │    ├── key: (1)
      │    │    ├── fd: (1)-->(2,4)
      │    │    ├── ordering: +2,+1
      │    │    ├── limit hint: 20.00
      │    │    └── scan a
      │    │         ├── columns: k:1!null i:2 s:4
      │    │         ├── key: (1)
      │    │         └── fd: (1)-->(2,4)
      │    └── 20
      └── 10

# Project filter offset/limit columns, but can't push all the way down to scan.
norm
SELECT f, f*2.0 AS r FROM (SELECT f, s FROM a GROUP BY f, s OFFSET 5 LIMIT 5) a
----
project
 ├── columns: f:3 r:7
 ├── cardinality: [0 - 5]
 ├── immutable
 ├── fd: (3)-->(7)
 ├── offset
 │    ├── columns: f:3 s:4
 │    ├── cardinality: [0 - 5]
 │    ├── key: (3,4)
 │    ├── limit
 │    │    ├── columns: f:3 s:4
 │    │    ├── cardinality: [0 - 10]
 │    │    ├── key: (3,4)
 │    │    ├── distinct-on
 │    │    │    ├── columns: f:3 s:4
 │    │    │    ├── grouping columns: f:3 s:4
 │    │    │    ├── key: (3,4)
 │    │    │    ├── limit hint: 10.00
 │    │    │    └── scan a
 │    │    │         ├── columns: f:3 s:4
 │    │    │         └── limit hint: 12.07
 │    │    └── 10
 │    └── 5
 └── projections
      └── f:3 * 2.0 [as=r:7, outer=(3), immutable]

# --------------------------------------------------
# PruneJoinLeftCols
# --------------------------------------------------

# Columns used only by projection or on condition, but not both.
norm expect=PruneJoinLeftCols
SELECT a.i, xy.* FROM a INNER JOIN xy ON a.k=xy.x
----
project
 ├── columns: i:2 x:7!null y:8
 ├── key: (7)
 ├── fd: (7)-->(2,8)
 └── inner-join (hash)
      ├── columns: k:1!null i:2 x:7!null y:8
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      ├── key: (7)
      ├── fd: (1)-->(2), (7)-->(8), (1)==(7), (7)==(1)
      ├── scan a
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── scan xy
      │    ├── columns: x:7!null y:8
      │    ├── key: (7)
      │    └── fd: (7)-->(8)
      └── filters
           └── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

# Columns used by both projection and on condition, left join.
norm expect=PruneJoinLeftCols
SELECT a.k, a.i, xy.* FROM a LEFT JOIN xy ON a.k=xy.x AND a.i<5
----
left-join (hash)
 ├── columns: k:1!null i:2 x:7 y:8
 ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 ├── key: (1)
 ├── fd: (1)-->(2,7,8), (7)-->(8)
 ├── scan a
 │    ├── columns: k:1!null i:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── scan xy
 │    ├── columns: x:7!null y:8
 │    ├── key: (7)
 │    └── fd: (7)-->(8)
 └── filters
      ├── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]
      └── i:2 < 5 [outer=(2), constraints=(/2: (/NULL - /4]; tight)]

# Columns needed only by projection, full join.
norm expect=PruneJoinLeftCols
SELECT a.k+1 AS r, xy.* FROM a FULL JOIN xy ON True
----
project
 ├── columns: r:11 x:7 y:8
 ├── immutable
 ├── fd: (7)-->(8)
 ├── full-join (cross)
 │    ├── columns: k:1 x:7 y:8
 │    ├── key: (1,7)
 │    ├── fd: (7)-->(8)
 │    ├── scan a
 │    │    ├── columns: k:1!null
 │    │    └── key: (1)
 │    ├── scan xy
 │    │    ├── columns: x:7!null y:8
 │    │    ├── key: (7)
 │    │    └── fd: (7)-->(8)
 │    └── filters (true)
 └── projections
      └── k:1 + 1 [as=r:11, outer=(1), immutable]

# No columns needed from left side of join.
norm expect=PruneJoinLeftCols
SELECT xy.* FROM a, xy
----
inner-join (cross)
 ├── columns: x:7!null y:8
 ├── fd: (7)-->(8)
 ├── scan a
 ├── scan xy
 │    ├── columns: x:7!null y:8
 │    ├── key: (7)
 │    └── fd: (7)-->(8)
 └── filters (true)

# Computed columns.
norm expect=PruneJoinLeftCols
SELECT a.k+1 AS r, a.i/2 AS s, xy.* FROM a INNER JOIN xy ON a.k*a.k=xy.x AND a.s||'o'='foo'
----
project
 ├── columns: r:12!null s:13 x:7!null y:8
 ├── immutable
 ├── fd: (7)-->(8)
 ├── inner-join (hash)
 │    ├── columns: k:1!null i:2 x:7!null y:8 column11:11!null
 │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-more)
 │    ├── immutable
 │    ├── key: (1)
 │    ├── fd: (1)-->(2,11), (7)-->(8), (7)==(11), (11)==(7)
 │    ├── project
 │    │    ├── columns: column11:11!null k:1!null i:2
 │    │    ├── immutable
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(2,11)
 │    │    ├── select
 │    │    │    ├── columns: k:1!null i:2 a.s:4
 │    │    │    ├── immutable
 │    │    │    ├── key: (1)
 │    │    │    ├── fd: (1)-->(2,4)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:1!null i:2 a.s:4
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2,4)
 │    │    │    └── filters
 │    │    │         └── (a.s:4 || 'o') = 'foo' [outer=(4), immutable]
 │    │    └── projections
 │    │         └── k:1 * k:1 [as=column11:11, outer=(1), immutable]
 │    ├── scan xy
 │    │    ├── columns: x:7!null y:8
 │    │    ├── key: (7)
 │    │    └── fd: (7)-->(8)
 │    └── filters
 │         └── column11:11 = x:7 [outer=(7,11), constraints=(/7: (/NULL - ]; /11: (/NULL - ]), fd=(7)==(11), (11)==(7)]
 └── projections
      ├── k:1 + 1 [as=r:12, outer=(1), immutable]
      └── i:2 / 2 [as=s:13, outer=(2)]

# Join that is nested in another join.
norm expect=PruneJoinLeftCols
SELECT a.k, xy.*
FROM
(
    SELECT * FROM a INNER JOIN xy ON a.k=xy.x
) a
INNER JOIN xy
ON a.i < xy.y
----
project
 ├── columns: k:1!null x:11!null y:12!null
 ├── key: (1,11)
 ├── fd: (11)-->(12)
 └── inner-join (cross)
      ├── columns: k:1!null i:2!null x:7!null x:11!null y:12!null
      ├── key: (7,11)
      ├── fd: (1)-->(2), (11)-->(12), (1)==(7), (7)==(1)
      ├── inner-join (hash)
      │    ├── columns: k:1!null i:2 x:7!null
      │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      │    ├── key: (7)
      │    ├── fd: (1)-->(2), (1)==(7), (7)==(1)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan xy
      │    │    ├── columns: x:7!null
      │    │    └── key: (7)
      │    └── filters
      │         └── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]
      ├── scan xy
      │    ├── columns: x:11!null y:12
      │    ├── key: (11)
      │    └── fd: (11)-->(12)
      └── filters
           └── i:2 < y:12 [outer=(2,12), constraints=(/2: (/NULL - ]; /12: (/NULL - ])]

# ApplyJoin operator.
norm expect=PruneJoinLeftCols disable=TryRemapJoinOuterColsRight
SELECT k, i
FROM a
WHERE (SELECT k+1 AS r FROM xy WHERE y=k) = 1
----
project
 ├── columns: k:1!null i:2
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2)
 └── select
      ├── columns: k:1!null i:2 r:11!null
      ├── immutable
      ├── key: (1)
      ├── fd: ()-->(11), (1)-->(2)
      ├── ensure-distinct-on
      │    ├── columns: k:1!null i:2 r:11
      │    ├── grouping columns: k:1!null
      │    ├── error: "more than one row returned by a subquery used as an expression"
      │    ├── immutable
      │    ├── key: (1)
      │    ├── fd: (1)-->(2,11)
      │    ├── left-join-apply
      │    │    ├── columns: k:1!null i:2 y:8 r:11
      │    │    ├── immutable
      │    │    ├── fd: (1)-->(2)
      │    │    ├── scan a
      │    │    │    ├── columns: k:1!null i:2
      │    │    │    ├── key: (1)
      │    │    │    └── fd: (1)-->(2)
      │    │    ├── project
      │    │    │    ├── columns: r:11 y:8
      │    │    │    ├── outer: (1)
      │    │    │    ├── immutable
      │    │    │    ├── fd: ()-->(11)
      │    │    │    ├── scan xy
      │    │    │    │    └── columns: y:8
      │    │    │    └── projections
      │    │    │         └── k:1 + 1 [as=r:11, outer=(1), immutable]
      │    │    └── filters
      │    │         └── y:8 = k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      │    └── aggregations
      │         ├── const-agg [as=i:2, outer=(2)]
      │         │    └── i:2
      │         └── const-agg [as=r:11, outer=(11)]
      │              └── r:11
      └── filters
           └── r:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]

# SemiJoin operator.
norm expect=PruneJoinLeftCols
SELECT a.i
FROM a
WHERE
    EXISTS(SELECT * FROM xy WHERE a.k=xy.x) AND
    EXISTS(SELECT * FROM xy WHERE a.k=xy.x)
----
project
 ├── columns: i:2
 └── semi-join (hash)
      ├── columns: k:1!null i:2
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── semi-join (hash)
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan xy
      │    │    ├── columns: x:11!null
      │    │    └── key: (11)
      │    └── filters
      │         └── k:1 = x:11 [outer=(1,11), constraints=(/1: (/NULL - ]; /11: (/NULL - ]), fd=(1)==(11), (11)==(1)]
      ├── scan xy
      │    ├── columns: x:7!null
      │    └── key: (7)
      └── filters
           └── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

# AntiJoin operator.
norm expect=PruneJoinLeftCols
SELECT a.i
FROM a
WHERE
    NOT EXISTS(SELECT * FROM xy WHERE a.k=xy.x) AND
    NOT EXISTS(SELECT * FROM xy WHERE a.k=xy.x)
----
project
 ├── columns: i:2
 └── anti-join (hash)
      ├── columns: k:1!null i:2
      ├── key: (1)
      ├── fd: (1)-->(2)
      ├── anti-join (hash)
      │    ├── columns: k:1!null i:2
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    ├── scan xy
      │    │    ├── columns: x:11!null
      │    │    └── key: (11)
      │    └── filters
      │         └── k:1 = x:11 [outer=(1,11), constraints=(/1: (/NULL - ]; /11: (/NULL - ]), fd=(1)==(11), (11)==(1)]
      ├── scan xy
      │    ├── columns: x:7!null
      │    └── key: (7)
      └── filters
           └── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

# --------------------------------------------------
# PruneJoinRightCols
# --------------------------------------------------

# Columns used only by projection or on condition, but not both.
norm expect=PruneJoinRightCols
SELECT xy.*, a.i FROM xy INNER JOIN a ON xy.x=a.k
----
project
 ├── columns: x:1!null y:2 i:6
 ├── key: (1)
 ├── fd: (1)-->(2,6)
 └── inner-join (hash)
      ├── columns: x:1!null y:2 k:5!null i:6
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      ├── key: (5)
      ├── fd: (1)-->(2), (5)-->(6), (1)==(5), (5)==(1)
      ├── scan xy
      │    ├── columns: x:1!null y:2
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── scan a
      │    ├── columns: k:5!null i:6
      │    ├── key: (5)
      │    └── fd: (5)-->(6)
      └── filters
           └── x:1 = k:5 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]

# Columns used by both projection and on condition, left join.
norm expect=PruneJoinRightCols
SELECT xy.*, a.k, a.i FROM xy LEFT JOIN a ON xy.x=a.k AND a.i<xy.x
----
left-join (hash)
 ├── columns: x:1!null y:2 k:5 i:6
 ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 ├── key: (1)
 ├── fd: (1)-->(2,5,6), (5)-->(6)
 ├── scan xy
 │    ├── columns: x:1!null y:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── select
 │    ├── columns: k:5!null i:6!null
 │    ├── key: (5)
 │    ├── fd: (5)-->(6)
 │    ├── scan a
 │    │    ├── columns: k:5!null i:6
 │    │    ├── key: (5)
 │    │    └── fd: (5)-->(6)
 │    └── filters
 │         └── i:6 < k:5 [outer=(5,6), constraints=(/5: (/NULL - ]; /6: (/NULL - ])]
 └── filters
      └── x:1 = k:5 [outer=(1,5), constraints=(/1: (/NULL - ]; /5: (/NULL - ]), fd=(1)==(5), (5)==(1)]

# Columns needed only by projection, full join.
norm expect=PruneJoinRightCols
SELECT xy.*, a.k+1 AS r FROM xy FULL JOIN a ON True
----
project
 ├── columns: x:1 y:2 r:11
 ├── immutable
 ├── fd: (1)-->(2)
 ├── full-join (cross)
 │    ├── columns: x:1 y:2 k:5
 │    ├── key: (1,5)
 │    ├── fd: (1)-->(2)
 │    ├── scan xy
 │    │    ├── columns: x:1!null y:2
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2)
 │    ├── scan a
 │    │    ├── columns: k:5!null
 │    │    └── key: (5)
 │    └── filters (true)
 └── projections
      └── k:5 + 1 [as=r:11, outer=(5), immutable]

# No columns needed from right side of join.
norm expect=PruneJoinRightCols
SELECT xy.* FROM xy, a
----
inner-join (cross)
 ├── columns: x:1!null y:2
 ├── fd: (1)-->(2)
 ├── scan xy
 │    ├── columns: x:1!null y:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 ├── scan a
 └── filters (true)

# Computed columns.
norm expect=PruneJoinRightCols
SELECT xy.*, a.k+1 AS r, a.i/2 AS s FROM xy INNER JOIN a ON xy.x=a.k*a.k AND a.s||'o'='foo'
----
project
 ├── columns: x:1!null y:2 r:12!null s:13
 ├── immutable
 ├── fd: (1)-->(2)
 ├── inner-join (hash)
 │    ├── columns: x:1!null y:2 k:5!null i:6 column11:11!null
 │    ├── multiplicity: left-rows(zero-or-more), right-rows(zero-or-one)
 │    ├── immutable
 │    ├── key: (5)
 │    ├── fd: (1)-->(2), (5)-->(6,11), (1)==(11), (11)==(1)
 │    ├── scan xy
 │    │    ├── columns: x:1!null y:2
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2)
 │    ├── project
 │    │    ├── columns: column11:11!null k:5!null i:6
 │    │    ├── immutable
 │    │    ├── key: (5)
 │    │    ├── fd: (5)-->(6,11)
 │    │    ├── select
 │    │    │    ├── columns: k:5!null i:6 a.s:8
 │    │    │    ├── immutable
 │    │    │    ├── key: (5)
 │    │    │    ├── fd: (5)-->(6,8)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:5!null i:6 a.s:8
 │    │    │    │    ├── key: (5)
 │    │    │    │    └── fd: (5)-->(6,8)
 │    │    │    └── filters
 │    │    │         └── (a.s:8 || 'o') = 'foo' [outer=(8), immutable]
 │    │    └── projections
 │    │         └── k:5 * k:5 [as=column11:11, outer=(5), immutable]
 │    └── filters
 │         └── x:1 = column11:11 [outer=(1,11), constraints=(/1: (/NULL - ]; /11: (/NULL - ]), fd=(1)==(11), (11)==(1)]
 └── projections
      ├── k:5 + 1 [as=r:12, outer=(5), immutable]
      └── i:6 / 2 [as=s:13, outer=(6)]

# Join that is nested in another join.
norm expect=PruneJoinRightCols
SELECT a.k, xy.*
FROM xy
INNER JOIN
(
    SELECT * FROM a INNER JOIN xy ON a.k=xy.x
) a
ON a.y < xy.y
----
project
 ├── columns: k:5!null x:1!null y:2!null
 ├── key: (1,5)
 ├── fd: (1)-->(2)
 └── inner-join (cross)
      ├── columns: x:1!null y:2!null k:5!null x:11!null y:12!null
      ├── key: (1,11)
      ├── fd: (1)-->(2), (11)-->(12), (5)==(11), (11)==(5)
      ├── scan xy
      │    ├── columns: x:1!null y:2
      │    ├── key: (1)
      │    └── fd: (1)-->(2)
      ├── inner-join (hash)
      │    ├── columns: k:5!null x:11!null y:12
      │    ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      │    ├── key: (11)
      │    ├── fd: (11)-->(12), (5)==(11), (11)==(5)
      │    ├── scan a
      │    │    ├── columns: k:5!null
      │    │    └── key: (5)
      │    ├── scan xy
      │    │    ├── columns: x:11!null y:12
      │    │    ├── key: (11)
      │    │    └── fd: (11)-->(12)
      │    └── filters
      │         └── k:5 = x:11 [outer=(5,11), constraints=(/5: (/NULL - ]; /11: (/NULL - ]), fd=(5)==(11), (11)==(5)]
      └── filters
           └── y:12 < y:2 [outer=(2,12), constraints=(/2: (/NULL - ]; /12: (/NULL - ])]

# --------------------------------------------------
# PruneJoinLeftCols + PruneJoinRightCols
# --------------------------------------------------

# Columns not needed by either side of join.
norm expect=(PruneJoinLeftCols,PruneJoinRightCols)
SELECT 1 r FROM a,xy
----
project
 ├── columns: r:11!null
 ├── fd: ()-->(11)
 ├── inner-join (cross)
 │    ├── scan a
 │    ├── scan xy
 │    └── filters (true)
 └── projections
      └── 1 [as=r:11]

# Subset of columns needed by each side of join.
norm expect=(PruneJoinLeftCols,PruneJoinRightCols)
SELECT a.k, xy.x, a.k+xy.x AS r FROM a LEFT JOIN xy ON a.k=xy.x
----
project
 ├── columns: k:1!null x:7 r:11
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(7,11)
 ├── left-join (hash)
 │    ├── columns: k:1!null x:7
 │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
 │    ├── key: (1)
 │    ├── fd: (1)-->(7)
 │    ├── scan a
 │    │    ├── columns: k:1!null
 │    │    └── key: (1)
 │    ├── scan xy
 │    │    ├── columns: x:7!null
 │    │    └── key: (7)
 │    └── filters
 │         └── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]
 └── projections
      └── k:1 + x:7 [as=r:11, outer=(1,7), immutable]

# --------------------------------------------------
# PruneAggCols
# --------------------------------------------------

# Discard all aggregates.
norm expect=PruneAggCols
SELECT s FROM (SELECT s, sum(i), min(s||'foo') FROM a GROUP BY s) a
----
distinct-on
 ├── columns: s:4
 ├── grouping columns: s:4
 ├── key: (4)
 └── scan a
      └── columns: s:4

# Discard subset of aggregates.
norm expect=PruneAggCols
SELECT s, sumi FROM (SELECT sum(i) sumi, s, min(s||'foo') FROM a GROUP BY s) a
----
group-by (hash)
 ├── columns: s:4 sumi:7
 ├── grouping columns: s:4
 ├── key: (4)
 ├── fd: (4)-->(7)
 ├── scan a
 │    └── columns: i:2 s:4
 └── aggregations
      └── sum [as=sum:7, outer=(2)]
           └── i:2

# No aggregates to discard.
norm expect-not=PruneAggCols
SELECT 1 r FROM (SELECT s FROM a GROUP BY s) a
----
project
 ├── columns: r:7!null
 ├── fd: ()-->(7)
 ├── distinct-on
 │    ├── columns: s:4
 │    ├── grouping columns: s:4
 │    ├── key: (4)
 │    └── scan a
 │         └── columns: s:4
 └── projections
      └── 1 [as=r:7]

# Scalar GroupBy case.
norm expect=PruneAggCols
SELECT sumi FROM (SELECT sum(i) sumi, min(s||'foo') FROM a) a
----
scalar-group-by
 ├── columns: sumi:7
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(7)
 ├── scan a
 │    └── columns: i:2
 └── aggregations
      └── sum [as=sum:7, outer=(2)]
           └── i:2

# DistinctOn case.
norm expect=PruneAggCols
SELECT f FROM (SELECT DISTINCT ON (i) f, s FROM a)
----
project
 ├── columns: f:3
 └── distinct-on
      ├── columns: i:2 f:3
      ├── grouping columns: i:2
      ├── key: (2)
      ├── fd: (2)-->(3)
      ├── scan a
      │    └── columns: i:2 f:3
      └── aggregations
           └── first-agg [as=f:3, outer=(3)]
                └── f:3

# EnsureDistinctOn case.
norm expect=PruneAggCols
SELECT max((SELECT y FROM xy WHERE y=i)) FROM a
----
scalar-group-by
 ├── columns: max:12
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(12)
 ├── project
 │    ├── columns: column11:11
 │    ├── ensure-distinct-on
 │    │    ├── columns: k:1!null y:8
 │    │    ├── grouping columns: k:1!null
 │    │    ├── error: "more than one row returned by a subquery used as an expression"
 │    │    ├── key: (1)
 │    │    ├── fd: (1)-->(8)
 │    │    ├── left-join (hash)
 │    │    │    ├── columns: k:1!null i:2 y:8
 │    │    │    ├── fd: (1)-->(2)
 │    │    │    ├── scan a
 │    │    │    │    ├── columns: k:1!null i:2
 │    │    │    │    ├── key: (1)
 │    │    │    │    └── fd: (1)-->(2)
 │    │    │    ├── scan xy
 │    │    │    │    └── columns: y:8
 │    │    │    └── filters
 │    │    │         └── y:8 = i:2 [outer=(2,8), constraints=(/2: (/NULL - ]; /8: (/NULL - ]), fd=(2)==(8), (8)==(2)]
 │    │    └── aggregations
 │    │         └── const-agg [as=y:8, outer=(8)]
 │    │              └── y:8
 │    └── projections
 │         └── y:8 [as=column11:11, outer=(8)]
 └── aggregations
      └── max [as=max:12, outer=(11)]
           └── column11:11

# Columns used only by aggregation, no grouping columns.
norm expect=PruneAggCols
SELECT min(i), max(k), max(k) FROM a ORDER BY max(f)
----
scalar-group-by
 ├── columns: min:7 max:8 max:8
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(7,8)
 ├── scan a
 │    ├── columns: k:1!null i:2
 │    ├── key: (1)
 │    └── fd: (1)-->(2)
 └── aggregations
      ├── min [as=min:7, outer=(2)]
      │    └── i:2
      └── max [as=max:8, outer=(1)]
           └── k:1

# --------------------------------------------------
# PruneGroupByCols
# --------------------------------------------------

# Columns used by grouping or aggregation, but not both should not be pruned.
norm expect=PruneGroupByCols
SELECT s, sum(i) FROM a GROUP BY s, s||'foo'
----
group-by (hash)
 ├── columns: s:4 sum:7
 ├── grouping columns: s:4
 ├── key: (4)
 ├── fd: (4)-->(7)
 ├── scan a
 │    └── columns: i:2 s:4
 └── aggregations
      └── sum [as=sum:7, outer=(2)]
           └── i:2

# Columns used by both grouping and aggregation should not be pruned.
norm expect=PruneGroupByCols
SELECT avg(s::int+i), s, i FROM a GROUP BY s, i, i+1
----
group-by (hash)
 ├── columns: avg:8 s:4 i:2
 ├── grouping columns: i:2 s:4
 ├── immutable
 ├── key: (2,4)
 ├── fd: (2,4)-->(8)
 ├── project
 │    ├── columns: column7:7 i:2 s:4
 │    ├── immutable
 │    ├── fd: (2,4)-->(7)
 │    ├── scan a
 │    │    └── columns: i:2 s:4
 │    └── projections
 │         └── i:2 + s:4::INT8 [as=column7:7, outer=(2,4), immutable]
 └── aggregations
      └── avg [as=avg:8, outer=(7)]
           └── column7:7

# Columns used only by groupings, no aggregation columns.
norm expect=PruneGroupByCols
SELECT s, i+1 AS r FROM a GROUP BY i, s, s||'foo'
----
project
 ├── columns: s:4 r:8
 ├── immutable
 ├── distinct-on
 │    ├── columns: i:2 s:4
 │    ├── grouping columns: i:2 s:4
 │    ├── key: (2,4)
 │    └── scan a
 │         └── columns: i:2 s:4
 └── projections
      └── i:2 + 1 [as=r:8, outer=(2), immutable]

# Groupby a groupby.
norm expect=PruneGroupByCols
SELECT min(sm), i FROM (SELECT s, i, sum(k) sm, avg(k) av FROM a GROUP BY i, s) a GROUP BY i, i+1
----
group-by (hash)
 ├── columns: min:9!null i:2
 ├── grouping columns: i:2
 ├── key: (2)
 ├── fd: (2)-->(9)
 ├── group-by (hash)
 │    ├── columns: i:2 s:4 sum:7!null
 │    ├── grouping columns: i:2 s:4
 │    ├── key: (2,4)
 │    ├── fd: (2,4)-->(7)
 │    ├── scan a
 │    │    ├── columns: k:1!null i:2 s:4
 │    │    ├── key: (1)
 │    │    └── fd: (1)-->(2,4)
 │    └── aggregations
 │         └── sum [as=sum:7, outer=(1)]
 │              └── k:1
 └── aggregations
      └── min [as=min:9, outer=(7)]
           └── sum:7

# Distinct (GroupBy operator with no aggregates).
norm expect=PruneGroupByCols
SELECT DISTINCT ON (s, s||'foo') s, f FROM a
----
distinct-on
 ├── columns: s:4 f:3
 ├── grouping columns: s:4
 ├── key: (4)
 ├── fd: (4)-->(3)
 ├── scan a
 │    └── columns: f:3 s:4
 └── aggregations
      └── first-agg [as=f:3, outer=(3)]
           └── f:3

# ScalarGroupBy case.
norm expect=PruneGroupByCols
SELECT icnt FROM (SELECT count(i+1) AS icnt, count(k+1) FROM a);
----
scalar-group-by
 ├── columns: icnt:8!null
 ├── cardinality: [1 - 1]
 ├── immutable
 ├── key: ()
 ├── fd: ()-->(8)
 ├── project
 │    ├── columns: column7:7
 │    ├── immutable
 │    ├── scan a
 │    │    └── columns: i:2
 │    └── projections
 │         └── i:2 + 1 [as=column7:7, outer=(2), immutable]
 └── aggregations
      └── count [as=count:8, outer=(7)]
           └── column7:7

# --------------------------------------------------
# PruneValuesCols
# --------------------------------------------------

# Discard all but first Values column.
norm expect=PruneValuesCols
SELECT column1 FROM (VALUES (1, 2), (3, 4)) a
----
values
 ├── columns: column1:1!null
 ├── cardinality: [2 - 2]
 ├── (1,)
 └── (3,)

# Discard all but middle Values column.
norm expect=PruneValuesCols
SELECT column2 FROM (VALUES (1, 2, 3), (4, 5, 6)) a
----
values
 ├── columns: column2:2!null
 ├── cardinality: [2 - 2]
 ├── (2,)
 └── (5,)

# Discard all but last Values column.
norm expect=PruneValuesCols
SELECT column3 FROM (VALUES ('foo', 'bar', 'baz'), ('apple', 'banana', 'cherry')) a
----
values
 ├── columns: column3:3!null
 ├── cardinality: [2 - 2]
 ├── ('baz',)
 └── ('cherry',)

# Discard all Values columns.
norm expect=PruneValuesCols
SELECT 1 r FROM (VALUES ('foo', 'bar', 'baz'), ('apple', 'banana', 'cherry')) a
----
project
 ├── columns: r:4!null
 ├── cardinality: [2 - 2]
 ├── fd: ()-->(4)
 ├── values
 │    ├── cardinality: [2 - 2]
 │    ├── ()
 │    └── ()
 └── projections
      └── 1 [as=r:4]

# --------------------------------------------------
# Prune - multiple combined operators
# --------------------------------------------------

norm
SELECT a.k, xy.y FROM a INNER JOIN xy ON a.k=xy.x WHERE a.i < 5
----
project
 ├── columns: k:1!null y:8
 ├── key: (1)
 ├── fd: (1)-->(8)
 └── inner-join (hash)
      ├── columns: k:1!null i:2!null x:7!null y:8
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      ├── key: (7)
      ├── fd: (1)-->(2), (7)-->(8), (1)==(7), (7)==(1)
      ├── select
      │    ├── columns: k:1!null i:2!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(2)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── filters
      │         └── i:2 < 5 [outer=(2), constraints=(/2: (/NULL - /4]; tight)]
      ├── scan xy
      │    ├── columns: x:7!null y:8
      │    ├── key: (7)
      │    └── fd: (7)-->(8)
      └── filters
           └── k:1 = x:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)]

norm
SELECT k FROM (SELECT k, min(s) FROM a GROUP BY k HAVING sum(i) > 5)
----
project
 ├── columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── select
      ├── columns: k:1!null sum:8!null
      ├── immutable
      ├── key: (1)
      ├── fd: (1)-->(8)
      ├── group-by (hash)
      │    ├── columns: k:1!null sum:8
      │    ├── grouping columns: k:1!null
      │    ├── key: (1)
      │    ├── fd: (1)-->(8)
      │    ├── scan a
      │    │    ├── columns: k:1!null i:2
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(2)
      │    └── aggregations
      │         └── sum [as=sum:8, outer=(2)]
      │              └── i:2
      └── filters
           └── sum:8 > 5 [outer=(8), immutable, constraints=(/8: (/5 - ]; tight)]

# --------------------------------------------------
# PruneOrdinalityCols
# --------------------------------------------------
norm expect=PruneOrdinalityCols
SELECT i, s FROM a WITH ORDINALITY
----
project
 ├── columns: i:2 s:4
 └── ordinality
      ├── columns: i:2 s:4 ordinality:7!null
      ├── key: (7)
      ├── fd: (7)-->(2,4)
      └── scan a
           └── columns: i:2 s:4

# With order by.
norm expect=PruneOrdinalityCols
SELECT i, s FROM (SELECT * FROM a ORDER BY f) WITH ORDINALITY
----
project
 ├── columns: i:2 s:4
 └── ordinality
      ├── columns: i:2 f:3 s:4 ordinality:7!null
      ├── key: (7)
      ├── fd: (7)-->(2-4)
      └── sort
           ├── columns: i:2 f:3 s:4
           ├── ordering: +3
           └── scan a
                └── columns: i:2 f:3 s:4

# --------------------------------------------------
# PruneExplainCols
# --------------------------------------------------
norm expect=PruneExplainCols
EXPLAIN SELECT a FROM abcde WHERE b=1 AND c IS NOT NULL ORDER BY c, d
----
explain
 ├── columns: info:8
 └── sort
      ├── columns: a:1!null  [hidden: c:3!null]
      ├── key: (1)
      ├── fd: (1)-->(3), (3)-->(1)
      ├── ordering: +3
      └── project
           ├── columns: a:1!null c:3!null
           ├── key: (1)
           ├── fd: (1)-->(3), (3)-->(1)
           └── select
                ├── columns: a:1!null b:2!null c:3!null
                ├── key: (1)
                ├── fd: ()-->(2), (1)-->(3), (3)-->(1)
                ├── scan abcde
                │    ├── columns: a:1!null b:2 c:3
                │    ├── key: (1)
                │    └── fd: (1)-->(2,3), (2,3)~~>(1)
                └── filters
                     ├── b:2 = 1 [outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)]
                     └── c:3 IS NOT NULL [outer=(3), constraints=(/3: (/NULL - ]; tight)]

# --------------------------------------------------
# PruneProjectSetCols
# --------------------------------------------------
norm expect=PruneProjectSetCols
SELECT a, b, generate_series(c, 10) FROM abcde
----
project
 ├── columns: a:1!null b:2 generate_series:8
 ├── immutable
 ├── fd: (1)-->(2)
 └── project-set
      ├── columns: a:1!null b:2 c:3 generate_series:8
      ├── immutable
      ├── fd: (1)-->(2,3), (2,3)~~>(1)
      ├── scan abcde
      │    ├── columns: a:1!null b:2 c:3
      │    ├── key: (1)
      │    └── fd: (1)-->(2,3), (2,3)~~>(1)
      └── zip
           └── generate_series(c:3, 10) [outer=(3), immutable]

norm expect=PruneProjectSetCols
SELECT k FROM a WHERE EXISTS(SELECT * FROM ROWS FROM (generate_series(i, k), length(s)))
----
distinct-on
 ├── columns: k:1!null
 ├── grouping columns: k:1!null
 ├── immutable
 ├── key: (1)
 └── project-set
      ├── columns: k:1!null i:2 s:4 generate_series:7 length:8
      ├── immutable
      ├── fd: (1)-->(2,4)
      ├── scan a
      │    ├── columns: k:1!null i:2 s:4
      │    ├── key: (1)
      │    └── fd: (1)-->(2,4)
      └── zip
           ├── generate_series(i:2, k:1) [outer=(1,2), immutable]
           └── length(s:4) [outer=(4), immutable]

# --------------------------------------------------
# PruneWindowInputCols
# --------------------------------------------------

norm expect=PruneWindowInputCols
SELECT rank() OVER () FROM a
----
window partition=()
 ├── columns: rank:7
 ├── scan a
 └── windows
      └── rank [as=rank:7]

norm expect=PruneWindowInputCols
SELECT ntile(1) OVER () FROM a
----
project
 ├── columns: ntile:7
 └── window partition=()
      ├── columns: ntile:7 ntile_1_arg1:8!null
      ├── fd: ()-->(8)
      ├── project
      │    ├── columns: ntile_1_arg1:8!null
      │    ├── fd: ()-->(8)
      │    ├── scan a
      │    └── projections
      │         └── 1 [as=ntile_1_arg1:8]
      └── windows
           └── ntile [as=ntile:7, outer=(8)]
                └── ntile_1_arg1:8

norm expect=PruneWindowInputCols format=show-all
SELECT ntile(i) OVER () FROM a
----
project
 ├── columns: ntile:7(int)
 ├── stats: [rows=1000]
 ├── cost: 1088.56
 ├── prune: (7)
 └── window partition=()
      ├── columns: t.public.a.i:2(int) ntile:7(int)
      ├── stats: [rows=1000]
      ├── cost: 1078.54
      ├── prune: (7)
      ├── scan t.public.a
      │    ├── columns: t.public.a.i:2(int)
      │    ├── stats: [rows=1000]
      │    ├── cost: 1078.52
      │    └── prune: (2)
      └── windows
           └── ntile [as=ntile:7, type=int, outer=(2)]
                └── variable: t.public.a.i:2 [type=int]

# Ensure filter cols don't get pruned.
norm
SELECT
    avg(i) FILTER (WHERE true) OVER (),
    avg(i) FILTER (WHERE false) OVER ()
FROM a
----
project
 ├── columns: avg:7 avg:8
 └── window partition=()
      ├── columns: i:2 avg:7 avg:8 avg_1_filter:9!null avg_2_filter:10!null
      ├── fd: ()-->(9,10)
      ├── project
      │    ├── columns: avg_1_filter:9!null avg_2_filter:10!null i:2
      │    ├── fd: ()-->(9,10)
      │    ├── scan a
      │    │    └── columns: i:2
      │    └── projections
      │         ├── true [as=avg_1_filter:9]
      │         └── false [as=avg_2_filter:10]
      └── windows
           ├── agg-filter [as=avg:7, outer=(2,9)]
           │    ├── avg
           │    │    └── i:2
           │    └── avg_1_filter:9
           └── agg-filter [as=avg:8, outer=(2,10)]
                ├── avg
                │    └── i:2
                └── avg_2_filter:10

# --------------------------------------------------
# PruneWindowOutputCols
# --------------------------------------------------

norm expect=PruneWindowOutputCols
SELECT x FROM (SELECT ntile(1) OVER () AS x, ntile(2) OVER () FROM a)
----
project
 ├── columns: x:7
 └── window partition=()
      ├── columns: ntile:7 ntile_1_arg1:9!null
      ├── fd: ()-->(9)
      ├── project
      │    ├── columns: ntile_1_arg1:9!null
      │    ├── fd: ()-->(9)
      │    ├── scan a
      │    └── projections
      │         └── 1 [as=ntile_1_arg1:9]
      └── windows
           └── ntile [as=ntile:7, outer=(9)]
                └── ntile_1_arg1:9

norm expect=(PruneWindowOutputCols,EliminateWindow)
SELECT 1 FROM (SELECT ntile(1) OVER () FROM a)
----
project
 ├── columns: "?column?":9!null
 ├── fd: ()-->(9)
 ├── scan a
 └── projections
      └── 1 [as="?column?":9]

norm expect=(PruneWindowOutputCols,EliminateWindow)
SELECT 1 FROM (SELECT x FROM (SELECT ntile(1) OVER () AS x, ntile(2) OVER () FROM a))
----
project
 ├── columns: "?column?":11!null
 ├── fd: ()-->(11)
 ├── scan a
 └── projections
      └── 1 [as="?column?":11]

norm expect-not=PruneWindowOutputCols
SELECT round(avg(k) OVER (PARTITION BY f ORDER BY s)) FROM a ORDER BY 1
----
sort
 ├── columns: round:8
 ├── immutable
 ├── ordering: +8
 └── project
      ├── columns: round:8
      ├── immutable
      ├── window partition=(3) ordering=+4 opt(3)
      │    ├── columns: k:1!null f:3 s:4 avg:7
      │    ├── key: (1)
      │    ├── fd: (1)-->(3,4,7)
      │    ├── scan a
      │    │    ├── columns: k:1!null f:3 s:4
      │    │    ├── key: (1)
      │    │    └── fd: (1)-->(3,4)
      │    └── windows
      │         └── avg [as=avg:7, outer=(1)]
      │              └── k:1
      └── projections
           └── round(avg:7) [as=round:8, outer=(7), immutable]

norm expect=(PruneWindowInputCols,PruneWindowOutputCols) format=show-all
SELECT x FROM (SELECT ntile(i) OVER () x, ntile(f::int) OVER () y FROM a)
----
project
 ├── columns: x:7(int)
 ├── stats: [rows=1000]
 ├── cost: 1088.56
 ├── prune: (7)
 └── window partition=()
      ├── columns: t.public.a.i:2(int) ntile:7(int)
      ├── stats: [rows=1000]
      ├── cost: 1078.54
      ├── prune: (7)
      ├── scan t.public.a
      │    ├── columns: t.public.a.i:2(int)
      │    ├── stats: [rows=1000]
      │    ├── cost: 1078.52
      │    └── prune: (2)
      └── windows
           └── ntile [as=ntile:7, type=int, outer=(2)]
                └── variable: t.public.a.i:2 [type=int]


# --------------------------------------------------
# PruneMutationFetchCols + PruneMutationInputCols
# --------------------------------------------------

# Prune all but the key column.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
DELETE FROM a
----
delete a
 ├── columns: <none>
 ├── fetch columns: k:7
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── scan a
      ├── columns: k:7!null
      ├── flags: avoid-full-scan
      └── key: (7)

# Prune when computed ordering column is present.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
DELETE FROM a WHERE i > 0 ORDER BY i*2 LIMIT 10
----
delete a
 ├── columns: <none>
 ├── fetch columns: k:7
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── limit
      ├── columns: k:7!null column13:13!null
      ├── internal-ordering: +13
      ├── cardinality: [0 - 10]
      ├── immutable
      ├── key: (7)
      ├── fd: (7)-->(13)
      ├── sort
      │    ├── columns: k:7!null column13:13!null
      │    ├── immutable
      │    ├── key: (7)
      │    ├── fd: (7)-->(13)
      │    ├── ordering: +13
      │    ├── limit hint: 10.00
      │    └── project
      │         ├── columns: column13:13!null k:7!null
      │         ├── immutable
      │         ├── key: (7)
      │         ├── fd: (7)-->(13)
      │         ├── select
      │         │    ├── columns: k:7!null i:8!null
      │         │    ├── key: (7)
      │         │    ├── fd: (7)-->(8)
      │         │    ├── scan a
      │         │    │    ├── columns: k:7!null i:8
      │         │    │    ├── flags: avoid-full-scan
      │         │    │    ├── key: (7)
      │         │    │    └── fd: (7)-->(8)
      │         │    └── filters
      │         │         └── i:8 > 0 [outer=(8), constraints=(/8: [/1 - ]; tight)]
      │         └── projections
      │              └── i:8 * 2 [as=column13:13, outer=(8), immutable]
      └── 10

# Prune when a secondary index is present on the table.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
DELETE FROM abcde WHERE a > 0
----
delete abcde
 ├── columns: <none>
 ├── fetch columns: a:8 b:9 c:10
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── select
      ├── columns: a:8!null b:9 c:10
      ├── key: (8)
      ├── fd: (8)-->(9,10), (9,10)~~>(8)
      ├── scan abcde
      │    ├── columns: a:8!null b:9 c:10
      │    ├── flags: avoid-full-scan
      │    ├── key: (8)
      │    └── fd: (8)-->(9,10), (9,10)~~>(8)
      └── filters
           └── a:8 > 0 [outer=(8), constraints=(/8: [/1 - ]; tight)]

# Prune when mutation columns/indexes exist.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
DELETE FROM mutation
----
delete mutation
 ├── columns: <none>
 ├── fetch columns: a:8 b:9 d:11 e:12
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── scan mutation
      ├── columns: a:8!null b:9 d:11 e:12
      ├── flags: avoid-full-scan
      ├── key: (8)
      └── fd: (8)-->(9,11,12)

norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
DELETE FROM a RETURNING k, s
----
delete a
 ├── columns: k:1!null s:4
 ├── fetch columns: k:7 s:10
 ├── return-mapping:
 │    ├── k:7 => k:1
 │    └── s:10 => s:4
 ├── volatile, mutations
 ├── key: (1)
 ├── fd: (1)-->(4)
 └── scan a
      ├── columns: k:7!null s:10
      ├── flags: avoid-full-scan
      ├── key: (7)
      └── fd: (7)-->(10)

# We should not be producing a value for the computed column d, which is not
# being changed. We should, however, produce a value for the write-only column
# x even if the column it depends on hasn't changed.
norm expect=PruneMutationInputCols
UPDATE computed SET b = b*2 WHERE b = 1
----
update computed
 ├── columns: <none>
 ├── fetch columns: a:8 b:9 x:12
 ├── update-mapping:
 │    ├── b_new:15 => b:2
 │    └── x_comp:17 => x:5
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: x_comp:17 b_new:15!null a:8!null b:9!null x:12
      ├── immutable
      ├── key: (8)
      ├── fd: ()-->(9,15), (8)-->(12,17)
      ├── select
      │    ├── columns: a:8!null b:9!null c:10 x:12
      │    ├── key: (8)
      │    ├── fd: ()-->(9), (8)-->(10,12)
      │    ├── scan computed
      │    │    ├── columns: a:8!null b:9 c:10 x:12
      │    │    ├── computed column expressions
      │    │    │    └── d:11
      │    │    │         └── c:10 + 1
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (8)
      │    │    └── fd: (8)-->(9,10,12)
      │    └── filters
      │         └── b:9 = 1 [outer=(9), constraints=(/9: [/1 - /1]; tight), fd=()-->(9)]
      └── projections
           ├── c:10 + 10 [as=x_comp:17, outer=(10), immutable]
           └── b:9 * 2 [as=b_new:15, outer=(9), immutable]

# We should produce a value for the computed column d.
norm expect=PruneMutationInputCols
UPDATE computed SET c = c*2 WHERE b = 1
----
update computed
 ├── columns: <none>
 ├── fetch columns: a:8 c:10 d:11 x:12
 ├── update-mapping:
 │    ├── c_new:15 => c:3
 │    ├── d_comp:16 => d:4
 │    └── x_comp:17 => x:5
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: d_comp:16 x_comp:17 a:8!null c:10 d:11 x:12 c_new:15
      ├── immutable
      ├── key: (8)
      ├── fd: (8)-->(10-12), (10)-->(11,15), (15)-->(16,17)
      ├── project
      │    ├── columns: c_new:15 a:8!null c:10 d:11 x:12
      │    ├── immutable
      │    ├── key: (8)
      │    ├── fd: (8)-->(10-12), (10)-->(11,15)
      │    ├── select
      │    │    ├── columns: a:8!null b:9!null c:10 d:11 x:12
      │    │    ├── key: (8)
      │    │    ├── fd: ()-->(9), (8)-->(10-12), (10)-->(11)
      │    │    ├── scan computed
      │    │    │    ├── columns: a:8!null b:9 c:10 d:11 x:12
      │    │    │    ├── computed column expressions
      │    │    │    │    └── d:11
      │    │    │    │         └── c:10 + 1
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (8)
      │    │    │    └── fd: (8)-->(9-12), (10)-->(11)
      │    │    └── filters
      │    │         └── b:9 = 1 [outer=(9), constraints=(/9: [/1 - /1]; tight), fd=()-->(9)]
      │    └── projections
      │         └── c:10 * 2 [as=c_new:15, outer=(10), immutable]
      └── projections
           ├── c_new:15 + 1 [as=d_comp:16, outer=(15), immutable]
           └── c_new:15 + 10 [as=x_comp:17, outer=(15), immutable]

# Prune UPDATE fetch columns when the partial index indexes the column but
# neither the column nor the columns referenced in the partial index predicate
# are mutating.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
UPDATE partial_indexes SET d = d + 1 WHERE a = 1
----
update partial_indexes
 ├── columns: <none>
 ├── fetch columns: a:7 d:10
 ├── update-mapping:
 │    └── d_new:13 => d:4
 ├── partial index put columns: partial_index_put1:14
 ├── partial index del columns: partial_index_put1:14
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:14!null d_new:13 a:7!null d:10
      ├── cardinality: [0 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(7,10,13,14)
      ├── select
      │    ├── columns: a:7!null d:10
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(7,10)
      │    ├── scan partial_indexes
      │    │    ├── columns: a:7!null d:10
      │    │    ├── partial index predicates
      │    │    │    └── partial_indexes_c_idx: filters
      │    │    │         └── b:8 > 1 [outer=(8), constraints=(/8: [/2 - ]; tight)]
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (7)
      │    │    └── fd: (7)-->(10)
      │    └── filters
      │         └── a:7 = 1 [outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]
      └── projections
           ├── false [as=partial_index_put1:14]
           └── d:10 + 1 [as=d_new:13, outer=(10), immutable]

# Do not prune the indexed column c when a column in the partial index
# predicate, b, is being updated.
norm expect-not=PruneMutationFetchCols
UPDATE partial_indexes SET d = d + 1, b = 2 WHERE a = 1
----
update partial_indexes
 ├── columns: <none>
 ├── fetch columns: a:7 b:8 c:9 d:10
 ├── update-mapping:
 │    ├── b_new:14 => b:2
 │    └── d_new:13 => d:4
 ├── partial index put columns: partial_index_put1:15
 ├── partial index del columns: partial_index_del1:16
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:15!null partial_index_del1:16 d_new:13 b_new:14!null a:7!null b:8 c:9 d:10
      ├── cardinality: [0 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(7-10,13-16)
      ├── select
      │    ├── columns: a:7!null b:8 c:9 d:10
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(7-10)
      │    ├── scan partial_indexes
      │    │    ├── columns: a:7!null b:8 c:9 d:10
      │    │    ├── partial index predicates
      │    │    │    └── partial_indexes_c_idx: filters
      │    │    │         └── b:8 > 1 [outer=(8), constraints=(/8: [/2 - ]; tight)]
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (7)
      │    │    └── fd: (7)-->(8-10)
      │    └── filters
      │         └── a:7 = 1 [outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]
      └── projections
           ├── true [as=partial_index_put1:15]
           ├── b:8 > 1 [as=partial_index_del1:16, outer=(8)]
           ├── d:10 + 1 [as=d_new:13, outer=(10), immutable]
           └── 2 [as=b_new:14]

# Prune UPSERT fetch columns when the partial index indexes the column but
# neither the column nor the columns referenced in the partial index predicate
# are mutating.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
UPSERT INTO partial_indexes (a, d) VALUES (1, 2)
----
upsert partial_indexes
 ├── arbiter indexes: partial_indexes_pkey
 ├── columns: <none>
 ├── canary column: a:11
 ├── fetch columns: a:11 d:14
 ├── insert-mapping:
 │    ├── column1:7 => a:1
 │    ├── b_default:9 => b:2
 │    ├── c_default:10 => c:3
 │    └── column2:8 => d:4
 ├── update-mapping:
 │    └── column2:8 => d:4
 ├── partial index put columns: partial_index_put1:20
 ├── partial index del columns: partial_index_del1:21
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:20 partial_index_del1:21 column1:7!null column2:8!null b_default:9 c_default:10 a:11 d:14
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(7-11,14,20,21)
      ├── left-join (cross)
      │    ├── columns: column1:7!null column2:8!null b_default:9 c_default:10 a:11 b:12 d:14
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── key: ()
      │    ├── fd: ()-->(7-12,14)
      │    ├── values
      │    │    ├── columns: column1:7!null column2:8!null b_default:9 c_default:10
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(7-10)
      │    │    └── (1, 2, NULL, NULL)
      │    ├── select
      │    │    ├── columns: a:11!null b:12 d:14
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(11,12,14)
      │    │    ├── scan partial_indexes
      │    │    │    ├── columns: a:11!null b:12 d:14
      │    │    │    ├── partial index predicates
      │    │    │    │    └── partial_indexes_c_idx: filters
      │    │    │    │         └── b:12 > 1 [outer=(12), constraints=(/12: [/2 - ]; tight)]
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (11)
      │    │    │    └── fd: (11)-->(12,14)
      │    │    └── filters
      │    │         └── a:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]
      │    └── filters (true)
      └── projections
           ├── CASE WHEN a:11 IS NULL THEN b_default:9 ELSE b:12 END > 1 [as=partial_index_put1:20, outer=(9,11,12)]
           └── b:12 > 1 [as=partial_index_del1:21, outer=(12)]

# Do not prune the indexed column c when a column in the partial index
# predicate, b, is being updated.
norm expect-not=PruneMutationFetchCols
UPSERT INTO partial_indexes (a, b, d) VALUES (1, 2, 3)
----
upsert partial_indexes
 ├── arbiter indexes: partial_indexes_pkey
 ├── columns: <none>
 ├── canary column: a:11
 ├── fetch columns: a:11 b:12 c:13 d:14
 ├── insert-mapping:
 │    ├── column1:7 => a:1
 │    ├── column2:8 => b:2
 │    ├── c_default:10 => c:3
 │    └── column3:9 => d:4
 ├── update-mapping:
 │    ├── column2:8 => b:2
 │    └── column3:9 => d:4
 ├── partial index put columns: partial_index_put1:19
 ├── partial index del columns: partial_index_del1:20
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:19!null partial_index_del1:20 column1:7!null column2:8!null column3:9!null c_default:10 a:11 b:12 c:13 d:14
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(7-14,19,20)
      ├── left-join (cross)
      │    ├── columns: column1:7!null column2:8!null column3:9!null c_default:10 a:11 b:12 c:13 d:14
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── key: ()
      │    ├── fd: ()-->(7-14)
      │    ├── values
      │    │    ├── columns: column1:7!null column2:8!null column3:9!null c_default:10
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(7-10)
      │    │    └── (1, 2, 3, NULL)
      │    ├── select
      │    │    ├── columns: a:11!null b:12 c:13 d:14
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(11-14)
      │    │    ├── scan partial_indexes
      │    │    │    ├── columns: a:11!null b:12 c:13 d:14
      │    │    │    ├── partial index predicates
      │    │    │    │    └── partial_indexes_c_idx: filters
      │    │    │    │         └── b:12 > 1 [outer=(12), constraints=(/12: [/2 - ]; tight)]
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (11)
      │    │    │    └── fd: (11)-->(12-14)
      │    │    └── filters
      │    │         └── a:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]
      │    └── filters (true)
      └── projections
           ├── column2:8 > 1 [as=partial_index_put1:19, outer=(8)]
           └── b:12 > 1 [as=partial_index_del1:20, outer=(12)]

# Prune INSERT ON CONFLICT DO UPDATE fetch columns when the partial index
# indexes the column but neither the column nor the columns referenced in the
# partial index predicate are mutating.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
INSERT INTO partial_indexes (a, d) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET d = 3
----
upsert partial_indexes
 ├── arbiter indexes: partial_indexes_pkey
 ├── columns: <none>
 ├── canary column: a:11
 ├── fetch columns: a:11 d:14
 ├── insert-mapping:
 │    ├── column1:7 => a:1
 │    ├── b_default:9 => b:2
 │    ├── c_default:10 => c:3
 │    └── column2:8 => d:4
 ├── update-mapping:
 │    └── upsert_d:21 => d:4
 ├── partial index put columns: partial_index_put1:22
 ├── partial index del columns: partial_index_del1:23
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:22 partial_index_del1:23 upsert_d:21!null column1:7!null column2:8!null b_default:9 c_default:10 a:11 d:14
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(7-11,14,21-23)
      ├── left-join (cross)
      │    ├── columns: column1:7!null column2:8!null b_default:9 c_default:10 a:11 b:12 d:14
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── key: ()
      │    ├── fd: ()-->(7-12,14)
      │    ├── values
      │    │    ├── columns: column1:7!null column2:8!null b_default:9 c_default:10
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(7-10)
      │    │    └── (1, 2, NULL, NULL)
      │    ├── select
      │    │    ├── columns: a:11!null b:12 d:14
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(11,12,14)
      │    │    ├── scan partial_indexes
      │    │    │    ├── columns: a:11!null b:12 d:14
      │    │    │    ├── partial index predicates
      │    │    │    │    └── partial_indexes_c_idx: filters
      │    │    │    │         └── b:12 > 1 [outer=(12), constraints=(/12: [/2 - ]; tight)]
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (11)
      │    │    │    └── fd: (11)-->(12,14)
      │    │    └── filters
      │    │         └── a:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]
      │    └── filters (true)
      └── projections
           ├── CASE WHEN a:11 IS NULL THEN b_default:9 ELSE b:12 END > 1 [as=partial_index_put1:22, outer=(9,11,12)]
           ├── b:12 > 1 [as=partial_index_del1:23, outer=(12)]
           └── CASE WHEN a:11 IS NULL THEN column2:8 ELSE 3 END [as=upsert_d:21, outer=(8,11)]

# Do not prune the indexed column c when a column in the partial index
# predicate, b, is being updated.
norm expect-not=PruneMutationFetchCols
INSERT INTO partial_indexes (a, d) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET b = 3, d = 4
----
upsert partial_indexes
 ├── arbiter indexes: partial_indexes_pkey
 ├── columns: <none>
 ├── canary column: a:11
 ├── fetch columns: a:11 b:12 c:13 d:14
 ├── insert-mapping:
 │    ├── column1:7 => a:1
 │    ├── b_default:9 => b:2
 │    ├── c_default:10 => c:3
 │    └── column2:8 => d:4
 ├── update-mapping:
 │    ├── upsert_b:20 => b:2
 │    └── upsert_d:22 => d:4
 ├── partial index put columns: partial_index_put1:23
 ├── partial index del columns: partial_index_del1:24
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:23 partial_index_del1:24 column1:7!null column2:8!null b_default:9 c_default:10 a:11 b:12 c:13 d:14 upsert_b:20 upsert_d:22!null
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(7-14,20,22-24)
      ├── project
      │    ├── columns: upsert_b:20 upsert_d:22!null column1:7!null column2:8!null b_default:9 c_default:10 a:11 b:12 c:13 d:14
      │    ├── cardinality: [1 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(7-14,20,22)
      │    ├── left-join (cross)
      │    │    ├── columns: column1:7!null column2:8!null b_default:9 c_default:10 a:11 b:12 c:13 d:14
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(7-14)
      │    │    ├── values
      │    │    │    ├── columns: column1:7!null column2:8!null b_default:9 c_default:10
      │    │    │    ├── cardinality: [1 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(7-10)
      │    │    │    └── (1, 2, NULL, NULL)
      │    │    ├── select
      │    │    │    ├── columns: a:11!null b:12 c:13 d:14
      │    │    │    ├── cardinality: [0 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(11-14)
      │    │    │    ├── scan partial_indexes
      │    │    │    │    ├── columns: a:11!null b:12 c:13 d:14
      │    │    │    │    ├── partial index predicates
      │    │    │    │    │    └── partial_indexes_c_idx: filters
      │    │    │    │    │         └── b:12 > 1 [outer=(12), constraints=(/12: [/2 - ]; tight)]
      │    │    │    │    ├── flags: avoid-full-scan
      │    │    │    │    ├── key: (11)
      │    │    │    │    └── fd: (11)-->(12-14)
      │    │    │    └── filters
      │    │    │         └── a:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]
      │    │    └── filters (true)
      │    └── projections
      │         ├── CASE WHEN a:11 IS NULL THEN b_default:9 ELSE 3 END [as=upsert_b:20, outer=(9,11)]
      │         └── CASE WHEN a:11 IS NULL THEN column2:8 ELSE 4 END [as=upsert_d:22, outer=(8,11)]
      └── projections
           ├── upsert_b:20 > 1 [as=partial_index_put1:23, outer=(20)]
           └── b:12 > 1 [as=partial_index_del1:24, outer=(12)]

# Do not prune DELETE fetch columns.
norm
DELETE FROM partial_indexes WHERE a = 1
----
delete partial_indexes
 ├── columns: <none>
 ├── fetch columns: a:7 c:9
 ├── partial index del columns: partial_index_del1:13
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_del1:13 a:7!null c:9
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(7,9,13)
      ├── select
      │    ├── columns: a:7!null b:8 c:9
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(7-9)
      │    ├── scan partial_indexes
      │    │    ├── columns: a:7!null b:8 c:9
      │    │    ├── partial index predicates
      │    │    │    └── partial_indexes_c_idx: filters
      │    │    │         └── b:8 > 1 [outer=(8), constraints=(/8: [/2 - ]; tight)]
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (7)
      │    │    └── fd: (7)-->(8,9)
      │    └── filters
      │         └── a:7 = 1 [outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]
      └── projections
           └── b:8 > 1 [as=partial_index_del1:13, outer=(8)]

# Prune UPDATE fetch columns when neither the column indexed by the partial
# index nor the virtual columns referenced in the partial index predicate are
# mutating.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
UPDATE virt_partial_idx SET d = d + 1 WHERE a = 1
----
update virt_partial_idx
 ├── columns: <none>
 ├── fetch columns: a:8 d:12
 ├── update-mapping:
 │    └── d_new:15 => d:5
 ├── partial index put columns: partial_index_put1:17
 ├── partial index del columns: partial_index_put1:17
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:17!null d_new:15 a:8!null d:12
      ├── cardinality: [0 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(8,12,15,17)
      ├── select
      │    ├── columns: a:8!null d:12
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(8,12)
      │    ├── scan virt_partial_idx
      │    │    ├── columns: a:8!null d:12
      │    │    ├── computed column expressions
      │    │    │    └── v:10
      │    │    │         └── b:9 + 1
      │    │    ├── partial index predicates
      │    │    │    └── virt_partial_idx_c_idx: filters
      │    │    │         └── b:9 > 0 [outer=(9), constraints=(/9: [/1 - ]; tight)]
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (8)
      │    │    └── fd: (8)-->(12)
      │    └── filters
      │         └── a:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)]
      └── projections
           ├── false [as=partial_index_put1:17]
           └── d:12 + 1 [as=d_new:15, outer=(12), immutable]

# Do not prune the indexed column c because a column referenced in the partial
# index predicate, v, is mutating because it is dependent on the value of b.
norm expect-not=PruneMutationFetchCols
UPDATE virt_partial_idx SET d = d + 1, b = 2 WHERE a = 1
----
update virt_partial_idx
 ├── columns: <none>
 ├── fetch columns: a:8 b:9 v:10 c:11 d:12
 ├── update-mapping:
 │    ├── b_new:16 => b:2
 │    ├── v_comp:17 => v:3
 │    └── d_new:15 => d:5
 ├── partial index put columns: partial_index_put1:18
 ├── partial index del columns: partial_index_del1:19
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:18!null partial_index_del1:19 a:8!null b:9 v:10 c:11 d:12 d_new:15 b_new:16!null v_comp:17!null
      ├── cardinality: [0 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(8-12,15-19)
      ├── project
      │    ├── columns: v_comp:17!null d_new:15 b_new:16!null v:10 a:8!null b:9 c:11 d:12
      │    ├── cardinality: [0 - 1]
      │    ├── immutable
      │    ├── key: ()
      │    ├── fd: ()-->(8-12,15-17)
      │    ├── select
      │    │    ├── columns: a:8!null b:9 c:11 d:12
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8,9,11,12)
      │    │    ├── scan virt_partial_idx
      │    │    │    ├── columns: a:8!null b:9 c:11 d:12
      │    │    │    ├── computed column expressions
      │    │    │    │    └── v:10
      │    │    │    │         └── b:9 + 1
      │    │    │    ├── partial index predicates
      │    │    │    │    └── virt_partial_idx_c_idx: filters
      │    │    │    │         └── b:9 > 0 [outer=(9), constraints=(/9: [/1 - ]; tight)]
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (8)
      │    │    │    └── fd: (8)-->(9,11,12)
      │    │    └── filters
      │    │         └── a:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)]
      │    └── projections
      │         ├── 3 [as=v_comp:17]
      │         ├── d:12 + 1 [as=d_new:15, outer=(12), immutable]
      │         ├── 2 [as=b_new:16]
      │         └── b:9 + 1 [as=v:10, outer=(9), immutable]
      └── projections
           ├── true [as=partial_index_put1:18]
           └── v:10 > 1 [as=partial_index_del1:19, outer=(10)]

# Prune UPSERT fetch columns when neither the column indexed by the partial
# index nor the virtual columns referenced in the partial index predicate are
# mutating.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
UPSERT INTO virt_partial_idx (a, d) VALUES (1, 2)
----
upsert virt_partial_idx
 ├── arbiter indexes: virt_partial_idx_pkey
 ├── columns: <none>
 ├── canary column: a:12
 ├── fetch columns: a:12 d:16
 ├── insert-mapping:
 │    ├── column1:8 => a:1
 │    ├── b_default:10 => b:2
 │    ├── v_comp:11 => v:3
 │    ├── b_default:10 => c:4
 │    └── column2:9 => d:5
 ├── update-mapping:
 │    └── column2:9 => d:5
 ├── partial index put columns: partial_index_put1:24
 ├── partial index del columns: partial_index_del1:25
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:24 partial_index_del1:25 column1:8!null column2:9!null b_default:10 v_comp:11 a:12 d:16
      ├── cardinality: [1 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(8-12,16,24,25)
      ├── left-join (cross)
      │    ├── columns: column1:8!null column2:9!null b_default:10 v_comp:11 a:12 v:14 d:16
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── immutable
      │    ├── key: ()
      │    ├── fd: ()-->(8-12,14,16)
      │    ├── values
      │    │    ├── columns: column1:8!null column2:9!null b_default:10 v_comp:11
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8-11)
      │    │    └── (1, 2, NULL, NULL)
      │    ├── project
      │    │    ├── columns: v:14 a:12!null d:16
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── immutable
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(12,14,16)
      │    │    ├── select
      │    │    │    ├── columns: a:12!null b:13 d:16
      │    │    │    ├── cardinality: [0 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(12,13,16)
      │    │    │    ├── scan virt_partial_idx
      │    │    │    │    ├── columns: a:12!null b:13 d:16
      │    │    │    │    ├── computed column expressions
      │    │    │    │    │    └── v:14
      │    │    │    │    │         └── b:13 + 1
      │    │    │    │    ├── partial index predicates
      │    │    │    │    │    └── virt_partial_idx_c_idx: filters
      │    │    │    │    │         └── b:13 > 0 [outer=(13), constraints=(/13: [/1 - ]; tight)]
      │    │    │    │    ├── flags: avoid-full-scan
      │    │    │    │    ├── key: (12)
      │    │    │    │    └── fd: (12)-->(13,16)
      │    │    │    └── filters
      │    │    │         └── a:12 = 1 [outer=(12), constraints=(/12: [/1 - /1]; tight), fd=()-->(12)]
      │    │    └── projections
      │    │         └── b:13 + 1 [as=v:14, outer=(13), immutable]
      │    └── filters (true)
      └── projections
           ├── CASE WHEN a:12 IS NULL THEN v_comp:11 ELSE v:14 END > 1 [as=partial_index_put1:24, outer=(11,12,14)]
           └── v:14 > 1 [as=partial_index_del1:25, outer=(14)]

# Do not prune the indexed column c because a column referenced in the partial
# index predicate, v, is mutating because it is dependent on the value of b.
norm expect-not=PruneMutationFetchCols
UPSERT INTO virt_partial_idx (a, b, d) VALUES (1, 2, 3)
----
upsert virt_partial_idx
 ├── arbiter indexes: virt_partial_idx_pkey
 ├── columns: <none>
 ├── canary column: a:13
 ├── fetch columns: a:13 b:14 v:15 c:16 d:17
 ├── insert-mapping:
 │    ├── column1:8 => a:1
 │    ├── column2:9 => b:2
 │    ├── v_comp:12 => v:3
 │    ├── c_default:11 => c:4
 │    └── column3:10 => d:5
 ├── update-mapping:
 │    ├── column2:9 => b:2
 │    ├── v_comp:12 => v:3
 │    └── column3:10 => d:5
 ├── partial index put columns: partial_index_put1:22
 ├── partial index del columns: partial_index_del1:23
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:22!null partial_index_del1:23 column1:8!null column2:9!null column3:10!null c_default:11 v_comp:12!null a:13 b:14 v:15 c:16 d:17
      ├── cardinality: [1 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(8-17,22,23)
      ├── left-join (cross)
      │    ├── columns: column1:8!null column2:9!null column3:10!null c_default:11 v_comp:12!null a:13 b:14 v:15 c:16 d:17
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── immutable
      │    ├── key: ()
      │    ├── fd: ()-->(8-17)
      │    ├── values
      │    │    ├── columns: column1:8!null column2:9!null column3:10!null c_default:11 v_comp:12!null
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8-12)
      │    │    └── (1, 2, 3, NULL, 3)
      │    ├── project
      │    │    ├── columns: v:15 a:13!null b:14 c:16 d:17
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── immutable
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(13-17)
      │    │    ├── select
      │    │    │    ├── columns: a:13!null b:14 c:16 d:17
      │    │    │    ├── cardinality: [0 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(13,14,16,17)
      │    │    │    ├── scan virt_partial_idx
      │    │    │    │    ├── columns: a:13!null b:14 c:16 d:17
      │    │    │    │    ├── computed column expressions
      │    │    │    │    │    └── v:15
      │    │    │    │    │         └── b:14 + 1
      │    │    │    │    ├── partial index predicates
      │    │    │    │    │    └── virt_partial_idx_c_idx: filters
      │    │    │    │    │         └── b:14 > 0 [outer=(14), constraints=(/14: [/1 - ]; tight)]
      │    │    │    │    ├── flags: avoid-full-scan
      │    │    │    │    ├── key: (13)
      │    │    │    │    └── fd: (13)-->(14,16,17)
      │    │    │    └── filters
      │    │    │         └── a:13 = 1 [outer=(13), constraints=(/13: [/1 - /1]; tight), fd=()-->(13)]
      │    │    └── projections
      │    │         └── b:14 + 1 [as=v:15, outer=(14), immutable]
      │    └── filters (true)
      └── projections
           ├── v_comp:12 > 1 [as=partial_index_put1:22, outer=(12)]
           └── v:15 > 1 [as=partial_index_del1:23, outer=(15)]

# Prune INSERT ON CONFLICT DO UPDATE fetch columns when neither the column
# indexed by the partial index nor the virtual columns referenced in the partial
# index predicate are mutating.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
INSERT INTO virt_partial_idx (a, d) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET d = 3
----
upsert virt_partial_idx
 ├── arbiter indexes: virt_partial_idx_pkey
 ├── columns: <none>
 ├── canary column: a:12
 ├── fetch columns: a:12 d:16
 ├── insert-mapping:
 │    ├── column1:8 => a:1
 │    ├── b_default:10 => b:2
 │    ├── v_comp:11 => v:3
 │    ├── b_default:10 => c:4
 │    └── column2:9 => d:5
 ├── update-mapping:
 │    └── upsert_d:25 => d:5
 ├── partial index put columns: partial_index_put1:26
 ├── partial index del columns: partial_index_del1:27
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:26 partial_index_del1:27 upsert_d:25!null column1:8!null column2:9!null b_default:10 v_comp:11 a:12 d:16
      ├── cardinality: [1 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(8-12,16,25-27)
      ├── left-join (cross)
      │    ├── columns: column1:8!null column2:9!null b_default:10 v_comp:11 a:12 v:14 d:16
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── immutable
      │    ├── key: ()
      │    ├── fd: ()-->(8-12,14,16)
      │    ├── values
      │    │    ├── columns: column1:8!null column2:9!null b_default:10 v_comp:11
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8-11)
      │    │    └── (1, 2, NULL, NULL)
      │    ├── project
      │    │    ├── columns: v:14 a:12!null d:16
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── immutable
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(12,14,16)
      │    │    ├── select
      │    │    │    ├── columns: a:12!null b:13 d:16
      │    │    │    ├── cardinality: [0 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(12,13,16)
      │    │    │    ├── scan virt_partial_idx
      │    │    │    │    ├── columns: a:12!null b:13 d:16
      │    │    │    │    ├── computed column expressions
      │    │    │    │    │    └── v:14
      │    │    │    │    │         └── b:13 + 1
      │    │    │    │    ├── partial index predicates
      │    │    │    │    │    └── virt_partial_idx_c_idx: filters
      │    │    │    │    │         └── b:13 > 0 [outer=(13), constraints=(/13: [/1 - ]; tight)]
      │    │    │    │    ├── flags: avoid-full-scan
      │    │    │    │    ├── key: (12)
      │    │    │    │    └── fd: (12)-->(13,16)
      │    │    │    └── filters
      │    │    │         └── a:12 = 1 [outer=(12), constraints=(/12: [/1 - /1]; tight), fd=()-->(12)]
      │    │    └── projections
      │    │         └── b:13 + 1 [as=v:14, outer=(13), immutable]
      │    └── filters (true)
      └── projections
           ├── CASE WHEN a:12 IS NULL THEN v_comp:11 ELSE v:14 END > 1 [as=partial_index_put1:26, outer=(11,12,14)]
           ├── v:14 > 1 [as=partial_index_del1:27, outer=(14)]
           └── CASE WHEN a:12 IS NULL THEN column2:9 ELSE 3 END [as=upsert_d:25, outer=(9,12)]

# Do not prune the indexed column c because a column referenced in the partial
# index predicate, v, is mutating because it is dependent on the value of b.
norm expect-not=PruneMutationFetchCols
INSERT INTO virt_partial_idx (a, d) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET b = 3, d = 4
----
upsert virt_partial_idx
 ├── arbiter indexes: virt_partial_idx_pkey
 ├── columns: <none>
 ├── canary column: a:12
 ├── fetch columns: a:12 b:13 v:14 c:15 d:16
 ├── insert-mapping:
 │    ├── column1:8 => a:1
 │    ├── b_default:10 => b:2
 │    ├── v_comp:11 => v:3
 │    ├── b_default:10 => c:4
 │    └── column2:9 => d:5
 ├── update-mapping:
 │    ├── upsert_b:23 => b:2
 │    ├── upsert_v:24 => v:3
 │    └── upsert_d:26 => d:5
 ├── partial index put columns: partial_index_put1:27
 ├── partial index del columns: partial_index_del1:28
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:27 partial_index_del1:28 column1:8!null column2:9!null b_default:10 v_comp:11 a:12 b:13 v:14 c:15 d:16 upsert_b:23 upsert_v:24 upsert_d:26!null
      ├── cardinality: [1 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(8-16,23,24,26-28)
      ├── project
      │    ├── columns: upsert_b:23 upsert_v:24 upsert_d:26!null column1:8!null column2:9!null b_default:10 v_comp:11 a:12 b:13 v:14 c:15 d:16
      │    ├── cardinality: [1 - 1]
      │    ├── immutable
      │    ├── key: ()
      │    ├── fd: ()-->(8-16,23,24,26)
      │    ├── left-join (cross)
      │    │    ├── columns: column1:8!null column2:9!null b_default:10 v_comp:11 a:12 b:13 v:14 c:15 d:16
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    │    ├── immutable
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8-16)
      │    │    ├── values
      │    │    │    ├── columns: column1:8!null column2:9!null b_default:10 v_comp:11
      │    │    │    ├── cardinality: [1 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(8-11)
      │    │    │    └── (1, 2, NULL, NULL)
      │    │    ├── project
      │    │    │    ├── columns: v:14 a:12!null b:13 c:15 d:16
      │    │    │    ├── cardinality: [0 - 1]
      │    │    │    ├── immutable
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(12-16)
      │    │    │    ├── select
      │    │    │    │    ├── columns: a:12!null b:13 c:15 d:16
      │    │    │    │    ├── cardinality: [0 - 1]
      │    │    │    │    ├── key: ()
      │    │    │    │    ├── fd: ()-->(12,13,15,16)
      │    │    │    │    ├── scan virt_partial_idx
      │    │    │    │    │    ├── columns: a:12!null b:13 c:15 d:16
      │    │    │    │    │    ├── computed column expressions
      │    │    │    │    │    │    └── v:14
      │    │    │    │    │    │         └── b:13 + 1
      │    │    │    │    │    ├── partial index predicates
      │    │    │    │    │    │    └── virt_partial_idx_c_idx: filters
      │    │    │    │    │    │         └── b:13 > 0 [outer=(13), constraints=(/13: [/1 - ]; tight)]
      │    │    │    │    │    ├── flags: avoid-full-scan
      │    │    │    │    │    ├── key: (12)
      │    │    │    │    │    └── fd: (12)-->(13,15,16)
      │    │    │    │    └── filters
      │    │    │    │         └── a:12 = 1 [outer=(12), constraints=(/12: [/1 - /1]; tight), fd=()-->(12)]
      │    │    │    └── projections
      │    │    │         └── b:13 + 1 [as=v:14, outer=(13), immutable]
      │    │    └── filters (true)
      │    └── projections
      │         ├── CASE WHEN a:12 IS NULL THEN b_default:10 ELSE 3 END [as=upsert_b:23, outer=(10,12)]
      │         ├── CASE WHEN a:12 IS NULL THEN v_comp:11 ELSE 4 END [as=upsert_v:24, outer=(11,12)]
      │         └── CASE WHEN a:12 IS NULL THEN column2:9 ELSE 4 END [as=upsert_d:26, outer=(9,12)]
      └── projections
           ├── upsert_v:24 > 1 [as=partial_index_put1:27, outer=(24)]
           └── v:14 > 1 [as=partial_index_del1:28, outer=(14)]

# Do not prune DELETE fetch columns when the partial index predicate references
# virtual columns.
norm
DELETE FROM virt_partial_idx WHERE a = 1
----
delete virt_partial_idx
 ├── columns: <none>
 ├── fetch columns: a:8 c:11
 ├── partial index del columns: partial_index_del1:15
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_del1:15 a:8!null c:11
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(8,11,15)
      ├── select
      │    ├── columns: a:8!null b:9 c:11
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(8,9,11)
      │    ├── scan virt_partial_idx
      │    │    ├── columns: a:8!null b:9 c:11
      │    │    ├── computed column expressions
      │    │    │    └── v:10
      │    │    │         └── b:9 + 1
      │    │    ├── partial index predicates
      │    │    │    └── virt_partial_idx_c_idx: filters
      │    │    │         └── b:9 > 0 [outer=(9), constraints=(/9: [/1 - ]; tight)]
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (8)
      │    │    └── fd: (8)-->(9,11)
      │    └── filters
      │         └── a:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)]
      └── projections
           └── b:9 > 0 [as=partial_index_del1:15, outer=(9)]

# Do not prune columns that are required for evaluating partial index
# predicates.
norm
UPDATE partial_indexes SET b = b + 1 WHERE a = 1
----
update partial_indexes
 ├── columns: <none>
 ├── fetch columns: a:7 b:8 c:9
 ├── update-mapping:
 │    └── b_new:13 => b:2
 ├── partial index put columns: partial_index_put1:14
 ├── partial index del columns: partial_index_del1:15
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: partial_index_put1:14 partial_index_del1:15 a:7!null b:8 c:9 b_new:13
      ├── cardinality: [0 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(7-9,13-15)
      ├── project
      │    ├── columns: b_new:13 a:7!null b:8 c:9
      │    ├── cardinality: [0 - 1]
      │    ├── immutable
      │    ├── key: ()
      │    ├── fd: ()-->(7-9,13)
      │    ├── select
      │    │    ├── columns: a:7!null b:8 c:9
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(7-9)
      │    │    ├── scan partial_indexes
      │    │    │    ├── columns: a:7!null b:8 c:9
      │    │    │    ├── partial index predicates
      │    │    │    │    └── partial_indexes_c_idx: filters
      │    │    │    │         └── b:8 > 1 [outer=(8), constraints=(/8: [/2 - ]; tight)]
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (7)
      │    │    │    └── fd: (7)-->(8,9)
      │    │    └── filters
      │    │         └── a:7 = 1 [outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)]
      │    └── projections
      │         └── b:8 + 1 [as=b_new:13, outer=(8), immutable]
      └── projections
           ├── b_new:13 > 1 [as=partial_index_put1:14, outer=(13)]
           └── b:8 > 1 [as=partial_index_del1:15, outer=(8)]

# Prune secondary family column not needed for the update.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
UPDATE family SET b=c WHERE a > 100
----
update family
 ├── columns: <none>
 ├── fetch columns: a:8 b:9
 ├── update-mapping:
 │    └── c:10 => b:2
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── select
      ├── columns: a:8!null b:9 c:10
      ├── key: (8)
      ├── fd: (8)-->(9,10)
      ├── scan family
      │    ├── columns: a:8!null b:9 c:10
      │    ├── flags: avoid-full-scan
      │    ├── key: (8)
      │    └── fd: (8)-->(9,10)
      └── filters
           └── a:8 > 100 [outer=(8), constraints=(/8: [/101 - ]; tight)]

# Do not prune when key column is updated.
norm expect-not=(PruneMutationFetchCols)
UPDATE family SET a=a+1 WHERE a > 100
----
update family
 ├── columns: <none>
 ├── fetch columns: a:8 b:9 c:10 d:11 e:12
 ├── update-mapping:
 │    └── a_new:15 => a:1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: a_new:15!null a:8!null b:9 c:10 d:11 e:12
      ├── immutable
      ├── key: (8)
      ├── fd: (8)-->(9-12,15)
      ├── select
      │    ├── columns: a:8!null b:9 c:10 d:11 e:12
      │    ├── key: (8)
      │    ├── fd: (8)-->(9-12)
      │    ├── scan family
      │    │    ├── columns: a:8!null b:9 c:10 d:11 e:12
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (8)
      │    │    └── fd: (8)-->(9-12)
      │    └── filters
      │         └── a:8 > 100 [outer=(8), constraints=(/8: [/101 - ]; tight)]
      └── projections
           └── a:8 + 1 [as=a_new:15, outer=(8), immutable]

# Do not prune columns that must be returned.
norm expect=(PruneMutationFetchCols, PruneMutationReturnCols)
UPDATE family SET c=c+1 RETURNING b
----
project
 ├── columns: b:2
 ├── volatile, mutations
 └── update family
      ├── columns: a:1!null b:2
      ├── fetch columns: a:8 b:9 c:10 d:11
      ├── update-mapping:
      │    └── c_new:15 => c:3
      ├── return-mapping:
      │    ├── a:8 => a:1
      │    └── b:9 => b:2
      ├── volatile, mutations
      ├── key: (1)
      ├── fd: (1)-->(2)
      └── project
           ├── columns: c_new:15 a:8!null b:9 c:10 d:11
           ├── immutable
           ├── key: (8)
           ├── fd: (8)-->(9-11), (10)-->(15)
           ├── scan family
           │    ├── columns: a:8!null b:9 c:10 d:11
           │    ├── flags: avoid-full-scan
           │    ├── key: (8)
           │    └── fd: (8)-->(9-11)
           └── projections
                └── c:10 + 1 [as=c_new:15, outer=(10), immutable]

# Do not prune columns that are required for encoding a multi-column inverted
# index.
norm expect-not=PruneMutationFetchCols
UPDATE multi_col_inv_idx SET j = '[1]' WHERE a = 1
----
update multi_col_inv_idx
 ├── columns: <none>
 ├── fetch columns: a:8 b:9 c:10 j:11
 ├── update-mapping:
 │    └── j_new:15 => j:4
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: j_new:15!null a:8!null b:9 c:10 j:11
      ├── cardinality: [0 - 1]
      ├── key: ()
      ├── fd: ()-->(8-11,15)
      ├── select
      │    ├── columns: a:8!null b:9 c:10 j:11
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(8-11)
      │    ├── scan multi_col_inv_idx
      │    │    ├── columns: a:8!null b:9 c:10 j:11
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (8)
      │    │    └── fd: (8)-->(9-11)
      │    └── filters
      │         └── a:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)]
      └── projections
           └── '[1]' [as=j_new:15]

# Prune unused upsert columns.
norm expect=PruneMutationInputCols
INSERT INTO a (k, s) VALUES (1, 'foo') ON CONFLICT (k) DO UPDATE SET i=a.i+1
----
upsert a
 ├── arbiter indexes: a_pkey
 ├── columns: <none>
 ├── canary column: k:11
 ├── fetch columns: k:11 i:12 f:13 s:14
 ├── insert-mapping:
 │    ├── column1:7 => k:1
 │    ├── i_default:9 => i:2
 │    ├── f_default:10 => f:3
 │    └── column2:8 => s:4
 ├── update-mapping:
 │    └── upsert_i:19 => i:2
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: upsert_i:19 column1:7!null column2:8!null i_default:9 f_default:10 k:11 i:12 f:13 s:14
      ├── cardinality: [1 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(7-14,19)
      ├── left-join (cross)
      │    ├── columns: column1:7!null column2:8!null i_default:9 f_default:10 k:11 i:12 f:13 s:14
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── key: ()
      │    ├── fd: ()-->(7-14)
      │    ├── values
      │    │    ├── columns: column1:7!null column2:8!null i_default:9 f_default:10
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(7-10)
      │    │    └── (1, 'foo', NULL, NULL)
      │    ├── select
      │    │    ├── columns: k:11!null i:12 f:13 s:14
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(11-14)
      │    │    ├── scan a
      │    │    │    ├── columns: k:11!null i:12 f:13 s:14
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (11)
      │    │    │    └── fd: (11)-->(12-14)
      │    │    └── filters
      │    │         └── k:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]
      │    └── filters (true)
      └── projections
           └── CASE WHEN k:11 IS NULL THEN i_default:9 ELSE i:12 + 1 END [as=upsert_i:19, outer=(9,11,12), immutable]

# Prune update columns replaced by upsert columns.
# TODO(andyk): Need to also prune output columns.
norm expect=PruneMutationInputCols expect-not=PruneMutationFetchCols
INSERT INTO a (k, s) VALUES (1, 'foo') ON CONFLICT (k) DO UPDATE SET i=a.i+1 RETURNING *
----
upsert a
 ├── columns: k:1!null i:2 f:3 s:4
 ├── arbiter indexes: a_pkey
 ├── canary column: k:11
 ├── fetch columns: k:11 i:12 f:13 s:14
 ├── insert-mapping:
 │    ├── column1:7 => k:1
 │    ├── i_default:9 => i:2
 │    ├── f_default:10 => f:3
 │    └── column2:8 => s:4
 ├── update-mapping:
 │    └── upsert_i:19 => i:2
 ├── return-mapping:
 │    ├── upsert_k:18 => k:1
 │    ├── upsert_i:19 => i:2
 │    ├── upsert_f:20 => f:3
 │    └── upsert_s:21 => s:4
 ├── cardinality: [1 - 1]
 ├── volatile, mutations
 ├── key: ()
 ├── fd: ()-->(1-4)
 └── project
      ├── columns: upsert_k:18 upsert_i:19 upsert_f:20 upsert_s:21 column1:7!null column2:8!null i_default:9 f_default:10 k:11 i:12 f:13 s:14
      ├── cardinality: [1 - 1]
      ├── immutable
      ├── key: ()
      ├── fd: ()-->(7-14,18-21)
      ├── left-join (cross)
      │    ├── columns: column1:7!null column2:8!null i_default:9 f_default:10 k:11 i:12 f:13 s:14
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── key: ()
      │    ├── fd: ()-->(7-14)
      │    ├── values
      │    │    ├── columns: column1:7!null column2:8!null i_default:9 f_default:10
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(7-10)
      │    │    └── (1, 'foo', NULL, NULL)
      │    ├── select
      │    │    ├── columns: k:11!null i:12 f:13 s:14
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(11-14)
      │    │    ├── scan a
      │    │    │    ├── columns: k:11!null i:12 f:13 s:14
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (11)
      │    │    │    └── fd: (11)-->(12-14)
      │    │    └── filters
      │    │         └── k:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]
      │    └── filters (true)
      └── projections
           ├── CASE WHEN k:11 IS NULL THEN column1:7 ELSE k:11 END [as=upsert_k:18, outer=(7,11)]
           ├── CASE WHEN k:11 IS NULL THEN i_default:9 ELSE i:12 + 1 END [as=upsert_i:19, outer=(9,11,12), immutable]
           ├── CASE WHEN k:11 IS NULL THEN f_default:10 ELSE f:13 END [as=upsert_f:20, outer=(10,11,13)]
           └── CASE WHEN k:11 IS NULL THEN column2:8 ELSE s:14 END [as=upsert_s:21, outer=(8,11,14)]

# Prune column in column family that is not updated.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
UPSERT INTO family (a, b) VALUES (1, 2)
----
upsert family
 ├── arbiter indexes: family_pkey
 ├── columns: <none>
 ├── canary column: a:11
 ├── fetch columns: a:11 b:12
 ├── insert-mapping:
 │    ├── column1:8 => a:1
 │    ├── column2:9 => b:2
 │    ├── c_default:10 => c:3
 │    ├── c_default:10 => d:4
 │    └── c_default:10 => e:5
 ├── update-mapping:
 │    └── column2:9 => b:2
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── left-join (cross)
      ├── columns: column1:8!null column2:9!null c_default:10 a:11 b:12
      ├── cardinality: [1 - 1]
      ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      ├── key: ()
      ├── fd: ()-->(8-12)
      ├── values
      │    ├── columns: column1:8!null column2:9!null c_default:10
      │    ├── cardinality: [1 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(8-10)
      │    └── (1, 2, NULL)
      ├── select
      │    ├── columns: a:11!null b:12
      │    ├── cardinality: [0 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(11,12)
      │    ├── scan family
      │    │    ├── columns: a:11!null b:12
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (11)
      │    │    └── fd: (11)-->(12)
      │    └── filters
      │         └── a:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]
      └── filters (true)

norm
INSERT INTO family VALUES (1, 2, 3, 4, 5) ON CONFLICT (a) DO UPDATE SET c = 10 RETURNING e
----
project
 ├── columns: e:5
 ├── cardinality: [1 - 1]
 ├── volatile, mutations
 ├── key: ()
 ├── fd: ()-->(5)
 └── upsert family
      ├── columns: a:1!null e:5
      ├── arbiter indexes: family_pkey
      ├── canary column: a:13
      ├── fetch columns: a:13 c:15 d:16 e:17
      ├── insert-mapping:
      │    ├── column1:8 => a:1
      │    ├── column2:9 => b:2
      │    ├── column3:10 => c:3
      │    ├── column4:11 => d:4
      │    └── column5:12 => e:5
      ├── update-mapping:
      │    └── upsert_c:23 => c:3
      ├── return-mapping:
      │    ├── upsert_a:21 => a:1
      │    └── upsert_e:25 => e:5
      ├── cardinality: [1 - 1]
      ├── volatile, mutations
      ├── key: ()
      ├── fd: ()-->(1,5)
      └── project
           ├── columns: upsert_a:21 upsert_c:23!null upsert_e:25 column1:8!null column2:9!null column3:10!null column4:11!null column5:12!null a:13 c:15 d:16 e:17
           ├── cardinality: [1 - 1]
           ├── key: ()
           ├── fd: ()-->(8-13,15-17,21,23,25)
           ├── left-join (cross)
           │    ├── columns: column1:8!null column2:9!null column3:10!null column4:11!null column5:12!null a:13 c:15 d:16 e:17
           │    ├── cardinality: [1 - 1]
           │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
           │    ├── key: ()
           │    ├── fd: ()-->(8-13,15-17)
           │    ├── values
           │    │    ├── columns: column1:8!null column2:9!null column3:10!null column4:11!null column5:12!null
           │    │    ├── cardinality: [1 - 1]
           │    │    ├── key: ()
           │    │    ├── fd: ()-->(8-12)
           │    │    └── (1, 2, 3, 4, 5)
           │    ├── select
           │    │    ├── columns: a:13!null c:15 d:16 e:17
           │    │    ├── cardinality: [0 - 1]
           │    │    ├── key: ()
           │    │    ├── fd: ()-->(13,15-17)
           │    │    ├── scan family
           │    │    │    ├── columns: a:13!null c:15 d:16 e:17
           │    │    │    ├── flags: avoid-full-scan
           │    │    │    ├── key: (13)
           │    │    │    └── fd: (13)-->(15-17)
           │    │    └── filters
           │    │         └── a:13 = 1 [outer=(13), constraints=(/13: [/1 - /1]; tight), fd=()-->(13)]
           │    └── filters (true)
           └── projections
                ├── CASE WHEN a:13 IS NULL THEN column1:8 ELSE a:13 END [as=upsert_a:21, outer=(8,13)]
                ├── CASE WHEN a:13 IS NULL THEN column3:10 ELSE 10 END [as=upsert_c:23, outer=(10,13)]
                └── CASE WHEN a:13 IS NULL THEN column5:12 ELSE e:17 END [as=upsert_e:25, outer=(12,13,17)]

# Do not prune column in same secondary family as updated column. But prune
# non-key column in primary family.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
INSERT INTO family VALUES (1, 2, 3, 4) ON CONFLICT (a) DO UPDATE SET d=10
----
upsert family
 ├── arbiter indexes: family_pkey
 ├── columns: <none>
 ├── canary column: a:13
 ├── fetch columns: a:13 c:15 d:16
 ├── insert-mapping:
 │    ├── column1:8 => a:1
 │    ├── column2:9 => b:2
 │    ├── column3:10 => c:3
 │    ├── column4:11 => d:4
 │    └── e_default:12 => e:5
 ├── update-mapping:
 │    └── upsert_d:24 => d:4
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: upsert_d:24!null column1:8!null column2:9!null column3:10!null column4:11!null e_default:12 a:13 c:15 d:16
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(8-13,15,16,24)
      ├── left-join (cross)
      │    ├── columns: column1:8!null column2:9!null column3:10!null column4:11!null e_default:12 a:13 c:15 d:16
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── key: ()
      │    ├── fd: ()-->(8-13,15,16)
      │    ├── values
      │    │    ├── columns: column1:8!null column2:9!null column3:10!null column4:11!null e_default:12
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8-12)
      │    │    └── (1, 2, 3, 4, NULL)
      │    ├── select
      │    │    ├── columns: a:13!null c:15 d:16
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(13,15,16)
      │    │    ├── scan family
      │    │    │    ├── columns: a:13!null c:15 d:16
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (13)
      │    │    │    └── fd: (13)-->(15,16)
      │    │    └── filters
      │    │         └── a:13 = 1 [outer=(13), constraints=(/13: [/1 - /1]; tight), fd=()-->(13)]
      │    └── filters (true)
      └── projections
           └── CASE WHEN a:13 IS NULL THEN column4:11 ELSE 10 END [as=upsert_d:24, outer=(11,13)]

# Prune upsert columns when mutation columns/indexes exist.
norm expect=(PruneMutationInputCols)
INSERT INTO mutation VALUES (1, 2, 3) ON CONFLICT (a) DO UPDATE SET b=10
----
upsert mutation
 ├── arbiter indexes: mutation_pkey
 ├── columns: <none>
 ├── canary column: a:12
 ├── fetch columns: a:12 b:13 c:14 d:15 e:16
 ├── insert-mapping:
 │    ├── column1:8 => a:1
 │    ├── column2:9 => b:2
 │    ├── column3:10 => c:3
 │    └── d_default:11 => d:4
 ├── update-mapping:
 │    ├── upsert_b:21 => b:2
 │    └── d_default:11 => d:4
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: upsert_b:21!null column1:8!null column2:9!null column3:10!null d_default:11 a:12 b:13 c:14 d:15 e:16
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(8-16,21)
      ├── left-join (cross)
      │    ├── columns: column1:8!null column2:9!null column3:10!null d_default:11 a:12 b:13 c:14 d:15 e:16
      │    ├── cardinality: [1 - 1]
      │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    ├── key: ()
      │    ├── fd: ()-->(8-16)
      │    ├── values
      │    │    ├── columns: column1:8!null column2:9!null column3:10!null d_default:11
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(8-11)
      │    │    └── (1, 2, 3, NULL)
      │    ├── select
      │    │    ├── columns: a:12!null b:13 c:14 d:15 e:16
      │    │    ├── cardinality: [0 - 1]
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(12-16)
      │    │    ├── scan mutation
      │    │    │    ├── columns: a:12!null b:13 c:14 d:15 e:16
      │    │    │    ├── flags: avoid-full-scan
      │    │    │    ├── key: (12)
      │    │    │    └── fd: (12)-->(13-16)
      │    │    └── filters
      │    │         └── a:12 = 1 [outer=(12), constraints=(/12: [/1 - /1]; tight), fd=()-->(12)]
      │    └── filters (true)
      └── projections
           └── CASE WHEN a:12 IS NULL THEN column2:9 ELSE 10 END [as=upsert_b:21, outer=(9,12)]

exec-ddl
CREATE TABLE checks (
    a INT PRIMARY KEY,
    b INT DEFAULT 20,
    c INT,
    d INT,
    CHECK (a > 0),
    CHECK (b > 10),
    CHECK (c > b)
)
----

# Prune all check columns when none of the referenced columns are updated.
norm expect=PruneMutationInputCols
UPDATE checks SET d = 0
----
update checks
 ├── columns: <none>
 ├── fetch columns: a:7 b:8 c:9 d:10
 ├── update-mapping:
 │    └── d_new:13 => d:4
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: d_new:13!null a:7!null b:8 c:9 d:10
      ├── key: (7)
      ├── fd: ()-->(13), (7)-->(8-10)
      ├── scan checks
      │    ├── columns: a:7!null b:8 c:9 d:10
      │    ├── check constraint expressions
      │    │    └── a:7 > 0 [outer=(7), constraints=(/7: [/1 - ]; tight)]
      │    ├── flags: avoid-full-scan
      │    ├── key: (7)
      │    └── fd: (7)-->(8-10)
      └── projections
           └── 0 [as=d_new:13]

# Do not prune check columns when their referenced columns are updated.
norm expect=PruneMutationInputCols
UPDATE checks SET b = 5
----
update checks
 ├── columns: <none>
 ├── fetch columns: a:7 b:8 c:9 d:10
 ├── update-mapping:
 │    └── b_new:13 => b:2
 ├── check columns: check2:15 check3:16
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: check2:15!null check3:16 b_new:13!null a:7!null b:8 c:9 d:10
      ├── key: (7)
      ├── fd: ()-->(13,15), (7)-->(8-10), (9)-->(16)
      ├── scan checks
      │    ├── columns: a:7!null b:8 c:9 d:10
      │    ├── check constraint expressions
      │    │    └── a:7 > 0 [outer=(7), constraints=(/7: [/1 - ]; tight)]
      │    ├── flags: avoid-full-scan
      │    ├── key: (7)
      │    └── fd: (7)-->(8-10)
      └── projections
           ├── false [as=check2:15]
           ├── c:9 > 5 [as=check3:16, outer=(9)]
           └── 5 [as=b_new:13]


# Do not prune check columns for an insert.
norm expect-not=PruneMutationInputCols
INSERT INTO checks (a, c, d) VALUES (1, 3, 4)
----
insert checks
 ├── columns: <none>
 ├── insert-mapping:
 │    ├── column1:7 => a:1
 │    ├── b_default:10 => b:2
 │    ├── column2:8 => c:3
 │    └── column3:9 => d:4
 ├── check columns: check1:11 check2:12 check3:13
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── values
      ├── columns: column1:7!null column2:8!null column3:9!null b_default:10!null check1:11!null check2:12!null check3:13!null
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(7-13)
      └── (1, 3, 4, 20, true, true, false)

# Do not prune check columns for an upsert that does not require a scan.
norm expect-not=PruneMutationInputCols
UPSERT INTO checks (a, b, c, d) VALUES (1, 2, 3, 4)
----
upsert checks
 ├── columns: <none>
 ├── upsert-mapping:
 │    ├── column1:7 => a:1
 │    ├── column2:8 => b:2
 │    ├── column3:9 => c:3
 │    └── column4:10 => d:4
 ├── check columns: check1:11 check2:12 check3:13
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── values
      ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null check1:11!null check2:12!null check3:13!null
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(7-13)
      └── (1, 2, 3, 4, true, false, true)

# Do not prune check columns for an upsert that requires a scan.
norm
UPSERT INTO checks (a, c, d) VALUES (1, 3, 4)
----
upsert checks
 ├── arbiter indexes: checks_pkey
 ├── columns: <none>
 ├── canary column: a:11
 ├── fetch columns: a:11 b:12 c:13 d:14
 ├── insert-mapping:
 │    ├── column1:7 => a:1
 │    ├── b_default:10 => b:2
 │    ├── column2:8 => c:3
 │    └── column3:9 => d:4
 ├── update-mapping:
 │    ├── column2:8 => c:3
 │    └── column3:9 => d:4
 ├── check columns: check1:19 check2:20 check3:21
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: check1:19 check2:20 check3:21 column1:7!null column2:8!null column3:9!null b_default:10!null a:11 b:12 c:13 d:14
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(7-14,19-21)
      ├── project
      │    ├── columns: upsert_a:17 upsert_b:18 column1:7!null column2:8!null column3:9!null b_default:10!null a:11 b:12 c:13 d:14
      │    ├── cardinality: [1 - 1]
      │    ├── key: ()
      │    ├── fd: ()-->(7-14,17,18)
      │    ├── left-join (cross)
      │    │    ├── columns: column1:7!null column2:8!null column3:9!null b_default:10!null a:11 b:12 c:13 d:14
      │    │    ├── cardinality: [1 - 1]
      │    │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
      │    │    ├── key: ()
      │    │    ├── fd: ()-->(7-14)
      │    │    ├── values
      │    │    │    ├── columns: column1:7!null column2:8!null column3:9!null b_default:10!null
      │    │    │    ├── cardinality: [1 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(7-10)
      │    │    │    └── (1, 3, 4, 20)
      │    │    ├── select
      │    │    │    ├── columns: a:11!null b:12 c:13 d:14
      │    │    │    ├── cardinality: [0 - 1]
      │    │    │    ├── key: ()
      │    │    │    ├── fd: ()-->(11-14)
      │    │    │    ├── scan checks
      │    │    │    │    ├── columns: a:11!null b:12 c:13 d:14
      │    │    │    │    ├── check constraint expressions
      │    │    │    │    │    └── a:11 > 0 [outer=(11), constraints=(/11: [/1 - ]; tight)]
      │    │    │    │    ├── flags: avoid-full-scan
      │    │    │    │    ├── key: (11)
      │    │    │    │    └── fd: (11)-->(12-14)
      │    │    │    └── filters
      │    │    │         └── a:11 = 1 [outer=(11), constraints=(/11: [/1 - /1]; tight), fd=()-->(11)]
      │    │    └── filters (true)
      │    └── projections
      │         ├── CASE WHEN a:11 IS NULL THEN column1:7 ELSE a:11 END [as=upsert_a:17, outer=(7,11)]
      │         └── CASE WHEN a:11 IS NULL THEN b_default:10 ELSE b:12 END [as=upsert_b:18, outer=(10-12)]
      └── projections
           ├── upsert_a:17 > 0 [as=check1:19, outer=(17)]
           ├── upsert_b:18 > 10 [as=check2:20, outer=(18)]
           └── column2:8 > upsert_b:18 [as=check3:21, outer=(8,18)]

# Do not prune columns from updates that are needed for unique checks.
norm expect=PruneMutationInputCols
UPDATE uniq SET w = 1, x = 2 WHERE k = 3
----
update uniq
 ├── columns: <none>
 ├── fetch columns: uniq.k:9 uniq.w:11 uniq.x:12
 ├── update-mapping:
 │    ├── w_new:17 => uniq.w:3
 │    └── x_new:18 => uniq.x:4
 ├── input binding: &1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 ├── project
 │    ├── columns: w_new:17!null x_new:18!null uniq.k:9!null uniq.w:11 uniq.x:12 uniq.y:13
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(9,11-13,17,18)
 │    ├── select
 │    │    ├── columns: uniq.k:9!null uniq.w:11 uniq.x:12 uniq.y:13
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(9,11-13)
 │    │    ├── scan uniq
 │    │    │    ├── columns: uniq.k:9!null uniq.w:11 uniq.x:12 uniq.y:13
 │    │    │    ├── flags: avoid-full-scan
 │    │    │    ├── key: (9)
 │    │    │    └── fd: (9)-->(11-13), (11)~~>(9,12,13), (12,13)~~>(9,11)
 │    │    └── filters
 │    │         └── uniq.k:9 = 3 [outer=(9), constraints=(/9: [/3 - /3]; tight), fd=()-->(9)]
 │    └── projections
 │         ├── 1 [as=w_new:17]
 │         └── 2 [as=x_new:18]
 └── unique-checks
      ├── unique-checks-item: uniq(w)
      │    └── project
      │         ├── columns: w:29!null
      │         ├── cardinality: [0 - 1]
      │         ├── key: ()
      │         ├── fd: ()-->(29)
      │         └── semi-join (hash)
      │              ├── columns: k:27!null w:29!null
      │              ├── cardinality: [0 - 1]
      │              ├── key: ()
      │              ├── fd: ()-->(27,29)
      │              ├── with-scan &1
      │              │    ├── columns: k:27!null w:29!null
      │              │    ├── mapping:
      │              │    │    ├──  uniq.k:9 => k:27
      │              │    │    └──  w_new:17 => w:29
      │              │    ├── cardinality: [0 - 1]
      │              │    ├── key: ()
      │              │    └── fd: ()-->(27,29)
      │              ├── scan uniq
      │              │    ├── columns: uniq.k:19!null uniq.w:21
      │              │    ├── flags: avoid-full-scan
      │              │    ├── key: (19)
      │              │    └── fd: (19)-->(21)
      │              └── filters
      │                   ├── w:29 = uniq.w:21 [outer=(21,29), constraints=(/21: (/NULL - ]; /29: (/NULL - ]), fd=(21)==(29), (29)==(21)]
      │                   └── k:27 != uniq.k:19 [outer=(19,27), constraints=(/19: (/NULL - ]; /27: (/NULL - ])]
      └── unique-checks-item: uniq(x,y)
           └── project
                ├── columns: x:44!null y:45
                ├── cardinality: [0 - 1]
                ├── key: ()
                ├── fd: ()-->(44,45)
                └── semi-join (hash)
                     ├── columns: k:41!null x:44!null y:45
                     ├── cardinality: [0 - 1]
                     ├── key: ()
                     ├── fd: ()-->(41,44,45)
                     ├── with-scan &1
                     │    ├── columns: k:41!null x:44!null y:45
                     │    ├── mapping:
                     │    │    ├──  uniq.k:9 => k:41
                     │    │    ├──  x_new:18 => x:44
                     │    │    └──  uniq.y:13 => y:45
                     │    ├── cardinality: [0 - 1]
                     │    ├── key: ()
                     │    └── fd: ()-->(41,44,45)
                     ├── scan uniq
                     │    ├── columns: uniq.k:33!null uniq.x:36 uniq.y:37
                     │    ├── flags: avoid-full-scan
                     │    ├── key: (33)
                     │    └── fd: (33)-->(36,37)
                     └── filters
                          ├── x:44 = uniq.x:36 [outer=(36,44), constraints=(/36: (/NULL - ]; /44: (/NULL - ]), fd=(36)==(44), (44)==(36)]
                          ├── y:45 = uniq.y:37 [outer=(37,45), constraints=(/37: (/NULL - ]; /45: (/NULL - ]), fd=(37)==(45), (45)==(37)]
                          └── k:41 != uniq.k:33 [outer=(33,41), constraints=(/33: (/NULL - ]; /41: (/NULL - ])]

# Do not prune columns from updates that are needed for partial unique checks.
norm expect=PruneMutationInputCols
UPDATE uniq_partial SET v = 1 WHERE k = 3
----
update uniq_partial
 ├── columns: <none>
 ├── fetch columns: uniq_partial.k:7 uniq_partial.v:8
 ├── update-mapping:
 │    └── v_new:13 => uniq_partial.v:2
 ├── input binding: &1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 ├── project
 │    ├── columns: v_new:13!null uniq_partial.k:7!null uniq_partial.v:8 uniq_partial.w:9
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(7-9,13)
 │    ├── select
 │    │    ├── columns: uniq_partial.k:7!null uniq_partial.v:8 uniq_partial.w:9
 │    │    ├── cardinality: [0 - 1]
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(7-9)
 │    │    ├── scan uniq_partial
 │    │    │    ├── columns: uniq_partial.k:7!null uniq_partial.v:8 uniq_partial.w:9
 │    │    │    ├── flags: avoid-full-scan
 │    │    │    ├── key: (7)
 │    │    │    └── fd: (7)-->(8,9)
 │    │    └── filters
 │    │         └── uniq_partial.k:7 = 3 [outer=(7), constraints=(/7: [/3 - /3]; tight), fd=()-->(7)]
 │    └── projections
 │         └── 1 [as=v_new:13]
 └── unique-checks
      └── unique-checks-item: uniq_partial(v)
           └── project
                ├── columns: v:21!null
                ├── cardinality: [0 - 1]
                ├── key: ()
                ├── fd: ()-->(21)
                └── semi-join (hash)
                     ├── columns: k:20!null v:21!null w:22!null
                     ├── cardinality: [0 - 1]
                     ├── key: ()
                     ├── fd: ()-->(20-22)
                     ├── select
                     │    ├── columns: k:20!null v:21!null w:22!null
                     │    ├── cardinality: [0 - 1]
                     │    ├── key: ()
                     │    ├── fd: ()-->(20-22)
                     │    ├── with-scan &1
                     │    │    ├── columns: k:20!null v:21!null w:22
                     │    │    ├── mapping:
                     │    │    │    ├──  uniq_partial.k:7 => k:20
                     │    │    │    ├──  v_new:13 => v:21
                     │    │    │    └──  uniq_partial.w:9 => w:22
                     │    │    ├── cardinality: [0 - 1]
                     │    │    ├── key: ()
                     │    │    └── fd: ()-->(20-22)
                     │    └── filters
                     │         └── w:22 > 0 [outer=(22), constraints=(/22: [/1 - ]; tight)]
                     ├── select
                     │    ├── columns: uniq_partial.k:14!null uniq_partial.v:15 uniq_partial.w:16!null
                     │    ├── key: (14)
                     │    ├── fd: (14)-->(15,16)
                     │    ├── scan uniq_partial
                     │    │    ├── columns: uniq_partial.k:14!null uniq_partial.v:15 uniq_partial.w:16
                     │    │    ├── flags: avoid-full-scan
                     │    │    ├── key: (14)
                     │    │    └── fd: (14)-->(15,16)
                     │    └── filters
                     │         └── uniq_partial.w:16 > 0 [outer=(16), constraints=(/16: [/1 - ]; tight)]
                     └── filters
                          ├── v:21 = uniq_partial.v:15 [outer=(15,21), constraints=(/15: (/NULL - ]; /21: (/NULL - ]), fd=(15)==(21), (21)==(15)]
                          └── k:20 != uniq_partial.k:14 [outer=(14,20), constraints=(/14: (/NULL - ]; /20: (/NULL - ])]

# Do not prune columns that are needed for foreign key checks or cascades.
norm expect=PruneMutationInputCols
INSERT INTO uniq_fk_parent VALUES (2, 1) ON CONFLICT (k) DO UPDATE SET c = 1
----
upsert uniq_fk_parent
 ├── arbiter indexes: uniq_fk_parent_pkey
 ├── columns: <none>
 ├── canary column: uniq_fk_parent.k:11
 ├── fetch columns: uniq_fk_parent.k:11 uniq_fk_parent.b:13 uniq_fk_parent.c:14
 ├── insert-mapping:
 │    ├── column1:8 => uniq_fk_parent.k:1
 │    ├── column2:9 => uniq_fk_parent.a:2
 │    ├── b_default:10 => uniq_fk_parent.b:3
 │    ├── b_default:10 => uniq_fk_parent.c:4
 │    └── b_default:10 => uniq_fk_parent.d:5
 ├── update-mapping:
 │    └── upsert_c:22 => uniq_fk_parent.c:4
 ├── input binding: &1
 ├── cascades
 │    └── uniq_fk_child_b_c_fkey
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 ├── project
 │    ├── columns: upsert_k:19 upsert_a:20 upsert_b:21 upsert_c:22 column1:8!null column2:9!null b_default:10 uniq_fk_parent.k:11 uniq_fk_parent.b:13 uniq_fk_parent.c:14
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(8-11,13,14,19-22)
 │    ├── left-join (cross)
 │    │    ├── columns: column1:8!null column2:9!null b_default:10 uniq_fk_parent.k:11 uniq_fk_parent.a:12 uniq_fk_parent.b:13 uniq_fk_parent.c:14
 │    │    ├── cardinality: [1 - 1]
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(8-14)
 │    │    ├── values
 │    │    │    ├── columns: column1:8!null column2:9!null b_default:10
 │    │    │    ├── cardinality: [1 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(8-10)
 │    │    │    └── (2, 1, NULL)
 │    │    ├── select
 │    │    │    ├── columns: uniq_fk_parent.k:11!null uniq_fk_parent.a:12 uniq_fk_parent.b:13 uniq_fk_parent.c:14
 │    │    │    ├── cardinality: [0 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(11-14)
 │    │    │    ├── scan uniq_fk_parent
 │    │    │    │    ├── columns: uniq_fk_parent.k:11!null uniq_fk_parent.a:12 uniq_fk_parent.b:13 uniq_fk_parent.c:14
 │    │    │    │    ├── flags: avoid-full-scan
 │    │    │    │    ├── key: (11)
 │    │    │    │    └── fd: (11)-->(12-14), (12)~~>(11,13,14), (13,14)~~>(11,12)
 │    │    │    └── filters
 │    │    │         └── uniq_fk_parent.k:11 = 2 [outer=(11), constraints=(/11: [/2 - /2]; tight), fd=()-->(11)]
 │    │    └── filters (true)
 │    └── projections
 │         ├── CASE WHEN uniq_fk_parent.k:11 IS NULL THEN column1:8 ELSE uniq_fk_parent.k:11 END [as=upsert_k:19, outer=(8,11)]
 │         ├── CASE WHEN uniq_fk_parent.k:11 IS NULL THEN column2:9 ELSE uniq_fk_parent.a:12 END [as=upsert_a:20, outer=(9,11,12)]
 │         ├── CASE WHEN uniq_fk_parent.k:11 IS NULL THEN b_default:10 ELSE uniq_fk_parent.b:13 END [as=upsert_b:21, outer=(10,11,13)]
 │         └── CASE WHEN uniq_fk_parent.k:11 IS NULL THEN b_default:10 ELSE 1 END [as=upsert_c:22, outer=(10,11)]
 └── unique-checks
      ├── unique-checks-item: uniq_fk_parent(a)
      │    └── project
      │         ├── columns: a:32
      │         ├── cardinality: [0 - 1]
      │         ├── key: ()
      │         ├── fd: ()-->(32)
      │         └── semi-join (hash)
      │              ├── columns: k:31 a:32
      │              ├── cardinality: [0 - 1]
      │              ├── key: ()
      │              ├── fd: ()-->(31,32)
      │              ├── with-scan &1
      │              │    ├── columns: k:31 a:32
      │              │    ├── mapping:
      │              │    │    ├──  upsert_k:19 => k:31
      │              │    │    └──  upsert_a:20 => a:32
      │              │    ├── cardinality: [1 - 1]
      │              │    ├── key: ()
      │              │    └── fd: ()-->(31,32)
      │              ├── scan uniq_fk_parent
      │              │    ├── columns: uniq_fk_parent.k:24!null uniq_fk_parent.a:25
      │              │    ├── flags: avoid-full-scan
      │              │    ├── key: (24)
      │              │    └── fd: (24)-->(25)
      │              └── filters
      │                   ├── a:32 = uniq_fk_parent.a:25 [outer=(25,32), constraints=(/25: (/NULL - ]; /32: (/NULL - ]), fd=(25)==(32), (32)==(25)]
      │                   └── k:31 != uniq_fk_parent.k:24 [outer=(24,31), constraints=(/24: (/NULL - ]; /31: (/NULL - ])]
      └── unique-checks-item: uniq_fk_parent(b,c)
           └── project
                ├── columns: b:45 c:46
                ├── cardinality: [0 - 1]
                ├── key: ()
                ├── fd: ()-->(45,46)
                └── semi-join (hash)
                     ├── columns: k:43 b:45 c:46
                     ├── cardinality: [0 - 1]
                     ├── key: ()
                     ├── fd: ()-->(43,45,46)
                     ├── with-scan &1
                     │    ├── columns: k:43 b:45 c:46
                     │    ├── mapping:
                     │    │    ├──  upsert_k:19 => k:43
                     │    │    ├──  upsert_b:21 => b:45
                     │    │    └──  upsert_c:22 => c:46
                     │    ├── cardinality: [1 - 1]
                     │    ├── key: ()
                     │    └── fd: ()-->(43,45,46)
                     ├── scan uniq_fk_parent
                     │    ├── columns: uniq_fk_parent.k:36!null uniq_fk_parent.b:38 uniq_fk_parent.c:39
                     │    ├── flags: avoid-full-scan
                     │    ├── key: (36)
                     │    └── fd: (36)-->(38,39)
                     └── filters
                          ├── b:45 = uniq_fk_parent.b:38 [outer=(38,45), constraints=(/38: (/NULL - ]; /45: (/NULL - ]), fd=(38)==(45), (45)==(38)]
                          ├── c:46 = uniq_fk_parent.c:39 [outer=(39,46), constraints=(/39: (/NULL - ]; /46: (/NULL - ]), fd=(39)==(46), (46)==(39)]
                          └── k:43 != uniq_fk_parent.k:36 [outer=(36,43), constraints=(/36: (/NULL - ]; /43: (/NULL - ])]

# Prune inbound foreign key columns when they are not updated.
norm expect=PruneMutationInputCols
INSERT INTO uniq_fk_parent VALUES (1) ON CONFLICT (k) DO UPDATE SET d = 1
----
upsert uniq_fk_parent
 ├── arbiter indexes: uniq_fk_parent_pkey
 ├── columns: <none>
 ├── canary column: uniq_fk_parent.k:10
 ├── fetch columns: uniq_fk_parent.k:10 uniq_fk_parent.d:14
 ├── insert-mapping:
 │    ├── column1:8 => uniq_fk_parent.k:1
 │    ├── a_default:9 => uniq_fk_parent.a:2
 │    ├── a_default:9 => uniq_fk_parent.b:3
 │    ├── a_default:9 => uniq_fk_parent.c:4
 │    └── a_default:9 => uniq_fk_parent.d:5
 ├── update-mapping:
 │    └── upsert_d:22 => uniq_fk_parent.d:5
 ├── input binding: &1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 ├── project
 │    ├── columns: upsert_k:18 upsert_a:19 upsert_b:20 upsert_c:21 upsert_d:22 column1:8!null a_default:9 uniq_fk_parent.k:10 uniq_fk_parent.d:14
 │    ├── cardinality: [1 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(8-10,14,18-22)
 │    ├── left-join (cross)
 │    │    ├── columns: column1:8!null a_default:9 uniq_fk_parent.k:10 uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 uniq_fk_parent.d:14
 │    │    ├── cardinality: [1 - 1]
 │    │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
 │    │    ├── key: ()
 │    │    ├── fd: ()-->(8-14)
 │    │    ├── values
 │    │    │    ├── columns: column1:8!null a_default:9
 │    │    │    ├── cardinality: [1 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(8,9)
 │    │    │    └── (1, NULL)
 │    │    ├── select
 │    │    │    ├── columns: uniq_fk_parent.k:10!null uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 uniq_fk_parent.d:14
 │    │    │    ├── cardinality: [0 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(10-14)
 │    │    │    ├── scan uniq_fk_parent
 │    │    │    │    ├── columns: uniq_fk_parent.k:10!null uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 uniq_fk_parent.d:14
 │    │    │    │    ├── flags: avoid-full-scan
 │    │    │    │    ├── key: (10)
 │    │    │    │    └── fd: (10)-->(11-14), (11)~~>(10,12-14), (12,13)~~>(10,11,14)
 │    │    │    └── filters
 │    │    │         └── uniq_fk_parent.k:10 = 1 [outer=(10), constraints=(/10: [/1 - /1]; tight), fd=()-->(10)]
 │    │    └── filters (true)
 │    └── projections
 │         ├── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN column1:8 ELSE uniq_fk_parent.k:10 END [as=upsert_k:18, outer=(8,10)]
 │         ├── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN a_default:9 ELSE uniq_fk_parent.a:11 END [as=upsert_a:19, outer=(9-11)]
 │         ├── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN a_default:9 ELSE uniq_fk_parent.b:12 END [as=upsert_b:20, outer=(9,10,12)]
 │         ├── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN a_default:9 ELSE uniq_fk_parent.c:13 END [as=upsert_c:21, outer=(9,10,13)]
 │         └── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN a_default:9 ELSE 1 END [as=upsert_d:22, outer=(9,10)]
 └── unique-checks
      ├── unique-checks-item: uniq_fk_parent(a)
      │    └── project
      │         ├── columns: a:31
      │         ├── cardinality: [0 - 1]
      │         ├── key: ()
      │         ├── fd: ()-->(31)
      │         └── semi-join (hash)
      │              ├── columns: k:30 a:31
      │              ├── cardinality: [0 - 1]
      │              ├── key: ()
      │              ├── fd: ()-->(30,31)
      │              ├── with-scan &1
      │              │    ├── columns: k:30 a:31
      │              │    ├── mapping:
      │              │    │    ├──  upsert_k:18 => k:30
      │              │    │    └──  upsert_a:19 => a:31
      │              │    ├── cardinality: [1 - 1]
      │              │    ├── key: ()
      │              │    └── fd: ()-->(30,31)
      │              ├── scan uniq_fk_parent
      │              │    ├── columns: uniq_fk_parent.k:23!null uniq_fk_parent.a:24
      │              │    ├── flags: avoid-full-scan
      │              │    ├── key: (23)
      │              │    └── fd: (23)-->(24)
      │              └── filters
      │                   ├── a:31 = uniq_fk_parent.a:24 [outer=(24,31), constraints=(/24: (/NULL - ]; /31: (/NULL - ]), fd=(24)==(31), (31)==(24)]
      │                   └── k:30 != uniq_fk_parent.k:23 [outer=(23,30), constraints=(/23: (/NULL - ]; /30: (/NULL - ])]
      └── unique-checks-item: uniq_fk_parent(b,c)
           └── project
                ├── columns: b:44 c:45
                ├── cardinality: [0 - 1]
                ├── key: ()
                ├── fd: ()-->(44,45)
                └── semi-join (hash)
                     ├── columns: k:42 b:44 c:45
                     ├── cardinality: [0 - 1]
                     ├── key: ()
                     ├── fd: ()-->(42,44,45)
                     ├── with-scan &1
                     │    ├── columns: k:42 b:44 c:45
                     │    ├── mapping:
                     │    │    ├──  upsert_k:18 => k:42
                     │    │    ├──  upsert_b:20 => b:44
                     │    │    └──  upsert_c:21 => c:45
                     │    ├── cardinality: [1 - 1]
                     │    ├── key: ()
                     │    └── fd: ()-->(42,44,45)
                     ├── scan uniq_fk_parent
                     │    ├── columns: uniq_fk_parent.k:35!null uniq_fk_parent.b:37 uniq_fk_parent.c:38
                     │    ├── flags: avoid-full-scan
                     │    ├── key: (35)
                     │    └── fd: (35)-->(37,38)
                     └── filters
                          ├── b:44 = uniq_fk_parent.b:37 [outer=(37,44), constraints=(/37: (/NULL - ]; /44: (/NULL - ]), fd=(37)==(44), (44)==(37)]
                          ├── c:45 = uniq_fk_parent.c:38 [outer=(38,45), constraints=(/38: (/NULL - ]; /45: (/NULL - ]), fd=(38)==(45), (45)==(38)]
                          └── k:42 != uniq_fk_parent.k:35 [outer=(35,42), constraints=(/35: (/NULL - ]; /42: (/NULL - ])]

# Do not prune columns that are needed for foreign key checks or cascades.
norm expect=PruneMutationInputCols
DELETE FROM uniq_fk_parent WHERE k = 1
----
delete uniq_fk_parent
 ├── columns: <none>
 ├── fetch columns: k:8 uniq_fk_parent.a:9 uniq_fk_parent.b:10 uniq_fk_parent.c:11
 ├── input binding: &1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 ├── select
 │    ├── columns: k:8!null uniq_fk_parent.a:9 uniq_fk_parent.b:10 uniq_fk_parent.c:11
 │    ├── cardinality: [0 - 1]
 │    ├── key: ()
 │    ├── fd: ()-->(8-11)
 │    ├── scan uniq_fk_parent
 │    │    ├── columns: k:8!null uniq_fk_parent.a:9 uniq_fk_parent.b:10 uniq_fk_parent.c:11
 │    │    ├── flags: avoid-full-scan
 │    │    ├── key: (8)
 │    │    └── fd: (8)-->(9-11), (9)~~>(8,10,11), (10,11)~~>(8,9)
 │    └── filters
 │         └── k:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)]
 └── f-k-checks
      ├── f-k-checks-item: uniq_fk_child(b,c) -> uniq_fk_parent(b,c)
      │    └── semi-join (hash)
      │         ├── columns: b:15 c:16
      │         ├── cardinality: [0 - 1]
      │         ├── key: ()
      │         ├── fd: ()-->(15,16)
      │         ├── with-scan &1
      │         │    ├── columns: b:15 c:16
      │         │    ├── mapping:
      │         │    │    ├──  uniq_fk_parent.b:10 => b:15
      │         │    │    └──  uniq_fk_parent.c:11 => c:16
      │         │    ├── cardinality: [0 - 1]
      │         │    ├── key: ()
      │         │    └── fd: ()-->(15,16)
      │         ├── scan uniq_fk_child
      │         │    ├── columns: uniq_fk_child.b:18 uniq_fk_child.c:19
      │         │    ├── flags: avoid-full-scan
      │         │    ├── lax-key: (18,19)
      │         │    └── fd: (19)~~>(18)
      │         └── filters
      │              ├── b:15 = uniq_fk_child.b:18 [outer=(15,18), constraints=(/15: (/NULL - ]; /18: (/NULL - ]), fd=(15)==(18), (18)==(15)]
      │              └── c:16 = uniq_fk_child.c:19 [outer=(16,19), constraints=(/16: (/NULL - ]; /19: (/NULL - ]), fd=(16)==(19), (19)==(16)]
      └── f-k-checks-item: uniq_fk_child(a) -> uniq_fk_parent(a)
           └── semi-join (hash)
                ├── columns: a:24
                ├── cardinality: [0 - 1]
                ├── key: ()
                ├── fd: ()-->(24)
                ├── with-scan &1
                │    ├── columns: a:24
                │    ├── mapping:
                │    │    └──  uniq_fk_parent.a:9 => a:24
                │    ├── cardinality: [0 - 1]
                │    ├── key: ()
                │    └── fd: ()-->(24)
                ├── scan uniq_fk_child
                │    ├── columns: uniq_fk_child.a:25
                │    └── flags: avoid-full-scan
                └── filters
                     └── a:24 = uniq_fk_child.a:25 [outer=(24,25), constraints=(/24: (/NULL - ]; /25: (/NULL - ]), fd=(24)==(25), (25)==(24)]

# We should be pruning the virtual column.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
DELETE FROM virt WHERE a > 1
----
delete virt
 ├── columns: <none>
 ├── fetch columns: a:6
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── select
      ├── columns: a:6!null
      ├── key: (6)
      ├── scan virt
      │    ├── columns: a:6!null
      │    ├── computed column expressions
      │    │    └── v:8
      │    │         └── a:6 + b:7
      │    ├── flags: avoid-full-scan
      │    └── key: (6)
      └── filters
           └── a:6 > 1 [outer=(6), constraints=(/6: [/2 - ]; tight)]

# We should not be pruning the virtual column if it is used by RETURNING.
norm
DELETE FROM virt WHERE a > 1 RETURNING v
----
project
 ├── columns: v:3
 ├── volatile, mutations
 └── delete virt
      ├── columns: a:1!null v:3
      ├── fetch columns: a:6 v:8
      ├── return-mapping:
      │    ├── a:6 => a:1
      │    └── v:8 => v:3
      ├── volatile, mutations
      ├── key: (1)
      ├── fd: (1)-->(3)
      └── project
           ├── columns: v:8 a:6!null
           ├── immutable
           ├── key: (6)
           ├── fd: (6)-->(8)
           ├── select
           │    ├── columns: a:6!null b:7
           │    ├── key: (6)
           │    ├── fd: (6)-->(7)
           │    ├── scan virt
           │    │    ├── columns: a:6!null b:7
           │    │    ├── computed column expressions
           │    │    │    └── v:8
           │    │    │         └── a:6 + b:7
           │    │    ├── flags: avoid-full-scan
           │    │    ├── key: (6)
           │    │    └── fd: (6)-->(7)
           │    └── filters
           │         └── a:6 > 1 [outer=(6), constraints=(/6: [/2 - ]; tight)]
           └── projections
                └── a:6 + b:7 [as=v:8, outer=(6,7), immutable]

# We should not be pruning the virtual column when it is part of an index.
norm
DELETE FROM virt_idx WHERE a > 1
----
delete virt_idx
 ├── columns: <none>
 ├── fetch columns: a:7 v:10
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: v:10 a:7!null
      ├── immutable
      ├── key: (7)
      ├── fd: (7)-->(10)
      ├── select
      │    ├── columns: a:7!null b:8
      │    ├── key: (7)
      │    ├── fd: (7)-->(8)
      │    ├── scan virt_idx
      │    │    ├── columns: a:7!null b:8
      │    │    ├── computed column expressions
      │    │    │    └── v:10
      │    │    │         └── a:7 + b:8
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (7)
      │    │    └── fd: (7)-->(8)
      │    └── filters
      │         └── a:7 > 1 [outer=(7), constraints=(/7: [/2 - ]; tight)]
      └── projections
           └── a:7 + b:8 [as=v:10, outer=(7,8), immutable]

# We cannot prune the virtual column because it is modified.
norm
UPDATE virt_idx SET a = a + 1
----
update virt_idx
 ├── columns: <none>
 ├── fetch columns: a:7 b:8 c:9 v:10
 ├── update-mapping:
 │    ├── a_new:13 => a:1
 │    └── v_comp:14 => v:4
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: v_comp:14 a:7!null b:8 c:9 v:10 a_new:13!null
      ├── immutable
      ├── key: (7)
      ├── fd: (7)-->(8-10,13), (8,13)-->(14)
      ├── project
      │    ├── columns: a_new:13!null v:10 a:7!null b:8 c:9
      │    ├── immutable
      │    ├── key: (7)
      │    ├── fd: (7)-->(8-10,13)
      │    ├── scan virt_idx
      │    │    ├── columns: a:7!null b:8 c:9
      │    │    ├── computed column expressions
      │    │    │    └── v:10
      │    │    │         └── a:7 + b:8
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (7)
      │    │    └── fd: (7)-->(8,9)
      │    └── projections
      │         ├── a:7 + 1 [as=a_new:13, outer=(7), immutable]
      │         └── a:7 + b:8 [as=v:10, outer=(7,8), immutable]
      └── projections
           └── a_new:13 + b:8 [as=v_comp:14, outer=(8,13), immutable]

# We cannot prune column v because it is affected by the change.
norm
UPDATE virt_idx SET b = b + 1
----
update virt_idx
 ├── columns: <none>
 ├── fetch columns: a:7 b:8 v:10
 ├── update-mapping:
 │    ├── b_new:13 => b:2
 │    └── v_comp:14 => v:4
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: v_comp:14 a:7!null b:8 v:10 b_new:13
      ├── immutable
      ├── key: (7)
      ├── fd: (7)-->(8,10,14), (8)-->(13)
      ├── project
      │    ├── columns: b_new:13 v:10 a:7!null b:8
      │    ├── immutable
      │    ├── key: (7)
      │    ├── fd: (7)-->(8,10), (8)-->(13)
      │    ├── scan virt_idx
      │    │    ├── columns: a:7!null b:8
      │    │    ├── computed column expressions
      │    │    │    └── v:10
      │    │    │         └── a:7 + b:8
      │    │    ├── flags: avoid-full-scan
      │    │    ├── key: (7)
      │    │    └── fd: (7)-->(8)
      │    └── projections
      │         ├── b:8 + 1 [as=b_new:13, outer=(8), immutable]
      │         └── a:7 + b:8 [as=v:10, outer=(7,8), immutable]
      └── projections
           └── a:7 + b_new:13 [as=v_comp:14, outer=(7,13), immutable]

# We should prune columns b and v because they are unaffected by the update.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
UPDATE virt_idx SET c = c + 1
----
update virt_idx
 ├── columns: <none>
 ├── fetch columns: a:7 c:9
 ├── update-mapping:
 │    └── c_new:13 => c:3
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: c_new:13 a:7!null c:9
      ├── immutable
      ├── key: (7)
      ├── fd: (7)-->(9), (9)-->(13)
      ├── scan virt_idx
      │    ├── columns: a:7!null c:9
      │    ├── computed column expressions
      │    │    └── v:10
      │    │         └── a:7 + b:8
      │    ├── flags: avoid-full-scan
      │    ├── key: (7)
      │    └── fd: (7)-->(9)
      └── projections
           └── c:9 + 1 [as=c_new:13, outer=(9), immutable]

# Even though v doesn't depend on a changed column, we cannot prune it because
# the PK is changed and corresponding index entries need to be updated.
norm expect=PruneMutationInputCols expect-not=PruneMutationFetchCols
UPDATE virt_idx2 SET a = a + 1
----
update virt_idx2
 ├── columns: <none>
 ├── fetch columns: a:7 b:8 c:9 v:10
 ├── update-mapping:
 │    └── a_new:13 => a:1
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: a_new:13!null v:10 a:7!null b:8 c:9
      ├── immutable
      ├── key: (7)
      ├── fd: (7)-->(8,9,13), (8)-->(10)
      ├── scan virt_idx2
      │    ├── columns: a:7!null b:8 c:9
      │    ├── computed column expressions
      │    │    └── v:10
      │    │         └── b:8 + 1
      │    ├── flags: avoid-full-scan
      │    ├── key: (7)
      │    └── fd: (7)-->(8,9)
      └── projections
           ├── a:7 + 1 [as=a_new:13, outer=(7), immutable]
           └── b:8 + 1 [as=v:10, outer=(8), immutable]

# We cannot prune column v because it forms secondary index keys with the
# modified column.
norm expect=(PruneMutationFetchCols,PruneMutationInputCols)
UPDATE virt_idx3 SET c = c + 1
----
update virt_idx3
 ├── columns: <none>
 ├── fetch columns: a:7 c:9 v:10
 ├── update-mapping:
 │    └── c_new:13 => c:3
 ├── cardinality: [0 - 0]
 ├── volatile, mutations
 └── project
      ├── columns: c_new:13 v:10 a:7!null c:9
      ├── immutable
      ├── key: (7)
      ├── fd: (7)-->(9,10,13), (9)-->(13)
      ├── scan virt_idx3
      │    ├── columns: a:7!null b:8 c:9
      │    ├── computed column expressions
      │    │    └── v:10
      │    │         └── b:8 + 1
      │    ├── flags: avoid-full-scan
      │    ├── key: (7)
      │    └── fd: (7)-->(8,9)
      └── projections
           ├── c:9 + 1 [as=c_new:13, outer=(9), immutable]
           └── b:8 + 1 [as=v:10, outer=(8), immutable]

# ------------------------------------------------------------------------------
# PruneInsertReturnCols
# ------------------------------------------------------------------------------

# Returning a single insert column can prune the others.
norm expect=PruneInsertReturnCols
INSERT INTO computed (a, b, c) VALUES (1, 2, 3) RETURNING a
----
insert computed
 ├── columns: a:1!null
 ├── insert-mapping:
 │    ├── column1:8 => a:1
 │    ├── column2:9 => b:2
 │    ├── column3:10 => c:3
 │    ├── d_comp:11 => d:4
 │    └── x_comp:12 => x:5
 ├── return-mapping:
 │    └── column1:8 => a:1
 ├── cardinality: [1 - 1]
 ├── volatile, mutations
 ├── key: ()
 ├── fd: ()-->(1)
 └── values
      ├── columns: column1:8!null column2:9!null column3:10!null d_comp:11!null x_comp:12!null
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(8-12)
      └── (1, 2, 3, 4, 13)

# RETURNING * does not prune columns
norm expect-not=PruneInsertReturnCols
INSERT INTO computed (a, b, c) SELECT a, b, c FROM computed RETURNING *
----
insert computed
 ├── columns: a:1!null b:2 c:3 d:4
 ├── insert-mapping:
 │    ├── a:8 => a:1
 │    ├── b:9 => b:2
 │    ├── c:10 => c:3
 │    ├── d_comp:15 => d:4
 │    └── x_comp:16 => x:5
 ├── return-mapping:
 │    ├── a:8 => a:1
 │    ├── b:9 => b:2
 │    ├── c:10 => c:3
 │    └── d_comp:15 => d:4
 ├── volatile, mutations
 ├── key: (1)
 ├── fd: (1)-->(2-4), (3)-->(4)
 └── project
      ├── columns: d_comp:15 x_comp:16 a:8!null b:9 c:10
      ├── immutable
      ├── key: (8)
      ├── fd: (8)-->(9,10), (10)-->(15,16)
      ├── scan computed
      │    ├── columns: a:8!null b:9 c:10
      │    ├── computed column expressions
      │    │    └── d:11
      │    │         └── c:10 + 1
      │    ├── key: (8)
      │    └── fd: (8)-->(9,10)
      └── projections
           ├── c:10 + 1 [as=d_comp:15, outer=(10), immutable]
           └── c:10 + 10 [as=x_comp:16, outer=(10), immutable]

# Returning a subset of insert columns can prune the others.
norm expect=PruneInsertReturnCols
INSERT INTO computed (a, b, c) SELECT a, b, c FROM computed RETURNING a, b, c
----
insert computed
 ├── columns: a:1!null b:2 c:3
 ├── insert-mapping:
 │    ├── a:8 => a:1
 │    ├── b:9 => b:2
 │    ├── c:10 => c:3
 │    ├── d_comp:15 => d:4
 │    └── x_comp:16 => x:5
 ├── return-mapping:
 │    ├── a:8 => a:1
 │    ├── b:9 => b:2
 │    └── c:10 => c:3
 ├── volatile, mutations
 ├── key: (1)
 ├── fd: (1)-->(2,3)
 └── project
      ├── columns: d_comp:15 x_comp:16 a:8!null b:9 c:10
      ├── immutable
      ├── key: (8)
      ├── fd: (8)-->(9,10), (10)-->(15,16)
      ├── scan computed
      │    ├── columns: a:8!null b:9 c:10
      │    ├── computed column expressions
      │    │    └── d:11
      │    │         └── c:10 + 1
      │    ├── key: (8)
      │    └── fd: (8)-->(9,10)
      └── projections
           ├── c:10 + 1 [as=d_comp:15, outer=(10), immutable]
           └── c:10 + 10 [as=x_comp:16, outer=(10), immutable]

# We apply the PruneInsertReturnCols rule multiple times, to get
# the minimal set of columns. Columns c and b are pruned away.
norm expect=PruneInsertReturnCols
SELECT a FROM [SELECT a, b FROM [INSERT INTO computed (a, b, c) VALUES (1, 2, 3) RETURNING a, b, c]]
----
with &1
 ├── columns: a:16!null
 ├── cardinality: [1 - 1]
 ├── volatile, mutations
 ├── key: ()
 ├── fd: ()-->(16)
 ├── insert computed
 │    ├── columns: computed.a:1!null computed.b:2!null computed.c:3!null
 │    ├── insert-mapping:
 │    │    ├── column1:8 => computed.a:1
 │    │    ├── column2:9 => computed.b:2
 │    │    ├── column3:10 => computed.c:3
 │    │    ├── d_comp:11 => d:4
 │    │    └── x_comp:12 => x:5
 │    ├── return-mapping:
 │    │    ├── column1:8 => computed.a:1
 │    │    ├── column2:9 => computed.b:2
 │    │    └── column3:10 => computed.c:3
 │    ├── cardinality: [1 - 1]
 │    ├── volatile, mutations
 │    ├── key: ()
 │    ├── fd: ()-->(1-3)
 │    └── values
 │         ├── columns: column1:8!null column2:9!null column3:10!null d_comp:11!null x_comp:12!null
 │         ├── cardinality: [1 - 1]
 │         ├── key: ()
 │         ├── fd: ()-->(8-12)
 │         └── (1, 2, 3, 4, 13)
 └── project
      ├── columns: a:16!null
      ├── cardinality: [1 - 1]
      ├── key: ()
      ├── fd: ()-->(16)
      ├── with-scan &1
      │    ├── columns: a:13!null
      │    ├── mapping:
      │    │    └──  computed.a:1 => a:13
      │    ├── cardinality: [1 - 1]
      │    ├── key: ()
      │    └── fd: ()-->(13)
      └── projections
           └── a:13 [as=a:16, outer=(13)]

# ------------------------------------------------------------------------------
# PruneMutationReturnCols
# ------------------------------------------------------------------------------

# Create a table with multiple column families the mutations can take advantage of.
exec-ddl
CREATE TABLE returning_test (
    a INT,
    b INT,
    c STRING,
    d INT,
    e INT,
    f INT,
    g INT,
    FAMILY (a),
    FAMILY (b),
    FAMILY (c),
    FAMILY (d, e, f, g),
    UNIQUE (a)
)
----

# Fetch all the columns for the RETURN expression.
norm
UPDATE returning_test SET a = a + 1 RETURNING *
----
project
 ├── columns: a:1 b:2 c:3 d:4 e:5 f:6 g:7
 ├── volatile, mutations
 └── update returning_test
      ├── columns: a:1 b:2 c:3 d:4 e:5 f:6 g:7 rowid:8!null
      ├── fetch columns: a:11 b:12 c:13 d:14 e:15 f:16 g:17 rowid:18
      ├── update-mapping:
      │    └── a_new:21 => a:1
      ├── return-mapping:
      │    ├── a_new:21 => a:1
      │    ├── b:12 => b:2
      │    ├── c:13 => c:3
      │    ├── d:14 => d:4
      │    ├── e:15 => e:5
      │    ├── f:16 => f:6
      │    ├── g:17 => g:7
      │    └── rowid:18 => rowid:8
      ├── volatile, mutations
      ├── key: (8)
      ├── fd: (8)-->(1-7)
      └── project
           ├── columns: a_new:21 a:11 b:12 c:13 d:14 e:15 f:16 g:17 rowid:18!null
           ├── immutable
           ├── key: (18)
           ├── fd: (18)-->(11-17), (11)~~>(12-18), (11)-->(21)
           ├── scan returning_test
           │    ├── columns: a:11 b:12 c:13 d:14 e:15 f:16 g:17 rowid:18!null
           │    ├── flags: avoid-full-scan
           │    ├── key: (18)
           │    └── fd: (18)-->(11-17), (11)~~>(12-18)
           └── projections
                └── a:11 + 1 [as=a_new:21, outer=(11), immutable]


# Fetch all the columns in the (d, e, f, g) family as d is being set.
norm
UPDATE returning_test SET d = a + d RETURNING a, d
----
project
 ├── columns: a:1 d:4
 ├── volatile, mutations
 ├── lax-key: (1,4)
 ├── fd: (1)~~>(4)
 └── update returning_test
      ├── columns: a:1 d:4 rowid:8!null
      ├── fetch columns: a:11 d:14 e:15 f:16 g:17 rowid:18
      ├── update-mapping:
      │    └── d_new:21 => d:4
      ├── return-mapping:
      │    ├── a:11 => a:1
      │    ├── d_new:21 => d:4
      │    └── rowid:18 => rowid:8
      ├── volatile, mutations
      ├── key: (8)
      ├── fd: (8)-->(1,4), (1)~~>(4,8)
      └── project
           ├── columns: d_new:21 a:11 d:14 e:15 f:16 g:17 rowid:18!null
           ├── immutable
           ├── key: (18)
           ├── fd: (18)-->(11,14-17), (11)~~>(14-18), (11,14)-->(21)
           ├── scan returning_test
           │    ├── columns: a:11 d:14 e:15 f:16 g:17 rowid:18!null
           │    ├── flags: avoid-full-scan
           │    ├── key: (18)
           │    └── fd: (18)-->(11,14-17), (11)~~>(14-18)
           └── projections
                └── a:11 + d:14 [as=d_new:21, outer=(11,14), immutable]

# Fetch only whats being updated (not the (d, e, f, g) family).
norm
UPDATE returning_test SET a = a + d RETURNING a
----
project
 ├── columns: a:1
 ├── volatile, mutations
 └── update returning_test
      ├── columns: a:1 rowid:8!null
      ├── fetch columns: a:11 rowid:18
      ├── update-mapping:
      │    └── a_new:21 => a:1
      ├── return-mapping:
      │    ├── a_new:21 => a:1
      │    └── rowid:18 => rowid:8
      ├── volatile, mutations
      ├── key: (8)
      ├── fd: (8)-->(1)
      └── project
           ├── columns: a_new:21 a:11 rowid:18!null
           ├── immutable
           ├── key: (18)
           ├── fd: (18)-->(11,21), (11)~~>(18,21)
           ├── scan returning_test
           │    ├── columns: a:11 d:14 rowid:18!null
           │    ├── flags: avoid-full-scan
           │    ├── key: (18)
           │    └── fd: (18)-->(11,14), (11)~~>(14,18)
           └── projections
                └── a:11 + d:14 [as=a_new:21, outer=(11,14), immutable]

# We only fetch the minimal set of columns which is (a, b, c, rowid).
norm
UPDATE returning_test SET (b, a) = (a, a + b) RETURNING a, b, c
----
project
 ├── columns: a:1 b:2 c:3
 ├── volatile, mutations
 ├── lax-key: (1-3)
 ├── fd: (2)~~>(1,3)
 └── update returning_test
      ├── columns: a:1 b:2 c:3 rowid:8!null
      ├── fetch columns: a:11 b:12 c:13 rowid:18
      ├── update-mapping:
      │    ├── a_new:21 => a:1
      │    └── a:11 => b:2
      ├── return-mapping:
      │    ├── a_new:21 => a:1
      │    ├── a:11 => b:2
      │    ├── c:13 => c:3
      │    └── rowid:18 => rowid:8
      ├── volatile, mutations
      ├── key: (8)
      ├── fd: (8)-->(1-3), (2)~~>(1,3,8)
      └── project
           ├── columns: a_new:21 a:11 b:12 c:13 rowid:18!null
           ├── immutable
           ├── key: (18)
           ├── fd: (18)-->(11-13), (11)~~>(12,13,18), (11,12)-->(21)
           ├── scan returning_test
           │    ├── columns: a:11 b:12 c:13 rowid:18!null
           │    ├── flags: avoid-full-scan
           │    ├── key: (18)
           │    └── fd: (18)-->(11-13), (11)~~>(12,13,18)
           └── projections
                └── a:11 + b:12 [as=a_new:21, outer=(11,12), immutable]


# We apply the PruneMutationReturnCols rule multiple times, to get
# the minimal set of columns which is (a, rowid). Notice how c and b
# are pruned away.
norm
SELECT a FROM [SELECT a, b FROM [UPDATE returning_test SET a = a + 1 RETURNING a, b, c]]
----
with &1
 ├── columns: a:25
 ├── volatile, mutations
 ├── project
 │    ├── columns: returning_test.a:1 returning_test.b:2 returning_test.c:3
 │    ├── volatile, mutations
 │    └── update returning_test
 │         ├── columns: returning_test.a:1 returning_test.b:2 returning_test.c:3 rowid:8!null
 │         ├── fetch columns: returning_test.a:11 returning_test.b:12 returning_test.c:13 rowid:18
 │         ├── update-mapping:
 │         │    └── a_new:21 => returning_test.a:1
 │         ├── return-mapping:
 │         │    ├── a_new:21 => returning_test.a:1
 │         │    ├── returning_test.b:12 => returning_test.b:2
 │         │    ├── returning_test.c:13 => returning_test.c:3
 │         │    └── rowid:18 => rowid:8
 │         ├── volatile, mutations
 │         ├── key: (8)
 │         ├── fd: (8)-->(1-3)
 │         └── project
 │              ├── columns: a_new:21 returning_test.a:11 returning_test.b:12 returning_test.c:13 rowid:18!null
 │              ├── immutable
 │              ├── key: (18)
 │              ├── fd: (18)-->(11-13), (11)~~>(12,13,18), (11)-->(21)
 │              ├── scan returning_test
 │              │    ├── columns: returning_test.a:11 returning_test.b:12 returning_test.c:13 rowid:18!null
 │              │    ├── flags: avoid-full-scan
 │              │    ├── key: (18)
 │              │    └── fd: (18)-->(11-13), (11)~~>(12,13,18)
 │              └── projections
 │                   └── returning_test.a:11 + 1 [as=a_new:21, outer=(11), immutable]
 └── project
      ├── columns: a:25
      ├── with-scan &1
      │    ├── columns: a:22
      │    └── mapping:
      │         └──  returning_test.a:1 => a:22
      └── projections
           └── a:22 [as=a:25, outer=(22)]

# We derive the prune cols for the mutation appropriately so we
# can prune away columns even when the mutation is not under a
# projection. Another rule will fire to add the appropriate
# projection when this happens.
norm
SELECT a FROM [SELECT a, b FROM [UPDATE returning_test SET a = a + 1 RETURNING a, b, c] WHERE a > 1]
----
with &1
 ├── columns: a:25!null
 ├── volatile, mutations
 ├── project
 │    ├── columns: returning_test.a:1 returning_test.b:2 returning_test.c:3
 │    ├── volatile, mutations
 │    └── update returning_test
 │         ├── columns: returning_test.a:1 returning_test.b:2 returning_test.c:3 rowid:8!null
 │         ├── fetch columns: returning_test.a:11 returning_test.b:12 returning_test.c:13 rowid:18
 │         ├── update-mapping:
 │         │    └── a_new:21 => returning_test.a:1
 │         ├── return-mapping:
 │         │    ├── a_new:21 => returning_test.a:1
 │         │    ├── returning_test.b:12 => returning_test.b:2
 │         │    ├── returning_test.c:13 => returning_test.c:3
 │         │    └── rowid:18 => rowid:8
 │         ├── volatile, mutations
 │         ├── key: (8)
 │         ├── fd: (8)-->(1-3)
 │         └── project
 │              ├── columns: a_new:21 returning_test.a:11 returning_test.b:12 returning_test.c:13 rowid:18!null
 │              ├── immutable
 │              ├── key: (18)
 │              ├── fd: (18)-->(11-13), (11)~~>(12,13,18), (11)-->(21)
 │              ├── scan returning_test
 │              │    ├── columns: returning_test.a:11 returning_test.b:12 returning_test.c:13 rowid:18!null
 │              │    ├── flags: avoid-full-scan
 │              │    ├── key: (18)
 │              │    └── fd: (18)-->(11-13), (11)~~>(12,13,18)
 │              └── projections
 │                   └── returning_test.a:11 + 1 [as=a_new:21, outer=(11), immutable]
 └── project
      ├── columns: a:25!null
      ├── select
      │    ├── columns: a:22!null
      │    ├── with-scan &1
      │    │    ├── columns: a:22
      │    │    └── mapping:
      │    │         └──  returning_test.a:1 => a:22
      │    └── filters
      │         └── a:22 > 1 [outer=(22), constraints=(/22: [/2 - ]; tight)]
      └── projections
           └── a:22 [as=a:25, outer=(22)]

norm
SELECT
    *
FROM
    [SELECT a, b FROM returning_test] AS x
    JOIN [SELECT a, b FROM [UPDATE returning_test SET a = a + 1 RETURNING a, b, c] WHERE a > 1]
            AS y ON true
----
with &2
 ├── columns: a:11 b:12 a:37!null b:38
 ├── volatile, mutations
 ├── fd: (11)~~>(12)
 ├── project
 │    ├── columns: returning_test.a:13 returning_test.b:14 returning_test.c:15
 │    ├── volatile, mutations
 │    └── update returning_test
 │         ├── columns: returning_test.a:13 returning_test.b:14 returning_test.c:15 rowid:20!null
 │         ├── fetch columns: returning_test.a:23 returning_test.b:24 returning_test.c:25 rowid:30
 │         ├── update-mapping:
 │         │    └── a_new:33 => returning_test.a:13
 │         ├── return-mapping:
 │         │    ├── a_new:33 => returning_test.a:13
 │         │    ├── returning_test.b:24 => returning_test.b:14
 │         │    ├── returning_test.c:25 => returning_test.c:15
 │         │    └── rowid:30 => rowid:20
 │         ├── volatile, mutations
 │         ├── key: (20)
 │         ├── fd: (20)-->(13-15)
 │         └── project
 │              ├── columns: a_new:33 returning_test.a:23 returning_test.b:24 returning_test.c:25 rowid:30!null
 │              ├── immutable
 │              ├── key: (30)
 │              ├── fd: (30)-->(23-25), (23)~~>(24,25,30), (23)-->(33)
 │              ├── scan returning_test
 │              │    ├── columns: returning_test.a:23 returning_test.b:24 returning_test.c:25 rowid:30!null
 │              │    ├── flags: avoid-full-scan
 │              │    ├── key: (30)
 │              │    └── fd: (30)-->(23-25), (23)~~>(24,25,30)
 │              └── projections
 │                   └── returning_test.a:23 + 1 [as=a_new:33, outer=(23), immutable]
 └── inner-join (cross)
      ├── columns: a:11 b:12 a:37!null b:38
      ├── fd: (11)~~>(12)
      ├── project
      │    ├── columns: a:11 b:12
      │    ├── lax-key: (11,12)
      │    ├── fd: (11)~~>(12)
      │    ├── scan returning_test
      │    │    ├── columns: returning_test.a:1 returning_test.b:2
      │    │    ├── lax-key: (1,2)
      │    │    └── fd: (1)~~>(2)
      │    └── projections
      │         ├── returning_test.a:1 [as=a:11, outer=(1)]
      │         └── returning_test.b:2 [as=b:12, outer=(2)]
      ├── project
      │    ├── columns: a:37!null b:38
      │    ├── select
      │    │    ├── columns: a:34!null b:35
      │    │    ├── with-scan &2
      │    │    │    ├── columns: a:34 b:35
      │    │    │    └── mapping:
      │    │    │         ├──  returning_test.a:13 => a:34
      │    │    │         └──  returning_test.b:14 => b:35
      │    │    └── filters
      │    │         └── a:34 > 1 [outer=(34), constraints=(/34: [/2 - ]; tight)]
      │    └── projections
      │         ├── a:34 [as=a:37, outer=(34)]
      │         └── b:35 [as=b:38, outer=(35)]
      └── filters (true)

# Check if the rule works as desired for other mutations.
norm
INSERT INTO returning_test VALUES (1, 2, 'c') ON CONFLICT (a) DO UPDATE SET a = excluded.a + returning_test.a RETURNING a, b, c
----
project
 ├── columns: a:1 b:2 c:3
 ├── cardinality: [1 - 1]
 ├── volatile, mutations
 ├── key: ()
 ├── fd: ()-->(1-3)
 └── upsert returning_test
      ├── columns: a:1 b:2 c:3 rowid:8!null
      ├── arbiter indexes: returning_test_a_key
      ├── canary column: rowid:23
      ├── fetch columns: a:16 b:17 c:18 rowid:23
      ├── insert-mapping:
      │    ├── column1:11 => a:1
      │    ├── column2:12 => b:2
      │    ├── column3:13 => c:3
      │    ├── d_default:14 => d:4
      │    ├── d_default:14 => e:5
      │    ├── d_default:14 => f:6
      │    ├── d_default:14 => g:7
      │    └── rowid_default:15 => rowid:8
      ├── update-mapping:
      │    └── upsert_a:27 => a:1
      ├── return-mapping:
      │    ├── upsert_a:27 => a:1
      │    ├── upsert_b:28 => b:2
      │    ├── upsert_c:29 => c:3
      │    └── upsert_rowid:34 => rowid:8
      ├── cardinality: [1 - 1]
      ├── volatile, mutations
      ├── key: ()
      ├── fd: ()-->(1-3,8)
      └── project
           ├── columns: upsert_a:27 upsert_b:28 upsert_c:29 upsert_rowid:34 column1:11!null column2:12!null column3:13!null d_default:14 rowid_default:15 a:16 b:17 c:18 rowid:23
           ├── cardinality: [1 - 1]
           ├── volatile
           ├── key: ()
           ├── fd: ()-->(11-18,23,27-29,34)
           ├── left-join (cross)
           │    ├── columns: column1:11!null column2:12!null column3:13!null d_default:14 rowid_default:15 a:16 b:17 c:18 rowid:23
           │    ├── cardinality: [1 - 1]
           │    ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one)
           │    ├── volatile
           │    ├── key: ()
           │    ├── fd: ()-->(11-18,23)
           │    ├── values
           │    │    ├── columns: column1:11!null column2:12!null column3:13!null d_default:14 rowid_default:15
           │    │    ├── cardinality: [1 - 1]
           │    │    ├── volatile
           │    │    ├── key: ()
           │    │    ├── fd: ()-->(11-15)
           │    │    └── (1, 2, 'c', CAST(NULL AS INT8), unique_rowid())
           │    ├── select
           │    │    ├── columns: a:16!null b:17 c:18 rowid:23!null
           │    │    ├── cardinality: [0 - 1]
           │    │    ├── key: ()
           │    │    ├── fd: ()-->(16-18,23)
           │    │    ├── scan returning_test
           │    │    │    ├── columns: a:16 b:17 c:18 rowid:23!null
           │    │    │    ├── flags: avoid-full-scan
           │    │    │    ├── key: (23)
           │    │    │    └── fd: (23)-->(16-18), (16)~~>(17,18,23)
           │    │    └── filters
           │    │         └── a:16 = 1 [outer=(16), constraints=(/16: [/1 - /1]; tight), fd=()-->(16)]
           │    └── filters (true)
           └── projections
                ├── CASE WHEN rowid:23 IS NULL THEN column1:11 ELSE column1:11 + a:16 END [as=upsert_a:27, outer=(11,16,23), immutable]
                ├── CASE WHEN rowid:23 IS NULL THEN column2:12 ELSE b:17 END [as=upsert_b:28, outer=(12,17,23)]
                ├── CASE WHEN rowid:23 IS NULL THEN column3:13 ELSE c:18 END [as=upsert_c:29, outer=(13,18,23)]
                └── CASE WHEN rowid:23 IS NULL THEN rowid_default:15 ELSE rowid:23 END [as=upsert_rowid:34, outer=(15,23)]

norm
DELETE FROM returning_test WHERE a < b + d RETURNING a, b, d
----
project
 ├── columns: a:1!null b:2 d:4
 ├── volatile, mutations
 ├── key: (1)
 ├── fd: (1)-->(2,4)
 └── delete returning_test
      ├── columns: a:1!null b:2 d:4 rowid:8!null
      ├── fetch columns: a:11 b:12 d:14 rowid:18
      ├── return-mapping:
      │    ├── a:11 => a:1
      │    ├── b:12 => b:2
      │    ├── d:14 => d:4
      │    └── rowid:18 => rowid:8
      ├── volatile, mutations
      ├── key: (8)
      ├── fd: (8)-->(1,2,4), (1)-->(2,4,8)
      └── select
           ├── columns: a:11!null b:12 d:14 rowid:18!null
           ├── immutable
           ├── key: (18)
           ├── fd: (18)-->(11,12,14), (11)-->(12,14,18)
           ├── scan returning_test
           │    ├── columns: a:11 b:12 d:14 rowid:18!null
           │    ├── flags: avoid-full-scan
           │    ├── key: (18)
           │    └── fd: (18)-->(11,12,14), (11)~~>(12,14,18)
           └── filters
                └── a:11 < (b:12 + d:14) [outer=(11,12,14), immutable, constraints=(/11: (/NULL - ])]

norm
UPSERT INTO returning_test (a, b, c) VALUES (1, 2, 'c') RETURNING a, b, c, d
----
project
 ├── columns: a:1!null b:2!null c:3!null d:4
 ├── cardinality: [1 - 1]
 ├── volatile, mutations
 ├── key: ()
 ├── fd: ()-->(1-4)
 └── upsert returning_test
      ├── columns: a:1!null b:2!null c:3!null d:4 rowid:8!null
      ├── arbiter indexes: returning_test_pkey
      ├── canary column: rowid:23
      ├── fetch columns: a:16 b:17 c:18 d:19 rowid:23
      ├── insert-mapping:
      │    ├── column1:11 => a:1
      │    ├── column2:12 => b:2
      │    ├── column3:13 => c:3
      │    ├── d_default:14 => d:4
      │    ├── d_default:14 => e:5
      │    ├── d_default:14 => f:6
      │    ├── d_default:14 => g:7
      │    └── rowid_default:15 => rowid:8
      ├── update-mapping:
      │    ├── column1:11 => a:1
      │    ├── column2:12 => b:2
      │    └── column3:13 => c:3
      ├── return-mapping:
      │    ├── column1:11 => a:1
      │    ├── column2:12 => b:2
      │    ├── column3:13 => c:3
      │    ├── upsert_d:26 => d:4
      │    └── upsert_rowid:30 => rowid:8
      ├── cardinality: [1 - 1]
      ├── volatile, mutations
      ├── key: ()
      ├── fd: ()-->(1-4,8)
      └── project
           ├── columns: upsert_d:26 upsert_rowid:30 column1:11!null column2:12!null column3:13!null d_default:14 rowid_default:15 a:16 b:17 c:18 d:19 rowid:23
           ├── cardinality: [1 - 1]
           ├── volatile
           ├── key: ()
           ├── fd: ()-->(11-19,23,26,30)
           ├── left-join (hash)
           │    ├── columns: column1:11!null column2:12!null column3:13!null d_default:14 rowid_default:15 a:16 b:17 c:18 d:19 rowid:23
           │    ├── cardinality: [1 - 1]
           │    ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-one)
           │    ├── volatile
           │    ├── key: ()
           │    ├── fd: ()-->(11-19,23)
           │    ├── values
           │    │    ├── columns: column1:11!null column2:12!null column3:13!null d_default:14 rowid_default:15
           │    │    ├── cardinality: [1 - 1]
           │    │    ├── volatile
           │    │    ├── key: ()
           │    │    ├── fd: ()-->(11-15)
           │    │    └── (1, 2, 'c', CAST(NULL AS INT8), unique_rowid())
           │    ├── scan returning_test
           │    │    ├── columns: a:16 b:17 c:18 d:19 rowid:23!null
           │    │    ├── flags: avoid-full-scan
           │    │    ├── key: (23)
           │    │    └── fd: (23)-->(16-19), (16)~~>(17-19,23)
           │    └── filters
           │         └── rowid_default:15 = rowid:23 [outer=(15,23), constraints=(/15: (/NULL - ]; /23: (/NULL - ]), fd=(15)==(23), (23)==(15)]
           └── projections
                ├── CASE WHEN rowid:23 IS NULL THEN d_default:14 ELSE d:19 END [as=upsert_d:26, outer=(14,19,23)]
                └── CASE WHEN rowid:23 IS NULL THEN rowid_default:15 ELSE rowid:23 END [as=upsert_rowid:30, outer=(15,23)]

# Make sure the passthrough columns of an UPDATE ... FROM query are pruned.
norm
UPDATE abcde
SET
  b=family.b, c = family.c
FROM
  family
WHERE
  abcde.a=family.a
RETURNING
  abcde.a, family.b, family.c
----
update abcde
 ├── columns: a:1!null b:16 c:17
 ├── fetch columns: abcde.a:8 abcde.b:9 abcde.c:10 abcde.d:11 abcde.e:12
 ├── passthrough columns: "family".b:16 "family".c:17
 ├── update-mapping:
 │    ├── "family".b:16 => abcde.b:2
 │    └── "family".c:17 => abcde.c:3
 ├── return-mapping:
 │    └── abcde.a:8 => abcde.a:1
 ├── volatile, mutations
 ├── key: (1)
 ├── fd: (1)-->(16,17)
 └── inner-join (hash)
      ├── columns: abcde.a:8!null abcde.b:9 abcde.c:10 abcde.d:11 abcde.e:12 "family".a:15!null "family".b:16 "family".c:17
      ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one)
      ├── key: (15)
      ├── fd: (8)-->(9-12), (9,10)~~>(8,11,12), (15)-->(16,17), (8)==(15), (15)==(8)
      ├── scan abcde
      │    ├── columns: abcde.a:8!null abcde.b:9 abcde.c:10 abcde.d:11 abcde.e:12
      │    ├── flags: avoid-full-scan
      │    ├── key: (8)
      │    └── fd: (8)-->(9-12), (9,10)~~>(8,11,12)
      ├── scan family
      │    ├── columns: "family".a:15!null "family".b:16 "family".c:17
      │    ├── key: (15)
      │    └── fd: (15)-->(16,17)
      └── filters
           └── abcde.a:8 = "family".a:15 [outer=(8,15), constraints=(/8: (/NULL - ]; /15: (/NULL - ]), fd=(8)==(15), (15)==(8)]

# --------------------------------------------------
# PruneSemiAntiJoinRightCols
# --------------------------------------------------

# We should only see the `a` column scanned for family.
norm expect=PruneSemiAntiJoinRightCols
SELECT a, b, c FROM abcde WHERE EXISTS (SELECT * FROM family WHERE abcde.a=family.a)
----
semi-join (hash)
 ├── columns: a:1!null b:2 c:3
 ├── key: (1)
 ├── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── scan abcde
 │    ├── columns: abcde.a:1!null abcde.b:2 abcde.c:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── scan family
 │    ├── columns: "family".a:8!null
 │    └── key: (8)
 └── filters
      └── abcde.a:1 = "family".a:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# We should see the `a`, `b` and `c` columns scanned for family.
norm expect=PruneSemiAntiJoinRightCols
SELECT a, b, c FROM abcde WHERE EXISTS (SELECT * FROM family WHERE abcde.a=family.a AND abcde.b > family.b + family.c)
----
semi-join (hash)
 ├── columns: a:1!null b:2 c:3
 ├── immutable
 ├── key: (1)
 ├── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── scan abcde
 │    ├── columns: abcde.a:1!null abcde.b:2 abcde.c:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── project
 │    ├── columns: column16:16 "family".a:8!null
 │    ├── immutable
 │    ├── key: (8)
 │    ├── fd: (8)-->(16)
 │    ├── scan family
 │    │    ├── columns: "family".a:8!null "family".b:9 "family".c:10
 │    │    ├── key: (8)
 │    │    └── fd: (8)-->(9,10)
 │    └── projections
 │         └── "family".b:9 + "family".c:10 [as=column16:16, outer=(9,10), immutable]
 └── filters
      ├── abcde.a:1 = "family".a:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]
      └── abcde.b:2 > column16:16 [outer=(2,16), constraints=(/2: (/NULL - ]; /16: (/NULL - ])]

norm expect=PruneSemiAntiJoinRightCols
SELECT a, b, c FROM abcde WHERE NOT EXISTS (SELECT * FROM family WHERE abcde.a=family.a)
----
anti-join (hash)
 ├── columns: a:1!null b:2 c:3
 ├── key: (1)
 ├── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── scan abcde
 │    ├── columns: abcde.a:1!null abcde.b:2 abcde.c:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── scan family
 │    ├── columns: "family".a:8!null
 │    └── key: (8)
 └── filters
      └── abcde.a:1 = "family".a:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Test using multi-level nesting so we don't decorrelate the semi-join.
norm expect=PruneSemiAntiJoinRightCols disable=(TryRemapJoinOuterColsRight,TryRemapSelectOuterCols)
SELECT
    a, b, c
FROM
    abcde
WHERE
    EXISTS(
        SELECT
            *
        FROM
            "family"
        WHERE
            abcde.a = "family".a AND EXISTS(SELECT * FROM a WHERE abcde.a = a.k)
    )
----
semi-join-apply
 ├── columns: a:1!null b:2 c:3
 ├── key: (1)
 ├── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── scan abcde
 │    ├── columns: abcde.a:1!null abcde.b:2 abcde.c:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── semi-join (cross)
 │    ├── columns: "family".a:8!null
 │    ├── outer: (1)
 │    ├── key: (8)
 │    ├── scan family
 │    │    ├── columns: "family".a:8!null
 │    │    └── key: (8)
 │    ├── scan a
 │    │    ├── columns: k:15!null
 │    │    └── key: (15)
 │    └── filters
 │         └── abcde.a:1 = k:15 [outer=(1,15), constraints=(/1: (/NULL - ]; /15: (/NULL - ]), fd=(1)==(15), (15)==(1)]
 └── filters
      └── abcde.a:1 = "family".a:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

# Test using multi-level nesting so we don't decorrelate the anti-join.
norm expect=PruneSemiAntiJoinRightCols disable=(TryRemapJoinOuterColsRight,TryRemapSelectOuterCols)
SELECT
    a, b, c
FROM
    abcde
WHERE
    NOT EXISTS(
        SELECT
            *
        FROM
            "family"
        WHERE
            abcde.a = "family".a AND EXISTS(SELECT * FROM a WHERE abcde.a = a.k)
    )
----
anti-join-apply
 ├── columns: a:1!null b:2 c:3
 ├── key: (1)
 ├── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── scan abcde
 │    ├── columns: abcde.a:1!null abcde.b:2 abcde.c:3
 │    ├── key: (1)
 │    └── fd: (1)-->(2,3), (2,3)~~>(1)
 ├── semi-join (cross)
 │    ├── columns: "family".a:8!null
 │    ├── outer: (1)
 │    ├── key: (8)
 │    ├── scan family
 │    │    ├── columns: "family".a:8!null
 │    │    └── key: (8)
 │    ├── scan a
 │    │    ├── columns: k:15!null
 │    │    └── key: (15)
 │    └── filters
 │         └── abcde.a:1 = k:15 [outer=(1,15), constraints=(/1: (/NULL - ]; /15: (/NULL - ]), fd=(1)==(15), (15)==(1)]
 └── filters
      └── abcde.a:1 = "family".a:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)]

norm disable=InlineWith expect=PruneWithScanCols
WITH foo AS (SELECT * FROM a)
  SELECT i FROM foo
----
with &1 (foo)
 ├── columns: i:8
 ├── scan a
 │    ├── columns: a.k:1!null a.i:2 a.f:3 a.s:4
 │    ├── key: (1)
 │    └── fd: (1)-->(2-4)
 └── with-scan &1 (foo)
      ├── columns: i:8
      └── mapping:
           └──  a.i:2 => i:8

norm disable=InlineWith format=show-all expect=PruneWithCols
WITH foo AS (SELECT * FROM a)
  SELECT i FROM (SELECT i, 1 AS y FROM foo) ORDER BY y
----
with &1 (foo)
 ├── columns: i:8(int)
 ├── stats: [rows=1000]
 ├── cost: 1108.86
 ├── prune: (8)
 ├── scan t.public.a
 │    ├── columns: t.public.a.k:1(int!null) t.public.a.i:2(int) t.public.a.f:3(float) t.public.a.s:4(string)
 │    ├── stats: [rows=1000]
 │    ├── cost: 1108.82
 │    ├── key: (1)
 │    ├── fd: (1)-->(2-4)
 │    ├── prune: (1-4)
 │    └── interesting orderings: (+1)
 └── with-scan &1 (foo)
      ├── columns: i:8(int)
      ├── mapping:
      │    └──  t.public.a.i:2(int) => i:8(int)
      ├── stats: [rows=1000]
      ├── cost: 0.02
      ├── prune: (8)
      └── cte-uses
           └── &1: count=1 used-columns=(2)

# --------------------------------------------------
# PruneUnionAllCols
# --------------------------------------------------

norm expect=PruneUnionAllCols
SELECT a FROM (
  SELECT a, b FROM abcde
  UNION ALL
  SELECT * FROM xy
)
----
union-all
 ├── columns: a:12!null
 ├── left columns: abcde.a:1
 ├── right columns: x:8
 ├── scan abcde
 │    ├── columns: abcde.a:1!null
 │    └── key: (1)
 └── scan xy
      ├── columns: x:8!null
      └── key: (8)

norm expect=PruneUnionAllCols
SELECT count(*) FROM (
  SELECT a, b FROM abcde
  UNION ALL
  SELECT * FROM xy
)
----
scalar-group-by
 ├── columns: count:14!null
 ├── cardinality: [1 - 1]
 ├── key: ()
 ├── fd: ()-->(14)
 ├── union-all
 │    ├── scan abcde
 │    └── scan xy
 └── aggregations
      └── count-rows [as=count_rows:14]

norm expect=PruneUnionAllCols
SELECT 1 FROM (SELECT a FROM abcde WHERE a > 3 UNION ALL SELECT a FROM abcde)
----
project
 ├── columns: "?column?":16!null
 ├── fd: ()-->(16)
 ├── union-all
 │    ├── project
 │    │    └── select
 │    │         ├── columns: abcde.a:1!null
 │    │         ├── key: (1)
 │    │         ├── scan abcde
 │    │         │    ├── columns: abcde.a:1!null
 │    │         │    └── key: (1)
 │    │         └── filters
 │    │              └── abcde.a:1 > 3 [outer=(1), constraints=(/1: [/4 - ]; tight)]
 │    └── scan abcde
 └── projections
      └── 1 [as="?column?":16]

norm expect=PruneUnionAllCols
SELECT 1 FROM a INNER JOIN (SELECT a, b FROM abcde UNION ALL SELECT * from xy) AS b ON a.i=b.b
----
project
 ├── columns: "?column?":20!null
 ├── fd: ()-->(20)
 ├── inner-join (hash)
 │    ├── columns: i:2!null b:19!null
 │    ├── fd: (2)==(19), (19)==(2)
 │    ├── scan a
 │    │    └── columns: i:2
 │    ├── union-all
 │    │    ├── columns: b:19
 │    │    ├── left columns: abcde.b:8
 │    │    ├── right columns: y:15
 │    │    ├── scan abcde
 │    │    │    └── columns: abcde.b:8
 │    │    └── scan xy
 │    │         └── columns: y:15
 │    └── filters
 │         └── i:2 = b:19 [outer=(2,19), constraints=(/2: (/NULL - ]; /19: (/NULL - ]), fd=(2)==(19), (19)==(2)]
 └── projections
      └── 1 [as="?column?":20]

# Test that even when one side of the UnionAll input has a greater
# number of prunable columns than the other (neither the top-level
# Project nor the UnionAll need any input columns, but the right-hand
# Scan has a filter and cannot prune column x), a Project is added to
# ensure that both inputs to the UnionAll have the same number of
# columns.
norm expect=PruneUnionAllCols
SELECT 1 FROM (
  SELECT a, b FROM abcde
  UNION ALL
  SELECT * from xy WHERE x=1
)
----
project
 ├── columns: "?column?":14!null
 ├── fd: ()-->(14)
 ├── union-all
 │    ├── scan abcde
 │    └── project
 │         ├── cardinality: [0 - 1]
 │         ├── key: ()
 │         └── select
 │              ├── columns: x:8!null
 │              ├── cardinality: [0 - 1]
 │              ├── key: ()
 │              ├── fd: ()-->(8)
 │              ├── scan xy
 │              │    ├── columns: x:8!null
 │              │    └── key: (8)
 │              └── filters
 │                   └── x:8 = 1 [outer=(8), constraints=(/8: [/1 - /1]; tight), fd=()-->(8)]
 └── projections
      └── 1 [as="?column?":14]

# Regression test for #41772.

exec-ddl
CREATE TABLE table41772 ()
----

norm
WITH
    a AS (SELECT NULL FROM table41772),
    b
        AS (
            SELECT
                *
            FROM
                (VALUES ((SELECT true FROM table41772), ARRAY[0, 0, 0, 0:::OID]))
                    AS l (u, v)
            UNION ALL
                SELECT
                    *
                FROM
                    (VALUES (NULL, NULL), (false, ARRAY[0:::OID]))
                        AS r (x, y)
        )
SELECT
    NULL
FROM
    a, b
WHERE
    b.u
----
project
 ├── columns: "?column?":18
 ├── fd: ()-->(18)
 ├── inner-join (cross)
 │    ├── columns: u:13!null
 │    ├── scan table41772
 │    ├── union-all
 │    │    ├── columns: u:13!null
 │    │    ├── left columns: column1:9
 │    │    ├── right columns: column1:11
 │    │    ├── cardinality: [0 - 3]
 │    │    ├── select
 │    │    │    ├── columns: column1:9!null
 │    │    │    ├── cardinality: [0 - 1]
 │    │    │    ├── key: ()
 │    │    │    ├── fd: ()-->(9)
 │    │    │    ├── values
 │    │    │    │    ├── columns: column1:9
 │    │    │    │    ├── cardinality: [1 - 1]
 │    │    │    │    ├── key: ()
 │    │    │    │    ├── fd: ()-->(9)
 │    │    │    │    └── tuple
 │    │    │    │         └── subquery
 │    │    │    │              └── max1-row
 │    │    │    │                   ├── columns: bool:8!null
 │    │    │    │                   ├── error: "more than one row returned by a subquery used as an expression"
 │    │    │    │                   ├── cardinality: [0 - 1]
 │    │    │    │                   ├── key: ()
 │    │    │    │                   ├── fd: ()-->(8)
 │    │    │    │                   └── project
 │    │    │    │                        ├── columns: bool:8!null
 │    │    │    │                        ├── fd: ()-->(8)
 │    │    │    │                        ├── scan table41772
 │    │    │    │                        └── projections
 │    │    │    │                             └── true [as=bool:8]
 │    │    │    └── filters
 │    │    │         └── column1:9 [outer=(9), constraints=(/9: [/true - /true]; tight), fd=()-->(9)]
 │    │    └── select
 │    │         ├── columns: column1:11!null
 │    │         ├── cardinality: [0 - 2]
 │    │         ├── fd: ()-->(11)
 │    │         ├── values
 │    │         │    ├── columns: column1:11
 │    │         │    ├── cardinality: [2 - 2]
 │    │         │    ├── (NULL,)
 │    │         │    └── (false,)
 │    │         └── filters
 │    │              └── column1:11 [outer=(11), constraints=(/11: [/true - /true]; tight), fd=()-->(11)]
 │    └── filters (true)
 └── projections
      └── NULL [as="?column?":18]

# Regression test for #86308.
exec-ddl
CREATE TABLE table86308 (
  col3_0 INET NOT NULL,
  col3_1 INT4 NOT NULL,
  col3_2 TIMETZ NOT NULL,
  col3_3 FLOAT4 NOT NULL,
  col3_4 BIT(17) NOT NULL,
  col3_5 GEOGRAPHY NOT NULL,
  col3_6 BIT(22) NOT NULL,
  col3_7 STRING NOT NULL,
  col3_8 UUID NOT NULL,
  col3_9 FLOAT8 NOT NULL AS (col3_3 + 0.7530620098114014:::FLOAT8) STORED,
  col3_10 STRING NOT NULL AS (lower(CAST(col3_0 AS STRING))) STORED,
  col3_11 FLOAT8 NOT NULL AS (col3_3 + 0.8790965676307678:::FLOAT8) VIRTUAL,
  col3_12 STRING NOT NULL AS (lower(CAST(col3_0 AS STRING))) VIRTUAL,
  col3_13 STRING NOT NULL AS (lower(CAST(col3_6 AS STRING))) STORED,
  col3_14 FLOAT8 NOT NULL AS (col3_3 + (-0.27059364318847656):::FLOAT8) STORED,
  col3_15 STRING NOT NULL AS (lower(CAST(col3_5 AS STRING))) STORED,
  rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(),
  CONSTRAINT table3_pkey PRIMARY KEY (rowid ASC),
  UNIQUE INDEX table3_col3_4_col3_11_col3_1_col3_0_col3_3_key (col3_4 DESC, col3_11 ASC, col3_1 DESC, col3_0 ASC, col3_3 DESC) STORING (col3_5, col3_8, col3_9, col3_10, col3_13) WHERE ((((col3_9 = 5e-324:::FLOAT8) OR (col3_1 = (-32768):::INT8)) OR (col3_15 = '':::STRING)) AND (col3_3 > 3.4028234663852886e+38:::FLOAT8)) AND (col3_12 != '""':::STRING)
);
----

# Before the fix, PruneGroupByCols would fire to push a Project below the
# GroupBy. Then, since PruneWindowInputCols was disabled,
# EliminateGroupByProject would remove the Project and the cycle would repeat.
norm disable=PruneWindowInputCols format=hide-all
SELECT concat_agg(tab_1739.col3_7::STRING ORDER BY tab_1739.col3_7 DESC)::STRING AS col_4664
FROM table86308@[0] AS tab_1739 GROUP BY tab_1739.col3_7 HAVING bool_and(false::BOOL)::BOOL
----
project
 └── select
      ├── group-by (hash)
      │    ├── project
      │    │    ├── scan table86308
      │    │    │    ├── computed column expressions
      │    │    │    │    ├── col3_9
      │    │    │    │    │    └── col3_3 + 0.7530620098114014
      │    │    │    │    ├── col3_10
      │    │    │    │    │    └── lower(col3_0::STRING)
      │    │    │    │    ├── col3_11
      │    │    │    │    │    └── col3_3 + 0.8790965676307678
      │    │    │    │    ├── col3_12
      │    │    │    │    │    └── lower(col3_0::STRING)
      │    │    │    │    ├── col3_13
      │    │    │    │    │    └── lower(col3_6::STRING)
      │    │    │    │    ├── col3_14
      │    │    │    │    │    └── col3_3 + -0.27059364318847656
      │    │    │    │    └── col3_15
      │    │    │    │         └── lower(col3_5::STRING)
      │    │    │    └── partial index predicates
      │    │    │         └── table3_col3_4_col3_11_col3_1_col3_0_col3_3_key: filters
      │    │    │              ├── ((col3_9 = 5e-324) OR (col3_1 = -32768)) OR (col3_15 = '')
      │    │    │              ├── col3_3 > 3.4028234663852886e+38
      │    │    │              └── lower(col3_0::STRING) != '""'
      │    │    └── projections
      │    │         └── false
      │    └── aggregations
      │         ├── concat-agg
      │         │    └── col3_7
      │         └── bool-and
      │              └── column21
      └── filters
           └── bool_and

exec-ddl
CREATE TABLE p100478 (
  region STRING NOT NULL,
  id INT,
  PRIMARY KEY (region, id),
  UNIQUE INDEX p_id_idx (id)
)
----

exec-ddl
CREATE TABLE c100478 (
  region STRING NOT NULL,
  id INT PRIMARY KEY,
  p_id INT NOT NULL,
  FOREIGN KEY (region, p_id) REFERENCES p100478 (region, id)
)
----

# A join which doesn't prune columns which could potentially appear in derived
# ON clause conditions should not result in infinite rule recursion.
norm expect=(EliminateGroupByProject,PruneJoinLeftCols,PruneJoinRightCols) disable=(EliminateJoinUnderProjectLeft,EliminateJoinUnderProjectRight,EliminateJoinUnderGroupByLeft,EliminateJoinUnderGroupByRight)
SELECT p.id FROM p100478 p JOIN c100478 ON p_id = p.id GROUP BY p.id
----
distinct-on
 ├── columns: id:2!null
 ├── grouping columns: p.id:2!null
 ├── key: (2)
 └── inner-join (hash)
      ├── columns: p.region:1!null p.id:2!null c100478.region:5!null p_id:7!null
      ├── multiplicity: left-rows(zero-or-more), right-rows(exactly-one)
      ├── fd: (2)-->(1), (2)==(7), (7)==(2)
      ├── scan p100478 [as=p]
      │    ├── columns: p.region:1!null p.id:2!null
      │    ├── key: (2)
      │    └── fd: (2)-->(1)
      ├── scan c100478
      │    └── columns: c100478.region:5!null p_id:7!null
      └── filters
           └── p_id:7 = p.id:2 [outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)]
