Skip to content

fix: filter NULL column defaults case-insensitively to match builtin (#122) - #125

Merged
aesslinger merged 2 commits into
mainfrom
fix/122-default-value-null-filter-parity
Sep 30, 2026
Merged

aesslinger merged 2 commits into
mainfrom
fix/122-default-value-null-filter-parity

Conversation

@aesslinger

@aesslinger aesslinger commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

What

Fixes #122 — a byte-for-byte parity divergence in the default_value filter shared by get_columns, get_all_columns_batch, and get_schema_snapshot.

row_to_table_column filtered out NULL column defaults case-sensitively (d == "NULL"), but the built-in driver filters case-insensitively (d.eq_ignore_ascii_case("null")) in both its get_columns and get_all_columns_batch. So a column declared DEFAULT null (lowercase — legal PostgreSQL, stored verbatim in information_schema.columns.column_default) made this plugin emit a spurious default_value: "null" in the wire JSON, where the builtin omits the field entirely.

Fix

Match the builtin exactly:

  • The bare-null check is now case-insensitive (eq_ignore_ascii_case("null")) — drops NULL, null, Null, etc.
  • The NULL::<type> cast-prefix check stays case-sensitive (starts_with("NULL::")) — the builtin keeps that one starts_with("NULL::"), not eq_ignore_ascii_case, so this matches.

Extracted the filter into a pure column_default_value(column_default, is_identity) helper split out of row_to_table_column, for unit testing without a live tokio_postgres::Row (per the repo's extract-pure-logic pattern, .rules/rust.md #4/#5). row_to_table_column now calls it.

Why the parity suite didn't catch this

The 83-test cross-repo parity suite's seed fixtures (tabularis/tests/fixtures/postgres_seed.sql) declare no DEFAULT null columns (only DEFAULT 'neutral' and DEFAULT 0), so the divergence was never exercised — it was found by a source-level audit of the builtin's filter during the #123 hard verification, not by the suite. Filed separately as #122 per scope discipline rather than folded into #123.

TDD

Per CLAUDE.md's TDD-for-parity-bugs pattern: proved the divergence with a unit test before fixing. Added 5 column_default_value tests covering the divergence (lowercase null — the bug), mixed-case Null, uppercase NULL, NULL:: casts, real defaults (0, 'neutral', now()), auto-increment (nextval(...) + is_identity=YES), and empty/None. Confirmed column_default_value_drops_a_lowercase_null_default_to_match_builtin failed against the pre-fix code for the right reason (Some("null") instead of None) before applying the fix; all 5 pass after.

Verification

  • cargo build --release
  • Cross-repo 83-test byte-for-byte parity suite (POSTGRES_PLUGIN_BIN against tabularis' src-tauri/tests/postgres_integration parity*): 83/83 pass — no existing behavior regressed.
  • 361 unit tests pass (5 new).
  • 30 live-DB tests pass against a real postgres:16 instance.
  • cargo clippy --all-targets -- -D warnings; cargo fmt --all --check; npx markdownlint CHANGELOG.md — all clean.

Closes #122.

…122)

row_to_table_column's default_value filter used a case-sensitive
`d == "NULL"`, so a column declared `DEFAULT null` (lowercase — legal
PostgreSQL, stored verbatim in information_schema.columns.column_default)
surfaced a spurious `default_value: "null"` in the wire JSON, where the
built-in driver omits the field. The builtin filters case-insensitively
(`eq_ignore_ascii_case("null")`) in both get_columns and
get_all_columns_batch. Affects get_columns, get_all_columns_batch, and
get_schema_snapshot, which all share the row_to_table_column mapper.

Fixed by matching the builtin exactly: the bare-null check is now
case-insensitive, while the `NULL::<type>` cast-prefix check stays
case-sensitive (the builtin keeps that one starts_with("NULL::"), not
eq_ignore_ascii_case). Extracted the filter into a pure
column_default_value(column_default, is_identity) helper split out of
row_to_table_column for unit testing without a live tokio_postgres::Row,
per the repo's extract-pure-logic pattern.

TDD: added 5 unit tests for column_default_value covering the divergence
(lowercase `null` — the bug), mixed-case `Null`, uppercase `NULL`,
`NULL::` casts, real defaults, auto-increment (nextval + is_identity),
and empty/None. Confirmed the lowercase-null test failed against the
pre-fix code for the right reason (returned Some("null") instead of
None) before applying the fix; all 5 pass after.

Verified: cargo build --release; 361 unit + 30 live-DB against the local
pg-tabularis-test podman container; the cross-repo 83-test byte-for-byte
parity suite (POSTGRES_PLUGIN_BIN against tabularis'
src-tauri/tests/postgres_integration parity*) passes 83/83; cargo
clippy --all-targets -D warnings; cargo fmt --all --check; npx
markdownlint CHANGELOG.md -- all clean.

Note: the 83-test parity suite's seed fixtures declare no `DEFAULT null`
columns, so this divergence was never caught by it — only a source-level
audit of the builtin's filter found it (#122).
@aesslinger aesslinger added bug Something isn't working prerelease:rc Version suggestion targets a release candidate labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown

Version suggestion

Based on this PR's title (fix) and the prerelease:rc label:

Current 1.0.0-rc.5
Suggested next tag v1.0.0-rc.6

This is informational only — no tag or release is created automatically yet.

The #122 fix extracted column_default_value with its own is_auto_increment
computation, duplicating the one row_to_table_column already computes for
the is_auto_increment field. The builtin computes is_auto once and reuses
it for both the field and the filter; match that structure so the two
can't drift. column_default_value now takes the pre-computed bool instead
of is_identity, and the unit tests pass it through. Also adds a test
guarding that a string *literal* 'null' (stored by PG as 'null'::text,
quoted+cast) survives the filter — it doesn't match the bare-null check —
which is the empirical boundary the live-probe confirmed.

No behavior change: is_auto_increment was already computed identically in
both places; this just dedupes it. 361 unit + 30 live-DB + 83/83 parity
all pass.
@aesslinger aesslinger self-assigned this Sep 30, 2026
@aesslinger
aesslinger merged commit 5436cc6 into main Sep 30, 2026
14 checks passed
@aesslinger
aesslinger deleted the fix/122-default-value-null-filter-parity branch September 30, 2026 12:40
aesslinger added a commit that referenced this pull request Sep 30, 2026
Ships the two PRs merged since 1.0.0-rc.5: the NULL column-default filter
parity fix (#125, #122 — case-insensitive to match the builtin) and the
cancel server-side statement notification handler (#127, #126 — dormant
until tabularis#832 ships the host-side notification).

Verified: cargo build --release; 366 unit + 30 live-DB against the local
pg-tabularis-test podman container; the cross-repo 83-test byte-for-byte
parity suite (POSTGRES_PLUGIN_BIN against tabularis'
src-tauri/tests/postgres_integration parity*) passes 83/83; cargo
clippy --all-targets -D warnings; cargo fmt --all --check; npx
markdownlint CHANGELOG.md — all clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working prerelease:rc Version suggestion targets a release candidate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: default_value NULL filter is case-sensitive, diverges from builtin (misses lowercase DEFAULT null)

1 participant