# MDEV-41228: Item copy types and where they are used

There are four ways to copy an Item. Each caller uses the one whose
guarantees match what it then does with the copy.

| Copy | What it copies | Children |
|---|---|---|
| `surface_copy_with_checks` | one node | shared with the original |
| `same_select_copy_with_checks` | operator nodes (`Item_func`, `Item_cond`, `Item_row`) | leaves shared; comparators and IN arrays rebuilt |
| `new_select_copy_with_checks(thd, sel)` | an outer reference to the original, valid in a select nested in `sel` | never copied; table-dependent items are referenced, not copied |
| `pushdown_copy_with_checks` | every node | none shared; may refuse (nullptr) |

---

## surface_copy

Builds one new node with `get_item_copy<T>`. A typeid check (debug only)
makes sure the class overrides it.

**Call sites**
- Inside the other three copy methods, to copy the current node after its
  children have been dealt with.
- `sql_table.cc` ~9520, ALTER TABLE keeping a PERIOD constraint: the new
  `Virtual_column_info` needs its own top node for its expression. The
  expression below that node is not changed, so it can be shared.

**Why only this:** no caller outside the copy methods needs the children
copied. Callers that do need them use one of the three types below.

---

## same_select_copy

The copy is used in the **same** SELECT_LEX as the original, so any fixed
item evaluates the same in either tree. Sharing a whole subtree such as
`col1 + col2` would therefore be safe for evaluation. Operator nodes are
copied for a different reason: they own the `args[]` array.
- A caller rewrites the copy by writing into a node's `args[]`. The
  casefold rewrite does `cl->arguments()[0]= tmp`.
- Later passes rewrite in place too: `transform()` and
  `propagate_equal_fields()` replace `args[i]` with a new item.
- A rewrite replaces the child in its parent's slot. It never changes the
  child itself.

So every node with an `args[]` (`Item_func`, `Item_cond`, `Item_row`) gets
its own array. Then no rewrite through one tree can reach the other.
Leaves have no `args[]`, so nothing changes them through a slot, and they
are shared.

A shared leaf is one object in two places. Its per-object state (`marker`,
`item_equal` on an `Item_field`, cleanup) serves both uses. See the split
left side below for the one case where both places are in the same tree.

Copied nodes are cleaned up and the caller re-fixes them. Some per-node
state is detached and rebuilt rather than shared:
- comparators (`Item_bool_rowready_func2`);
- the IN array (`Item_func_in`);
- `Item_func_case_simple`.

Functions whose state can't be copied (regex, UDF, SP, etc.) are shared
as a whole instead.

**Call sites**
- `opt_rewrite_remove_casefold.cc:146`,
  `Item_func_in::varchar_upper_cmp_transformer`.
  - What it does: rewrites `UPPER(col) IN (...)` into `col IN (...)` on a
    copy, and keeps the original when that fails.
  - Why a copy at all: the original must stay intact, because it is
    returned on failure and the PS tree must not change. The copy's IN
    node gets its own `args[]` and array.
  - Why not pushdown_copy: it refuses subqueries.
    `upper(a) in ((select max(c) from t2),'A-2')` would lose its range
    access (tested in `main.sargable_casefold`).
  - Why not new_select_copy: the copy would refer to its own select as an
    outer one.
- `opt_split.cc:656`, the left side of the split equality,
  `producing_item` of the derived column.
  - The equality is added to the derived table's own WHERE, which is the
    same select as `producing_item`.
  - The equality is fixed separately from the select list, so its operator
    nodes need to be its own. The columns can stay shared.
  - Why not pushdown_copy: it would refuse expressions that contain
    subqueries, and split materialization would silently be lost.
  - Why not new_select_copy: same as above, wrong select.
  - **Side effect: one item in two places.** When `producing_item` is a
    plain column, `same_select_copy` returns the column itself. The derived
    table's select-list `Item_field` is then also an argument of the
    injected WHERE equality.
    - Normally each item object has one position in the tree, because the
      parser creates one `Item_field` per occurrence.
    - Here, per-object state is shared by both uses: `marker`,
      `item_equal`, and cleanup, which runs once for the object.
    - The suites pass and no concrete failure is known, but it is a new
      situation. Before this series, `deep_copy` gave the equality its own
      `Item_field`.

---

## new_select_copy

The copy is used in a select **nested in** `item_select`. It must read the
outer select's **current row**, so it becomes an outer reference
(`depended_from = item_select`, `OUTER_REF_TABLE_BIT` in `used_tables`):
- `Item_field`: copied, with `depended_from` set and `item_equal` cleared.
- `Item_direct_view_ref`: copied, and its new ref slot holds
  `new_select_copy` of `*ref`.
- Any other item that depends on a table: wrapped in an `Item_direct_ref`
  pointing at the original.
- An item that depends on no table: shared.

It never refuses.

**Call site**
- `opt_split.cc:662`, the right side of the split equality,
  `key_field->val`, an expression of the outer select.
  - The derived table is re-filled for each outer row, so the right side
    must see each new outer row.
  - Why not pushdown_copy:
    1. A copy of an `Item_cache` (e.g. one owned by `Item_in_optimizer`)
       is a second cache that nothing refreshes. It latches the first
       row's value and gives wrong results (tested in
       `main.derived_split_innodb`).
    2. The copy would not be marked as an outer reference. The old code
       needed a `set_fields_as_dependent_processor` walk plus
       `update_used_tables()` to fix that up afterwards.
    3. It refuses subqueries, caches and parameters, which would silently
       switch split materialization off.
  - Why not same_select_copy: the copy's columns would not be marked as
    outer references.

---

## pushdown_copy

The copy is moved into **another** select and **re-bound** there. The
pushdown code:
1. replaces the columns with what the target select computes them from;
2. walks the result with `cleanup_excluding_const_fields_processor`
   (`Item_field::cleanup()` sets `field= 0`);
3. calls `fix_fields()` in the target select.

So **no node that step 2 can reach may be shared** with the original, or
step 2 would break the original. The one shared node is the `example` of an
`Item_cache` over an aggregate or window function. Step 2 never reaches it:
every transformer in step 1 replaces an `Item_cache` with a basic constant
(`convert_to_basic_const_item()`), or fails and the condition is dropped.
`main.derived_cond_pushdown` (MDEV-10882) hits this case. Every node is copied, arguments before the node itself, so a
refusal leaves no half-made copy behind. A refusal (nullptr) is acceptable:
the condition simply is not pushed down.

**Call sites** (all are condition pushdown)
- `item.cc` ~8527, `Item::build_pushable_cond`: extracts the part of a
  condition that can be pushed.
- `item_cmpfunc.cc` ~8057/8086, `Item_equal::create_pushable_equalities`:
  builds `a = b` from a multiple equality.
- `sql_derived.cc:1641`, `pushdown_cond_for_derived`: the condition pushed
  into a derived table or view.
- `sql_lex.cc:10293`, `st_select_lex::build_cond_for_grouping_fields`:
  HAVING to WHERE.
- Transformers that put select-list expressions into the pushed condition:
  - `item.cc` ~8659/8687, `derived_field_transformer_for_where`
    (`Item_field`, `Item_direct_view_ref`);
  - `item.cc` ~8700/8734, `grouping_field_transformer_for_where`;
  - `opt_subselect.cc` ~7250/7263, `in_subq_field_transformer_for_where`.

  These copies stay in the same select, but the cleanup walk in step 2
  would reach them. A shared select-list item would be cleaned up under
  the select list.

**Why not same_select_copy:** it shares leaves, and the cleanup walk breaks
those for the original.

**Why not new_select_copy:** pushdown re-binds the columns by
transformation. It does not refer back to the original select.

**Known fix:** `Item_func_interval` used to keep a second pointer to its
`Item_row` besides `args[0]`. A copy got new args but kept the old pointer.
It now reads the row through `args[0]` alone (tested in
`main.derived_cond_pushdown`).
