Skip to content

Fix #3403: wrap top-level infix queries decoding into case classes - #3404

Open
eugkhp wants to merge 2 commits into
zio:masterfrom
eugkhp:fix/top-infix-product-wrap
Open

eugkhp wants to merge 2 commits into
zio:masterfrom
eugkhp:fix/top-infix-product-wrap

Conversation

@eugkhp

@eugkhp eugkhp commented Jun 12, 2026

Copy link
Copy Markdown

Fixes #3403.

Problem

#3147 (c334986f, released in quill-engine 4.8.6) made top-level infix queries pass through unwrapped (TopInfixQuery). For case-class results this silently changes the decode contract: extractors decode positionally in field-declaration order, while the raw SQL — e.g. SELECT b.* FROM blocked_periods b — returns the table's physical column order (including columns appended by later ALTERs). When the orders differ, decoders read neighboring columns' values: exceptions if types/enum members mismatch, silently wrong data if they don't.

Reproduction

Any table whose DDL column order differs from the case class:

CREATE TABLE blocked_periods (id UUID, home_id UUID, period DATERANGE, reason VARCHAR, active BOOLEAN);
ALTER TABLE blocked_periods ADD COLUMN source VARCHAR;          -- appended later
ALTER TABLE blocked_periods ADD COLUMN occupancy_status VARCHAR; -- appended later
case class BlockedPeriod(id: UUID, homeId: UUID, period: DateRange, reason: String,
                         source: BlockedPeriodSource /* enum */, occupancyStatus: OccupancyStatus /* enum */, ...)

run(quote { sql"""SELECT b.* FROM blocked_periods b WHERE ...""".as[Query[BlockedPeriod]] })
// 4.8.5: SELECT x.id, x.home_id, ... FROM (SELECT b.* ...) AS x  -> correct
// 4.8.6: SELECT b.* ...                                          -> source decoder reads occupancy_status:
// NoSuchElementException: OCCUPIED is not a member of Enum(BOOKING, ...)

The new SqlQuerySpec cases in this PR encode the contract at the SQL level (sql"SELECT * FROM TestEntity".as[Query[TestEntity]] must stay wrapped).

Fix

Raw passthrough remains for single values (one column) and tuples (column order is explicit in the same expression — the #3147 use case). Case-class results keep the wrapping query, whose explicit projection pins column order — the pre-4.8.6 behavior.

Validation

  • SqlQuerySpec: 92/92, including the existing Improve handling of raw-queries #3147 tuple/value passthrough tests (unchanged) and the two new case-class tests.
  • Production reproducer (200+ module Scala 3 codebase, Postgres testcontainers): the failing spec above goes red → green with only this engine change applied; the affected service's full persistence suite passes (71/71).

Single-commit bisection that identified #3147 is documented in #3403.

zio#3147 made top-level infix queries pass through unwrapped (TopInfixQuery).
For case-class results this changes the decode contract: extractors decode
positionally in field-declaration order, while the raw SQL (e.g. SELECT *)
returns the table's physical column order — decoders silently read
neighboring columns when the orders differ.

Keep the raw passthrough for single values and tuples (column order is
explicit in the same expression) and restore the wrapping query for
case-class results, whose explicit projection pins the column order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Jun 12, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Five quill-sql-test cases still asserted the pre-fix unwrapped SQL for
case-class infix results. With case-class results now re-wrapped to pin
column order for positional decoding, update their expected SQL to the
wrapping form the engine produces (values/tuples are unaffected):

- SqlIdiomSpec "full infix query"
- OracleDialectSpec "No 'AS' aliases"
- InfixSpec "use set notation when interpolating liftQuery"
- ExpandNestedQueriesSpec "... regular schema" + "... multi-level"
@eugkhp
eugkhp force-pushed the fix/top-infix-product-wrap branch from 3b4e7e2 to 71253e7 Compare June 19, 2026 00:40

This branch has not been deployed

No deployments
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.

Regression in 4.8.6 (#3147): unwrapped top-level infix entity queries decode by table column order, not case-class field order

2 participants