Skip to content

refactor: replace oid-coercion analyzer rule with a SQL rewrite rule#380

Merged
sunng87 merged 1 commit into
masterfrom
refactor/oid-coercion-to-sql-rewrite
Jul 19, 2026
Merged

refactor: replace oid-coercion analyzer rule with a SQL rewrite rule#380
sunng87 merged 1 commit into
masterfrom
refactor/oid-coercion-to-sql-rewrite

Conversation

@sunng87

@sunng87 sunng87 commented Jul 19, 2026

Copy link
Copy Markdown
Member

revert the string auto coersion to oid support, it doesn't work in a multi catalog setup, at least for now.

The oid-coercion analyzer rule (OidStringCoercion) resolved oid-alias
strings to oids, but doing so at the analyzer layer forced it to build a
LogicalPlan scan -- which can only be done with a TableProvider -- so the
rule carried an OidLookupProvider trait that implementers had to wire up.
That TableProvider dependency made the feature hard to integrate into
user code.

Move name->oid resolution back to the SQL layer, where a scan can be
emitted as plain SQL text that DataFusion plans normally through the
catalog -- no TableProvider, no provider trait:

* Add RewriteRegCastToSubquery (rules.rs), which rewrites the FORWARD
  oid-alias cast direction at the AST layer:
    - numeric string ('16417') -> literal oid (matches Postgres, and
      avoids embedding a subquery in VALUES/IN contexts DF can't
      decorrelate);
    - name string ('public') -> (SELECT oid FROM pg_catalog.<table>
      WHERE <name_col> = <operand>).
  A direction guard leaves reverse/column casts (prorettype::regtype,
  prorettype::regtype::text) untouched; PgOidTypePlanner still maps
  those cast types to Int32 so they parse.

* Delete oid_coercion_rule.rs entirely (the analyzer rule, the
  OidLookupProvider trait, and their tests -- ~598 lines).

* Drop the OidLookupProvider impl, the oid_lookup_target /
  build_oid_name_subquery helpers, their test, the analyzer-rule
  registration in setup_pg_catalog, and the now-unused imports from
  pg_catalog.rs.

* Register RewriteRegCastToSubquery in parser.rs (before
  RemoveSubqueryFromProjection so its subqueries get the LIMIT 1
  scalar stamp).

Functional difference: the implicit `oid_col = 'str'` form (bare string
vs an oid column) is no longer resolved, because the SQL layer has no
schema. That case is covered by the analyzer rule's Field-metadata path
which is now gone; exactly one client query (dbeaver's relation-size
`c.relnamespace = 'public'`) used it, and that query is re-added to the
blacklist with a Blocked by / Remove when comment. Every explicit
`'...'::regclass`-style cast still resolves to a real oid.

PgOidTypePlanner and oid_field are left in place: the TypePlanner is
still needed so reverse/column casts parse, and the pg.oid_alias column
metadata it stamps is now unused but harmless (cosmetic cleanup deferred
to keep this change focused).

All 15 workspace test suites pass (62+45+24 lib tests + 8 client
integration tests: dbeaver, metabase, pgcli, pgadbc, psql, grafana,
metabase, array_bounds); clippy clean; fmt clean. Net -396 lines.
@sunng87
sunng87 merged commit 83e22aa into master Jul 19, 2026
7 checks passed
@sunng87
sunng87 deleted the refactor/oid-coercion-to-sql-rewrite branch July 19, 2026 05:20
sunng87 added a commit that referenced this pull request Jul 20, 2026
… the oid (#385)

* fix(catalog): store reg* oid-alias columns as their display name, not the oid

PostgreSQL `reg*` columns (regproc, regtype, regclass, ...) display as the
object NAME, not the integer oid -- e.g. `pg_type.typinput` for the `text`
type shows `textin`. Commit 0300310 had exported these as `::oid` integers so
the oid-coercion analyzer rule could resolve name/numeric string comparisons.
That benefit is now mostly gone (the coercion analyzer was replaced by SQL
rewrites in #380, and the implicit comparison it served is blacklisted), while
the display regression it introduced remains: clients saw `42` where real
Postgres returns `textin` (issue #384).

Revert the `reg*` storage to the display name (string), keeping the
`pg.oid_alias=<kind>` field metadata so the wire layer (arrow-pg, fixed
separately) still reports the correct alias type in RowDescription. Genuine
`oid` columns stay int32 (Postgres displays oid as the integer).

  export_pg_catalog_arrow.sh (generates the committed feathers):
    - reg* family -> pa.string(); drop the `::oid` SELECT cast (select_expr).
    - oid stays pa.int32(); metadata stamping unchanged.
    - POSTGRES_PORT is now env-overridable (host-side publish is only for
      ad-hoc inspection; the export runs in-container).
  pg_to_arrow.py (generic exporter): it already stored reg* as string (the
    OIDs fall through to the pa.string() default); only the now-stale
    docstrings claimed int32. Updated the docstrings.

Regenerated the 10 affected feathers against postgres:17.6 (unchanged version):
pg_type, pg_proc, pg_aggregate, pg_operator, pg_am, pg_amproc, pg_conversion,
pg_range, pg_ts_parser, pg_ts_template. Verified pg_type.typinput/typoutput/
typreceive/typsend are now `string` + `pg.oid_alias=regproc` holding the name
(`textin`, `textout`, ...); oid/typnamespace remain int32.

Added end-to-end test pg_type_regproc_columns_display_as_names: selecting
typinput/typoutput for oid=25 returns `textin`/`textout`.

Trade-off (documented in the issue/option choice): numeric oid equality on
reg* columns (`WHERE typinput = 42`) and an explicit `col::regproc` cast on a
(now string) reg* column no longer work the int path. The common display and
name-comparison forms -- `SELECT typinput`, `typinput::text`,
`WHERE typinput = 'textin'` -- are restored.

Refs: #384

* chore: fmt
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