Skip to content

Fix typmod defaulting to 0 instead of -1 for all types#269

Merged
fuziontech merged 3 commits intomainfrom
fix/typmod-default-minus-one
Feb 28, 2026
Merged

Fix typmod defaulting to 0 instead of -1 for all types#269
fuziontech merged 3 commits intomainfrom
fix/typmod-default-minus-one

Conversation

@fuziontech
Copy link
Member

Summary

  • Fix typmod=-1 default: All TypeInfo returns that didn't explicitly set Typmod were getting Go's zero value (0) instead of PostgreSQL's "no modifier" sentinel (-1). This caused JDBC clients (pgjdbc) to misinterpret column metadata — most critically for INTERVAL, where typmod=0 means "second precision 0" (no fractional seconds) vs typmod=-1 meaning "default precision" (microseconds).
  • Add missing rows.Err() check in handleExecute: The extended query protocol's Execute path didn't check for streaming errors after the row iteration loop. The simple query path (executeSelectQuery) already had this check. Without it, gRPC stream errors from workers could be silently swallowed, sending CommandComplete to the client without reporting the error.

Context

Metabase (JDBC/c3p0) was reporting java.util.NoSuchElementException when running select worker_version(), uptime() with no corresponding errors in duckgres logs. The uptime() macro returns an INTERVAL. pgjdbc's TypeInfoCache.getScale() returns typmod & 0xFFFF when typmod != -1, so typmod=0 → scale=0 (no fractional seconds), while typmod=-1 → scale=6 (default microsecond precision). This metadata mismatch between what duckgres reports and what the actual data contains is the likely root cause of the JDBC client error.

Test plan

  • New TestMapDuckDBTypeTypmod verifies all types return typmod=-1 except HUGEINT, UBIGINT, and DECIMAL(p,s) which have specific positive typmods
  • Existing TestMapDuckDBType still passes (OID/Size unchanged)
  • Full go test ./server/ passes
  • go build ./... succeeds

🤖 Generated with Claude Code

fuziontech and others added 3 commits February 27, 2026 15:12
PostgreSQL uses typmod=-1 to mean "no type modifier". Go's zero value
for int32 is 0, so every TypeInfo without an explicit Typmod was
sending typmod=0 on the wire. JDBC clients (pgjdbc) interpret this
differently from -1:

- INTERVAL typmod=0 → getScale() returns 0 (precision 0, no fractional
  seconds) instead of 6 (default microsecond precision)
- TIME/TIMESTAMP typmod=0 → similar precision mismatch
- Other types: typmod=0 can trigger unexpected metadata behavior

This likely causes Metabase/JDBC NoSuchElementException errors when
querying INTERVAL-returning functions like uptime().

Also adds missing rows.Err() check in handleExecute (extended query
protocol). The simple query path checked for streaming errors but the
extended protocol path did not, meaning gRPC stream errors from workers
would be silently swallowed instead of reported to the client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add STRING, TIME WITH TIME ZONE, TIMESTAMP WITH TIME ZONE, and
  unknown type (SOMECUSTOMTYPE) cases to TestMapDuckDBTypeTypmod
- Add TestExtendedQueryErrorHandling integration test with two subtests:
  single error recovery and multi-cycle error/success on same connection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fuziontech fuziontech merged commit 8ab4017 into main Feb 28, 2026
14 checks passed
@fuziontech fuziontech deleted the fix/typmod-default-minus-one branch February 28, 2026 00:21
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