Skip to content

fix(nextly): the last two reads outside the pipeline resolve like a write - #1732

Closed
mobeenabdullah wants to merge 1 commit into
mainfrom
fix/the-last-two-pinned-schemas
Closed

mobeenabdullah wants to merge 1 commit into
mainfrom
fix/the-last-two-pinned-schemas

Conversation

@mobeenabdullah

Copy link
Copy Markdown
Collaborator

Finishes finding:pg-schema-pinned-in-seven-places. #1721 took the three reads in
the schema pipeline; these are the last two that are free to touch.

site what a wrong answer does
schema/utils/missing-columns.ts reports every column as missing, so the caller tries to add columns the table already has
services/system/system-table-service.ts reports an existing table as absent, so the caller sets about creating one it already has

Both now use the rule #1721 established: resolve the relation an unqualified
statement reaches, rather than naming a schema. Nothing changes for a database on
public, which is the default.

One rule, two forms — not two rules

These two build raw SQL text for the driver; the pipeline reads compose
Drizzle sql templates. Rather than write the predicate twice, the module now
holds it once as text and derives the Drizzle fragment from that:

export const PG_RELATION_THE_WRITES_HIT_SQL = `format('%I.%I', …)::regclass = to_regclass(quote_ident(c.table_name))`;
export const PG_RELATION_THE_WRITES_HIT: SQL = sql.raw(PG_RELATION_THE_WRITES_HIT_SQL);

sql.raw over a module constant interpolates nothing — the text is fixed at
build time and names only catalog columns — so it carries no injection surface.
A rule copied between the two forms would drift the first time either was
corrected, which is the defect this whole finding is about.

Evidence

A test asserts the SQL the adapter is handed, not the source text of the
module that built it — missing-columns.test.ts already has a fake adapter that
records queries, so the assertion sits on the value the server would see.

Break-verified twice, each failing only that test:

mutation result
re-pin the read to 'public' fails
drop quote_ident, so a capitalised name folds away fails

Not here

Two of the seven sites stay on the finding:

Gates

pnpm build 0 · nextly check-types 0 lint 0 vitest 0 (935 files, 11,874
tests) · fallow audit pass, every *_introduced 0 over 5 files ·
changeset status 0.

Worth noting: the integration probe added in #1721/#1727 has now executed for the
first time and passed on all three dialects, so the search-path rule these
reads adopt is verified against a real PostgreSQL rather than only reasoned about.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 22 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: f855c2f7-f1f2-4a70-a79f-c38527902bde

📥 Commits

Reviewing files that changed from the base of the PR and between 3b4b269 and ee0c1ff.

⛔ Files ignored due to path filters (1)
  • .changeset/the-last-two-pinned-schemas.md is excluded by !.changeset/**
📒 Files selected for processing (4)
  • packages/nextly/src/domains/schema/pipeline/pg-visible-relation.ts
  • packages/nextly/src/domains/schema/utils/__tests__/missing-columns.test.ts
  • packages/nextly/src/domains/schema/utils/missing-columns.ts
  • packages/nextly/src/services/system/system-table-service.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-10T14:55:41.649875Z ee0c1ff Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee0c1fffd6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* sequence-ownership check in `introspect-live.ts` already uses.
*/
export const PG_RELATION_THE_WRITES_HIT: SQL = sql`format('%I.%I', c.table_schema, c.table_name)::regclass = to_regclass(quote_ident(c.table_name))`;
export const PG_RELATION_THE_WRITES_HIT_SQL = `format('%I.%I', c.table_schema, c.table_name)::regclass = to_regclass(quote_ident(c.table_name))`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the relation predicate in Drizzle

This converts the existing Drizzle fragment into a raw SQL string and exposes that string for interpolation into two production driver queries. Even though this particular text is constant, it bypasses the repository's enforced Drizzle-only database-access boundary; keep the shared predicate as a Drizzle fragment and migrate these callers to execute composed Drizzle statements instead of introducing a raw-SQL API.

AGENTS.md reference: AGENTS.md:L274-L276

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, and I have closed the PR rather than working around it. Exporting the predicate as a raw string publishes a raw-SQL API, which is worse than the bug it fixes. Measured the migration you suggest: missing-columns.ts builds every statement as a string for executeQuery including its ALTER, system-table-service.ts holds 21 raw SQL strings across three dialect maps, and my test's fake adapter observes executeQuery — so it is a module-level migration, not a predicate change. Filed as its own task with those measurements. Inlining the predicate at both sites would have avoided the raw API and created two spellings of one rule, which is the defect this finding is about, so I did not take that route either.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

Closing this — the finding is right and the fix belongs in a different change.

What I got wrong. To reach two raw-SQL queries I exported the predicate as a
raw string. Even constant, that publishes a raw-SQL API and makes the non-Drizzle
path a shared, blessed one. The rule exists to keep a single query-building path,
and a new export is exactly what erodes it.

Why I am not just doing the suggested migration here. I measured it:

  • missing-columns.ts builds every statement as a string for
    adapter.executeQuery, including its ALTER TABLE. Moving one function to
    Drizzle leaves the file with two access paths.
  • system-table-service.ts holds 21 raw SQL strings across three dialect
    maps, checkTable among them, and is raw-SQL by construction.
  • My test's instrument is a fake adapter that records executeQuery; moving the
    read to Drizzle changes what it can observe.

So the honest scope is "migrate these two modules to Drizzle, then apply the
search-path rule", not "fix two predicates". Bundling those would mix a bug fix
with an access-layer migration in one diff.

The alternative I rejected: inlining the predicate at both sites instead of
exporting it. That avoids the raw-SQL API and creates two spellings of one rule —
which is the defect the whole finding is about.

What happens now. Filed as task:drizzle-then-schema-pin-the-last-two-reads,
with this reasoning and the measurements on it. The two failure modes are bounded
and neither is silent data loss — the column read reports every column missing, so
the following ALTER fails loudly, and the table read drives a
CREATE TABLE IF NOT EXISTS — so deferring is safe in a way the pipeline reads in
#1721 were not.

cli/commands/migrate-fresh.ts stays on finding:pg-schema-pinned-in-seven-places
and is the one to look at next: it lists public's tables in order to DROP them.

@github-actions

Copy link
Copy Markdown
Contributor

Whole-Repository Code Hygiene Summary

Full dead-code, duplication, and complexity report for the PR branch as it stands now. Playground is excluded. Quality gate enforcement on introduced issues is performed by the Changed files job.

🌿 Fallow

Warning

Review needed

⚠️ 73 code issues · ⚠️ 694 clone groups · ⚠️ 1046 health findings

See inline review comments for per-finding details.

Code issues (73)
Category Count
Unused files 2
Unused exports 5
Unused dependencies 19
Unused devDependencies 6
Unresolved imports 2
Unlisted dependencies 1
Circular dependencies 38
Duplication (694 groups · 28997 lines · 4.1%)
Locations Lines Tokens
schemas/_dialect-bundles/mysql.relations.ts:40-134
schemas/_dialect-bundles/postgres.relations.ts:40-134
schemas/_dialect-bundles/sqlite.relations.ts:40-134
95 593
cli/commands/db-sync-demote.ts:70-75
cli/commands/db-sync-promote.ts:38-43
cli/commands/dev-build.ts:100-105
cli/commands/dev-build.ts:179-184
cli/commands/dev-build.ts:299-304
cli/commands/dev-build.ts:411-416
cli/commands/dev-build.ts:552-557
cli/commands/dev-server.ts:575-580
cli/commands/dev-server.ts:840-845
cli/commands/dev-server.ts:1143-1148
cli/commands/migrate-field-groups.ts:110-115
6 70
entries/EntryList/EntryTableSkeleton.tsx:74-98
collection/components/CollectionTableSkeleton.tsx:94-118
field-group/components/FieldGroupTableSkeleton.tsx:90-114
plugins/components/PluginsTableSkeleton.tsx:86-110
singles/components/SinglesTableSkeleton.tsx:77-101
src/components/table-skeleton.tsx:100-124
25 89
collections/config/validate-config.ts:380-433
field-groups/config/validate-field-group.ts:185-238
singles/config/validate-single.ts:190-243
54 152
dispatcher/handlers/collection-dispatcher.ts:922-964
field-groups/services/field-group-table-provisioning.ts:186-236
singles/services/reconcile-single-companion.ts:110-160
51 149

… and 689 more groups.

Across 423 files.

Complexity (1046 functions above threshold)
File Function Severity Cyclomatic Cognitive CRAP Lines
singles/services/single-mutation-service.ts:981 <arrow> critical 251 ! 324 ! 13859.2 ! 1625
collections/services/collection-mutation-service.ts:6209 <arrow> critical 174 ! 177 ! 6713.6 ! 1301
src/init/reload-config.ts:1319 applyReload critical 144 ! 228 ! 4623 ! 1433
shared/lib/entry-validation.ts:223 validateFieldValue critical 109 ! 157 ! 2675.3 ! 432
blocks-engine/src/measure-bytes.ts:646 surveyDocument critical 102 ! 250 ! 137.1 ! 658

4981 files, 76033 functions analyzed (thresholds: cyclomatic > 20, cognitive > 15, CRAP >= 30)

Codebase health

Metric Value
Maintainability 91.7 / 100
Avg complexity 1.8

Tip

Run fallow fix --dry-run to preview auto-fixes.
Add /** @public */ above exports to preserve them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: core nextly type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant