Skip to content

Feat/dynamic select#1

Merged
cgardev merged 2 commits into
mainfrom
feat/dynamic-select
Jul 6, 2026
Merged

Feat/dynamic select#1
cgardev merged 2 commits into
mainfrom
feat/dynamic-select

Conversation

@cgardev

@cgardev cgardev commented Jul 6, 2026

Copy link
Copy Markdown
Owner

feat: add dynamic select with run-time projection and record accessors

Summary

This pull request adds a dynamic SELECT constructor for projections whose arity only becomes known at run time, mirroring jOOQ's DSL.select(SelectFieldOrAsterisk...) and its untyped org.jooq.Record. It also renames the library's leftover internal working name (jooq) to gooq across error messages and self-referential comments.

Motivation

The typed Select1..Select22 constructors require the projection arity at compile time. A query whose column set is assembled from a run-time value — for example a GROUP BY dimension list derived from a request — previously forced consumers into a switch over every possible arity, duplicating the query construction and row mapping once per case.

What is included

Select(fields ...AnyField) SelectFromStep[Record]

  • Accepts a projection assembled at run time and composes with the entire existing clause chain: joins, Distinct, Where, GroupBy, Having, OrderBy, limits, locking, set operations, and every terminal fetch.
  • Every projected field that carries a Go element type (Field[T], including aliased fields and the Raw/RawValue escape hatches) is scanned into a value of that type through a new unexported scanTarget hook on the concrete field implementation, so Record values match what the equivalent typed projection would produce, independent of the driver's native representations.
  • A SQL NULL scanned into a non-nullable element type is an error, exactly as in the typed constructors; nullable columns are projected with a pointer or sql.Null* element type.
  • Passing no fields mirrors the reference's empty-select convenience and renders SELECT *, taking the column names from the result set.

Record

Dynamic row type mirroring org.jooq.Record:

  • Size(), Columns(), Values(), Get(index), and GetByName(name) (case-insensitive, consistent with FetchInto column matching).
  • Generic accessors Value[T](record, name) and ValueAt[T](record, index) mirroring Record.get(..., Class), with deliberate conversions only: []byte relaxes to string and numeric kinds convert between one another. Anything else is an error rather than a silent reflect conversion, which would, for example, turn an integer into the string of its code point.

Rename: jooqgooq

Error message prefixes (gooq: ...), the fake test driver name, and every comment that refers to this library itself now say gooq. References to the jOOQ Java library that inspired the design — its org.jooq package paths and website — are intentionally preserved.

Testing

  • Golden SQL tests confirming the dynamic constructor renders exactly what the typed counterpart renders for the same projection.
  • Fake-driver unit tests covering typed scanning, alias resolution, the SELECT * path, nullable projections, conversion errors, and the single-row terminal operations.
  • New PostgreSQL and SQLite integration tests, including the motivating run-time GROUP BY arity case.
  • Both CI suites (core module and integration module) pass.

Introduce Select(fields ...AnyField), the counterpart of jOOQ's
DSL.select(SelectFieldOrAsterisk...) for projections whose arity only becomes
known at run time, such as a GROUP BY column set derived from a request. The
constructor produces dynamic Record rows and composes with the whole existing
clause chain, including set operations and every terminal fetch.

Each projected field that carries a Go element type is scanned into a value of
that type through a new unexported scanTarget hook on the concrete field
implementation, so Record values match the values the equivalent typed SelectN
projection would produce, independent of the driver's native representations.
Passing no fields mirrors the reference's empty-select convenience and renders
SELECT *, taking the column names from the result set.

Record mirrors the untyped org.jooq.Record: Size, Columns, Values, Get by
index, and GetByName, plus the generic accessors Value and ValueAt mirroring
Record.get(..., Class) with deliberate conversions only (byte slices relax to
string and numeric kinds convert between one another) rather than silent
reflect conversions.

Covered by golden SQL and fake-driver unit tests plus PostgreSQL and SQLite
integration suites, including the run-time GROUP BY arity use case.
Error message prefixes, the fake test driver name, and every comment that
refers to this library itself now say gooq instead of the leftover jooq
working name. References to the jOOQ Java library that inspired the design,
including its org.jooq package paths and website, are intentionally preserved.
@cgardev
cgardev merged commit b992013 into main Jul 6, 2026
2 checks passed
@cgardev
cgardev deleted the feat/dynamic-select branch July 6, 2026 20:42
@cgardev
cgardev restored the feat/dynamic-select branch July 7, 2026 10:34
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.

2 participants