Skip to content

feat(cli): port supabase inspect report to native TypeScript#5565

Open
Coly010 wants to merge 4 commits into
developfrom
cli/port-inspect-report
Open

feat(cli): port supabase inspect report to native TypeScript#5565
Coly010 wants to merge 4 commits into
developfrom
cli/port-inspect-report

Conversation

@Coly010

@Coly010 Coly010 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Promotes inspect report — the last inspect leaf still on the Phase 0 Go proxy — to a native TypeScript port in the legacy shell (CLI-1317). All 13 active inspect db subcommands + 12 deprecated aliases were already native (#5554); this finishes the inspect tree.

What it does

Runs every inspect query against the target Postgres database via server-side COPY (<query>) TO STDOUT WITH CSV HEADER, writes one CSV per query into <output-dir>/<YYYY-MM-DD>/ (14 files), then renders a Go-parity Glamour RULE | STATUS | MATCHES summary table validating those CSVs.

How it's built

  • copyToCsv on the shared driver (legacy-db-connection.sql-pg.layer.ts): @effect/sql-pg doesn't expose the COPY protocol, so the session opens one raw pg connection (via pg-copy-streams) lazily on the first copy and reuses it for all queries — against the same dial target the primary connection won (TLS/fallback/DoH/step-down parity preserved), and closed by a scope finalizer. This mirrors Go running every copy on a single pgconn. CSVs are byte-identical by construction (the server serializes the values, never the TS side).
  • Bounded csvq-subset evaluator (report.csvq.ts): there is no JS port of csvq and neither DuckDB nor alasql accept its dialect, so the rule queries are evaluated by a hand-written tokenizer + recursive-descent parser + RFC4180 CSV reader that replicates csvq's value/type-comparison semantics (numeric-vs-string promotion, three-valued logic, LISTAGG, aggregates). Unsupported grammar / unknown column → the rule's STATUS cell, not a command failure (matching Go).
  • Custom rules: [experimental.inspect.rules] from config.toml (with env(VAR) expansion) replace the 7 embedded defaults when present.
  • Hoists the shared inspect base layer into inspect/inspect.layers.ts (now used by both db leaves and report); adds legacy/output/legacy-bold.ts for lipgloss Bold parity.

Reviewer context

  • Strict Go parity is the deciding standard: the default rule "No large tables waiting on autovacuum" references s.tbl, which vacuum_stats emits as name — a pre-existing quirk in Go's rules.toml that surfaces as an unknown-column STATUS cell. It's preserved verbatim (documented in report.rules.ts + SIDE_EFFECTS.md); tests assert this Go-faithful behavior rather than "fixing" it.
  • The $2 database literal is not escaped, matching Go's fmt.Sprintf("'%s'", database).
  • json/stream-json output modes are a TS-only addition (Go is text-only); CSVs are still written.
  • New direct deps pg + pg-copy-streams are needed for the COPY protocol; verified to bundle under bun build --compile.
  • The issue listed a --project-ref flag that does not exist on the Go inspect tree — the surface is --db-url / --linked / --local / --output-dir, matching Go.

Closes CLI-1317

Promotes the last `inspect` leaf from a Phase 0 Go proxy to a native TS
port (CLI-1317). The command runs every inspect query via server-side
`COPY (...) TO STDOUT WITH CSV HEADER`, writes 14 CSVs under
`<output-dir>/<YYYY-MM-DD>/`, then renders a Go-parity Glamour rules
summary table validating those CSVs.

- Extend the shared `LegacyDbConnection` driver with `copyToCsv`, which
  reuses a single raw `pg` connection (via `pg-copy-streams`) for all
  copies against the winning dial target, matching Go's single `pgconn`.
- Add a bounded csvq-subset evaluator (`report.csvq.ts`) replicating
  csvq value/type-comparison semantics for the inspect rule grammar.
- Support custom `[experimental.inspect.rules]` from config.toml with
  `env(VAR)` expansion; default to the 7 embedded rules otherwise.
- Hoist the shared inspect base layer into `inspect/inspect.layers.ts`.
- Add `legacy/output/legacy-bold.ts` (lipgloss Bold parity).
- json/stream-json output modes are a TS-only addition (Go is text-only).
@Coly010 Coly010 requested a review from a team as a code owner June 12, 2026 12:01
@Coly010 Coly010 self-assigned this Jun 12, 2026
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Supabase CLI preview

npx --yes https://pkg.pr.new/supabase@5565

Preview package for commit 23a598e.

@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: e2850b07d8

ℹ️ 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".

Comment on lines +353 to +355
// plain column reference
const col = this.parseColRef();
return { column: col };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve csvq expressions in custom rules

When a project has custom [experimental.inspect.rules] using normal csvq SELECT expressions (for example SELECT COALESCE(name, '-') FROM ... or other functions/operators), the Go implementation passes r.Query directly to the csvq driver and scans the first column, but this parser only accepts an aggregate call or a bare column reference. Those valid custom rules now render a parser error in the STATUS cell instead of evaluating to pass/fail, so existing report rules outside the built-in subset regress.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The parity description is accurate: Go hands r.Query straight to the real csvq driver (apps/cli-go/internal/inspect/report.go), so a custom rule using full csvq syntax — COALESCE, CASE, joins, subqueries, etc. — evaluates to a real pass/fail there. This evaluator implements only a bounded subset (SELECT <agg|bare-column> FROM \csv` [WHERE ...]), so an out-of-subset custom rule surfaces a parser-error STATUS cell instead of a result. The 7 built-in default rules (templates/rules.toml`) are all inside the supported grammar and test-covered, so there is no regression for them; custom rules outside the subset do diverge from Go.

This is a deliberate, documented limitation (see the header of report.csvq.ts): there is no JS/TS port of mithrandie/csvq, and neither DuckDB nor alasql accepts csvq's dialect, so closing the gap fully means reimplementing the csvq expression dialect in TS — a large piece of work, not a quick follow-up. Flagging this as a scope decision for a maintainer rather than silently absorbing the divergence or pulling a full csvq reimplementation into this PR; leaving the thread open. Options:

  • (a) accept the bounded subset as the documented limit and track full csvq support as a follow-up issue;
  • (b) detect out-of-subset queries and emit a clearer "unsupported csvq expression" STATUS rather than a raw parser error;
  • (c) treat full custom-rule parity as in-scope here (large reimplementation; own PR).

Comment thread apps/cli/src/legacy/commands/inspect/report/report.config.ts Outdated
Coly010 added 2 commits June 12, 2026 13:22
Promoting `inspect report` from a Go proxy to a native TS handler exposed two
e2e mock gaps (the parity tests previously compared Go-vs-Go and passed
trivially):

- The native driver eagerly forces its lazy `pg` connection with a simple
  `SELECT 1` probe; the pg-mock rejected it in the empty state, so the command
  failed to connect. A real Postgres always answers `SELECT 1` — make the mock
  do so too, regardless of fixture state.
- The rules summary is computed by csvq over COPY CSV content the mock cannot
  emit (it returns an empty, header-less COPY for every query). On those empty
  CSVs Go's csvq panics with an "index out of range" fatal error printed into
  each STATUS cell, while the native TS evaluator reports a clean
  "unknown column" — so stdout is not faithfully comparable. Add a documented
  `compareStdout` opt-out to runParity and use it for the report parity test;
  exit code, stderr, request log, and the 14 written CSVs are still compared,
  and the rules-table rendering is covered by the apps/cli unit + integration
  tests against real fixtures.
…ules

Go loads `[experimental.inspect].rules` via `viper.UnmarshalExact` without
disabling `WeaklyTypedInput` (config.go:579-584), so mapstructure coerces
scalar rule fields to strings (123 → "123", true → "1") and aborts only when an
array entry is not a table or a field is a non-coercible type. The TS reader was
coercing non-strings to "" and silently skipping non-table entries, so a broken
custom rule could replace the defaults and let the command succeed with empty
rules.

Coerce scalar fields the same way Go does, and fail with LegacyDbConfigLoadError
on a non-table entry or a non-coercible field — matching Go's "expected a map or
struct" / "expected type string" decode errors. (review: report.config rule validation)

@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: 32524fe2eb

ℹ️ 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".

Comment thread apps/cli/src/legacy/commands/inspect/report/report.config.ts Outdated
Comment thread apps/cli/src/legacy/commands/inspect/report/report.handler.ts Outdated
Comment on lines +450 to +457
const chunks: Array<Buffer> = [];
stream.on("data", (chunk: Buffer) => chunks.push(chunk));
stream.on("error", (error: Error) =>
resume(
Effect.fail(new LegacyDbCopyError({ message: `failed to copy output: ${error}` })),
),
);
stream.on("end", () => resume(Effect.succeed(new Uint8Array(Buffer.concat(chunks)))));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stream COPY output instead of buffering it

For databases with many tables/indexes, report CSVs such as table or index stats can become large, but this accumulates every COPY chunk in memory, concatenates the full buffer, and the caller then keeps those bytes for rule evaluation. The Go implementation streams CopyTo directly into the output file, so this port can exhaust the Bun process heap or stall on a large report where the Go CLI would complete with bounded memory.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right that Go streams CopyTo straight into the file (internal/inspect/report.go:64-77) and re-reads the CSVs from disk for the rules check (sql.Open("csvq", outDir), report.go:97), so its peak memory is bounded, whereas this port buffers each COPY via Buffer.concat and pins the CSVs in an in-memory map for rule evaluation.

This is purely a resource/robustness difference, not an output one — the CSV bytes, the rules table, and the exit code are byte-identical to Go on every observable dimension (the server serializes the CSV; the TS side never touches the values), and the report runs a fixed set of catalog-stat queries rather than streaming row data, so the OOM risk is confined to pathologically large databases. Matching Go's streaming means reshaping the copyToCsv driver seam (take an output path / return a write sink) and switching the rule evaluator to read CSVs back from disk — and that seam has already changed a couple of times this review cycle. I'd rather land the byte-identical behavior now and do the streaming rework as a focused follow-up with its own tests; tracking it rather than folding it in here. Leaving this thread open for that follow-up.

Comment thread apps/cli/src/legacy/commands/inspect/report/report.config.ts Outdated
Three parity gaps in how custom `[experimental.inspect.rules]` are loaded, all
grounded in Go's `UnmarshalExact` (config.go:579) under viper's default
`WeaklyTypedInput: true` and its `PersistentPreRunE` config load:

- Validate the rule config BEFORE connecting or writing CSVs. Go loads/validates
  the whole config in PersistentPreRunE (via ParseDatabaseConfig → LoadConfig),
  so a malformed config aborts with zero side effects. The reader now runs at the
  top of the handler, before mkdir/connect/COPY (rules are still applied in the
  summary step). (review: validate-before-COPY)

- Reject a non-array `rules` value the way Go's decodeSlice does under weak
  typing: a scalar (`rules = "foo"`) aborts ("expected a map or struct"); a single
  inline table is wrapped into one rule; an empty table yields no custom rules.
  Previously any non-array silently fell through to the defaults. (review: non-array rules)

- Reject unknown/misspelled keys in a rule table. UnmarshalExact sets mapstructure
  ErrorUnused per-struct, so `fails = "bad"` aborts the load; the reader now fails
  with LegacyDbConfigLoadError instead of silently ignoring extra keys. (review: unknown keys)

Adds unit coverage (unknown key, single inline table, scalar rules) and an
integration test asserting a malformed config aborts before any connection or CSV.
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