feat(transform): object-level schema routing via SchemaRouter - #319
Merged
Conversation
Generalize the schema transform from a whole-schema Map<oldSchema,newSchema> to a SchemaRouter that resolves a target schema per (schema, name, namespace). This lets a single source schema fan out per object — e.g. route a table to one schema and a function to another — while cross-object references (SQL and PL/pgSQL bodies, DROP/revert statements) are rewritten to each object's routed target. - Add SchemaRouter (relations/functions/types buckets + schema-level default), mirroring the routing model in qualifyUnqualified. - Thread an object namespace through transformNameList/transformRelation and annotate create/reference sites (functions, types, relations, drops). - Rewrite LANGUAGE sql function bodies AST-precisely when object routes exist; whole-schema routes keep the existing string pass (fully backwards compatible). - validateNoUntransformedSchemas only enforces fully-moved (schema-default) schemas; partially-routed schemas may legitimately retain references. - transformSql/transformSqlStatement accept a Map or a SchemaRouter. 160 existing tests unchanged; 12 new tests cover table/function fan-out, SQL + PL/pgSQL cross-object body refs, DROP routing, and Map/router parity.
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Generalizes the schema transform from a whole-schema
Map<oldSchema, newSchema>to aSchemaRouterthat resolves a target schema per object occurrence —(schema, name, namespace)— instead of per schema. This makes a single source schema fan out at the object level: e.g. route a table to one schema and a function to another, with every cross-object reference (SQL bodies, PL/pgSQL bodies,DROP/revert statements, types, params, return types) rewritten to each object's routed target.Motivation: downstream (
pgpm applymodules) needs to cherry-pick objects out of a reusable package into consumer-selected schemas — e.g. a shared helper function inreportingthat reads a per-tenanttenant_a.accountstable, from a singleusers-schema source. AMap<string,string>can only send all ofusersto one place.Model
SchemaRoutermirrors the routing shape already used byqualifyUnqualified(relations/functions/typesbuckets → matchingpg_class/pg_proc/pg_type):Resolution is object-route-first, then the schema-level default, then leave unchanged. Namespaces are independent (a table
widgetand a functionwidgetcan route to different schemas).SchemaRouter.fromSchemaMap(...)reproduces the historic whole-schema behaviour exactly.Changes
router.ts(new) —SchemaRouter,SchemaRoute,RouteSpec,ObjectNamespace,RouteNamespace.transformNameList/transformRelation; annotated create & reference sites (functions, types, relations) andDROP …(namespace derived fromremoveType, so revert scripts route correctly).LANGUAGE sqlbodies (opaque String nodes) are rewritten AST-precisely (parse → walk → deparse) when object routes are present; pure whole-schema routes keep the existing quoting-preserving string pass — so existing behaviour is byte-for-byte unchanged.validateNoUntransformedSchemasnow enforces the leftover check only for fully-moved schemas (those with a schema-level default); partially/object-only routed schemas may legitimately retain references.transformSql/transformSqlStatementacceptMap<string,string> | SchemaRouter(SchemaMappingInput).assumeSchemasExistandSchemaTransformResultreporting are preserved.Backwards compatibility
Map<string,string>remains fully supported — it is coerced to a schema-level-only router. All 160 existing tests pass unchanged.Tests
12 new tests (
__tests__/router.test.ts): router unit behaviour (override/fallback/namespace independence/fully-moved reporting), table+function fan-out from one schema, cross-object refs in SQL and PL/pgSQL bodies,DROProuting by namespace,assumeSchemasExistwith a router (CREATE SCHEMA IF NOT EXISTS), and Map/router output parity. Round-trip validation enabled on the full-module test.Notes
pnpm run buildsucceeds.pnpm run lintfails repo-wide with a pre-existing ESLint 9 vs legacy.eslintrc.jsonmismatch (couldn't find an eslint.config.(js|mjs|cjs)), unrelated to this change.constructive-io/constructive(pgpm object-level apply routing) — PR linked separately.Link to Devin session: https://app.devin.ai/sessions/025fb88043964fdbb335ac5e39df2478
Requested by: @pyramation