Skip to content

feat: name unaliased select items the way Snowflake does - #99

Merged
kvlonge merged 12 commits into
mainfrom
feat/output-names
Nov 6, 2026
Merged

kvlonge merged 12 commits into
mainfrom
feat/output-names

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Why

Unaliased select items such as SELECT COUNT(*) FROM orders or SELECT 1 were refused with SQ0009, the first thing many readers type. Snowflake names these columns after the item's text, and recorded receipts show exactly how for a set of shapes.

Changes

  • An unaliased item takes its source text upper-cased as written, only for the shapes the receipts in cases/relational/unaliased_output_names.toml record: literals in their recorded spellings (1, -2, 1.50, 1e3, plain text, NULL, TRUE, FALSE); COUNT, SUM, MAX and AVG of a column, COUNT(*) and COUNT(DISTINCT col); LOWER, UPPER and COALESCE of columns and integers; a column then a literal under + - * / and ||; a column cast to INTEGER with CAST or ::. Columns may be qualified, or quoted when already upper case. The text may hold single spaces, spaces inside parentheses and one line break.
  • Every other item refuses with SQ0009 and the alias hint, as on main: semi-structured paths and subscripts, CASE, IFF, subqueries, windows, nested calls, other functions (ROUND, CURRENT_DATE() and so on), tabs, CRLF, COUNT (*), other number spellings, and lower-case quoted identifiers. Each can be added with its own receipt.
  • A result column of only NULL is cast to VARCHAR at the output, as Snowflake describes it.
  • A repeated result name (SQ0004) now suggests aliasing each column.
  • The SQ0009 explanation lists the named shapes.

Verification

  • A CLI test checks the converted names against the receipts' names, and another refuses each of 44 unrecorded shapes, including every path and subscript form.
  • Library and CLI tests pass; recorded replay and the translation measure run through the merge gate.

SELECT COUNT(*) FROM orders is refused as SQ0009 because no recorded
result shows the name Snowflake gives the column. These cases record
those names for aggregates, literals and expressions as written.
Snowflake results for the five cases in cases/relational/unaliased_output_names.toml at 37346da.
An unaliased select item that is not a column takes its source text
upper-cased as written, with spacing, line breaks and quotes kept, as the
recorded receipts show. Literals, which carry no source position, are
rebuilt from their value. Text outside ASCII or holding a comment is
still refused with SQ0009.
Snowflake describes an untyped NULL result column as VARCHAR(0), as the
recorded unaliased NULL literal shows; the converted statement now casts
such a column to VARCHAR at its output, leaving inner queries untouched.
Naming an unaliased select item after its text now applies only to the
shapes the receipts cover: literals in their recorded spellings, COUNT,
SUM, MAX and AVG of a column or COUNT(*), LOWER, UPPER and COALESCE of
columns and integers, a column then a literal under + - * / and ||, and a
column cast to INTEGER, written with single spaces and at most one line
break. Semi-structured paths and subscripts, which were named from a span
stopping at their base, and every other shape refuse with SQ0009 again.
A repeated result name now suggests aliasing each column.
kvlonge added a commit that referenced this pull request Nov 6, 2026
…s on main e9f002d

Round 3 adds 76 queries: dbt_utils and dbt_date macros compiled by
dbt-snowflake 1.12 (date_spine, get_date_dimension, pivot,
generate_surrogate_key, deduplicate, cross-database macros, generic tests),
dbt-snowflake's rendered snapshot SQL and incremental DML (merge with
incremental predicates, delete+insert, insert_overwrite, snapshot merge),
FLATTEN over VARIANT, and unaliased output names ahead of #99. Database and
schema qualifiers are dropped; ORDERS_SNAPSHOT joins the model schema.

kvlonge commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

D94 gate on 1aba664 (main e9f002d merged in):

  • make check-ci: exit 0
  • make test-rust: passed, every suite exit 0
  • make verify-recorded: 11,599 cases, 7,640 passed, 1,031 diverged as expected, 2,811 refused, 0 unproven, 0 failed; 220 of 220 equivalences and rules verified. translation-results: 0 regressions, 5 statements newly Matching (relational/unaliased_output_names.toml), recorded in the results baseline in 1aba664.
  • Hygiene scan: clean.

check-ci and test-rust ran on a3b4a52. 1aba664 changes only coverage/translation/results-baseline.json, and translation-results passes against it.

kvlonge commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

D94 gate for landing, after merging main c3555dd (#110):

  • a43f173 (the merge): make check-ci exit 0; make test-rust passed, every suite exit 0; make verify-recorded 11,779 cases, 7,761 passed, 1,031 diverged as expected, 2,951 refused, 0 pending, 0 unproven, 0 failed; 220 of 220 equivalences and rules verified.
  • translation-results: 0 regressions. One more statement now matches (translation/query/with.select.toml#t001_chained, Refused → Matching, its unaliased item now named), recorded in the baseline in 37e3767. That commit changes only coverage/translation/results-baseline.json, and translation-results passes on it.
  • Hygiene scan: clean. Auditor round 2: approved at 1aba664, with no code change since.
    GitHub Actions jobs aren't starting (billing), so this gate is the landing check.

@kvlonge
kvlonge merged commit 37e3767 into main Nov 6, 2026
1 of 2 checks passed
@kvlonge
kvlonge deleted the feat/output-names branch November 9, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant