Skip to content

opt: expand immediate stars in row constructors - #174773

Open
Alignyx wants to merge 1 commit into
cockroachdb:masterfrom
Alignyx:fix-115150-row-star-expansion
Open

opt: expand immediate stars in row constructors#174773
Alignyx wants to merge 1 commit into
cockroachdb:masterfrom
Alignyx:fix-115150-row-star-expansion

Conversation

@Alignyx

@Alignyx Alignyx commented Sep 6, 2026

Copy link
Copy Markdown

ROW(table.*) introduced an extra record layer, so array_agg(ROW(table.*)) returned arrays whose elements had the wrong tuple shape. This PR expands immediate star arguments into the enclosing row constructor's fields, matching PostgreSQL's row-constructor semantics.

Fixes #115150.

Problem and reproduction

CREATE TABLE xy (x INT PRIMARY KEY, y INT);
INSERT INTO xy VALUES (1, 2), (100, 100);

SELECT array_agg(ROW(xy.*) ORDER BY x) FROM xy;

Before this change, the array contains one-field records whose sole field is another record:

{"(\"(1,2)\")","(\"(100,100)\")"}

After this change, it contains the two-field records requested by the query:

{"(1,2)","(100,100)"}

The aggregate's ORDER BY makes element order explicit for this reproduction; the defect concerns tuple shape, not ordering. The result should match array_agg(ROW(x, y) ORDER BY x) and array_agg(xy.* ORDER BY x). Likewise, ROW(ROW(xy.*)) should preserve its explicit outer row while expanding the inner row's star exactly once.

Cause and change

During optbuilder's expression walk, a star encountered as an ordinary expression is converted into a tuple of its expanded columns. That is appropriate for a whole-row argument such as array_agg(xy.*). Previously, the same handling ran for a star directly inside a row constructor: the child became a tuple, and the enclosing ROW(...) then wrapped that tuple as one field. The extra nesting was already present in the optimizer expression before aggregate execution.

scope.VisitPre now handles *tree.Tuple before the generic child walk:

  • Normalize unresolved child names when recognizing immediate table/composite stars.
  • Keep the original tuple unchanged when no immediate star needs expansion.
  • Expand AllColumnsSelector and TupleStar children with the existing expandStar resolver and append each expanded field to the enclosing expression list.
  • Copy the original tuple by value, replace only its expression list, and continue ordinary traversal of the resulting children.

Why this preserves the intended semantics

Expansion is restricted to syntax that directly requests the fields of a row. It does not flatten arbitrary tuple-valued expressions: ROW(xy) still contains one whole-row field, and explicit nested ROW(...) layers remain. Non-star children are retained for normal name resolution. Reusing expandStar preserves the existing field ordering, visibility rules (including hidden columns), and composite-expression resolution. Copying the tuple preserves its labels and other metadata. Ordinary function-star arguments continue through their existing path.

This fixes the shape at row construction, so aggregate execution and array formatting require no special cases or changes.

Validation

The branch is based directly on upstream 8812064a015d2faf99d3fc7e15880f94042954b0 and contains only the product change and its native regression file.

  • Added 11 SQL cases to TestBuilder/row-star-expansion, including the ordered array_agg(ROW(xy.*)) reproduction, immediate stars, mixed/repeated fields, aliases, composite-expression stars, deliberate nesting, and bare whole-row controls.
  • Generated the expected plans from executed explicit-field reference queries, then froze the fixture. The identical fixture fails on the exact upstream baseline and passes with this patch.
  • Built and ran the complete //pkg/sql/opt/optbuilder:optbuilder_test executable on this branch: 109 tests and subtests passed, with no failures or skips.
  • Executed 22 SQL scenarios with vectorize=on and vectorize=off, twice per setting: 20/88 observations passed on the upstream baseline and 88/88 passed with the patch. Comparisons retain complete ordered array values, duplicates, NULLs, and tuple nesting. All reference values and previously correct results were unchanged.
  • crlfmt -fast -tab 2 and git diff --check passed.

The complete native package can be run with ./dev test pkg/sql/opt/optbuilder; the focused case is TestBuilder/row-star-expansion. In this environment the ./dev generate, ./dev lint --short, and ./dev test wrappers refuse to run as root, so validation used directly built Bazel test executables with --norun_validations. Full-repository generation, lint, and tests are not claimed. SQL validation used real single-node test servers; no historical release or separate PostgreSQL binary was tested.

The product fix and the initial regression inputs were generated with DeepSeek, then reviewed and tested locally; an additional direct aggregate regression was added during PR preparation.

Release note (bug fix): Fixed a bug where ROW(table.*) introduced an extra level of record nesting instead of expanding the table's fields. This could produce incorrectly nested array elements in expressions such as array_agg(ROW(table.*)). Explicit nested ROW expressions and whole-row references without a star retain their intended nesting.

ROW(table.*) previously wrapped the table's expanded fields in an
additional tuple. For example, array_agg(ROW(table.*)) produced nested
records where PostgreSQL produces an array of flat row values. Explicit
nested row constructors acquired one additional level as well.

Expand immediate table/composite star arguments in the enclosing row
constructor before ordinary child traversal. Preserve deliberate nested
rows, bare whole-row references, field order, visibility, and tuple
metadata. Reuse the existing star resolver and leave ordinary function
arguments unchanged.

Add native optbuilder coverage for direct and aliased stars, mixed and
repeated fields, composite-expression stars, intentional nesting, bare
whole-row controls, and ordered array_agg. The same regression fixture
fails before the fix and passes afterward.

Fixes cockroachdb#115150
Epic: none

Release note (bug fix): Fixed a bug where ROW(table.*) introduced an
extra level of record nesting instead of expanding the table's fields.
This could produce incorrectly nested array elements in expressions
such as array_agg(ROW(table.*)). Explicit nested ROW expressions and
whole-row references without a star retain their intended nesting.
@Alignyx
Alignyx requested a review from a team as a code owner September 6, 2026 10:58
@Alignyx
Alignyx requested review from michae2 and removed request for a team September 6, 2026 10:58
@blathers-crl

blathers-crl Bot commented Sep 6, 2026

Copy link
Copy Markdown

Thank you for contributing to CockroachDB. Please ensure you have followed the guidelines for creating a PR.

My owl senses detect your PR is good for review. Please keep an eye out for any test failures in CI.

🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is dev-inf.

@blathers-crl blathers-crl Bot added the O-community Originated from the community label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

O-community Originated from the community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

opt: incorrect nested tuple within array_agg

1 participant