Skip to content

Fix AdvancedSql/raw-SQL scalar queries for reference-typed columns (byte[], IPAddress, etc.)#4960

Merged
mysticmind merged 2 commits into
JasperFx:masterfrom
mdissel:repro/advanced-sql-bytea-scalar
Jul 16, 2026
Merged

Fix AdvancedSql/raw-SQL scalar queries for reference-typed columns (byte[], IPAddress, etc.)#4960
mysticmind merged 2 commits into
JasperFx:masterfrom
mdissel:repro/advanced-sql-bytea-scalar

Conversation

@mdissel

@mdissel mdissel commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #4959.

  • ScalarSelectClause<T> is where T : struct because it also implements ISelector<T?> via Nullable<T>. Any Npgsql-mapped reference type (byte[]/bytea, IPAddress/inet, BitArray, JsonDocument, ...) blew up at query-construction time with ArgumentException/TypeLoadException before Npgsql's own type mapping ever got a chance to run. Only string had a dedicated workaround (ScalarStringSelectClause) — byte[] and other reference types did not.
  • Added ScalarClassSelectClause<T> where T : class as the reference-type counterpart — relies on natural reference-type nullability instead of Nullable<T>, mirroring the existing ScalarStringSelectClause pattern.
  • Branch on typeof(T).IsValueType in both AdvancedSqlQueryHandlerBase.GetSelectClause<T> (session.AdvancedSql.QueryAsync/StreamAsync) and UserSuppliedQueryHandler.GetSelectClause (session.QueryAsync<T>("select ...")), which had the identical latent bug.
  • Confirmed this isn't byte[]-specific but is specific to types with a genuine Npgsql scalar type mapping — CLR arrays like int[]/Guid[] are composed from their element type by Npgsql rather than registered as scalar mappings, so they don't hit this code path at all (a separate, unrelated limitation, out of scope here).

Test plan

  • New src/DocumentDbTests/Bugs/advanced_sql_query_scalar_reference_types.cs: single-scalar, row-tuple (row(...)), and StreamAsync cases for a byte[]/bytea column, plus a single-scalar case for an IPAddress/inet column, against a plain (non-document) table added via ExtendedSchemaObjects.
  • Full CoreTests suite: 479 passed, 0 failed.
  • Full DocumentDbTests suite: 1055 passed, 0 failed.
  • LinqTests scalar-select suites (Scalar/select_scalar filters): 36 passed, 0 failed — confirms the existing LINQ scalar-projection paths (which never route arbitrary reference types through ScalarSelectClause<T> in the first place) are unaffected.
  • Verified pre-fix: both new tests reproduce the exact reported ArgumentException/TypeLoadException before the fix, and pass after.

🤖 Generated with Claude Code

…yte[], IPAddress, etc.)

ScalarSelectClause<T> is `where T : struct` because it also implements
ISelector<T?> via Nullable<T>, so any Npgsql-mapped reference type (byte[]
bytea, IPAddress inet, BitArray, JsonDocument, ...) blew up at query
construction with an ArgumentException/TypeLoadException instead of ever
reaching Npgsql's own type mapping. Only string had a dedicated workaround
(ScalarStringSelectClause); byte[] and other reference types did not.

Add ScalarClassSelectClause<T> where T : class as the reference-type
counterpart (natural nullability, no Nullable<T>), and branch on
typeof(T).IsValueType in both AdvancedSqlQueryHandlerBase.GetSelectClause<T>
and UserSuppliedQueryHandler.GetSelectClause, which had the identical bug.

Fixes JasperFx#4959.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mysticmind mysticmind added this to the 9.15.5 milestone Jul 16, 2026

@mysticmind mysticmind left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AdvancedSql coverage is solid. Could you please also add a unit test for the UserSuppliedQueryHandler path, i.e. a raw-SQL scalar case like QueryAsync<byte[]>("select …")? Right now all the new tests go through AdvancedSql, so that second fixed path has no coverage.

Addresses PR review feedback: all prior new tests went through AdvancedSql,
leaving the UserSuppliedQueryHandler.GetSelectClause path (session.QueryAsync<T>
raw-SQL scalar queries) — which had the identical struct-constraint bug —
without its own regression test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mdissel

mdissel commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — added `can_query_single_bytea_scalar_via_raw_sql` which exercises `session.QueryAsync<byte[]>("select ...")`, i.e. the `UserSuppliedQueryHandler.GetSelectClause` path directly (pushed in 586b34f).

Note: the tuple/`row(...)` form (`QueryAsync<T1, T2, T3>`) only exists on `AdvancedSql`, not on the base `QueryAsync` API, so that variant is already fully covered by the existing `AdvancedSql` row-tuple test — there's no equivalent to add on the raw-SQL side.

@mdissel
mdissel requested a review from mysticmind July 16, 2026 12:39

@mysticmind mysticmind left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mdissel, looks good to me!

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.

AdvancedSql.QueryAsync<...> throws ArgumentException/TypeLoadException when a requested type is byte[] (bytea column)

2 participants