Skip to content

fix: Harden parser, schema input, layout, diff, and CLI output gating#133

Merged
mhiro2 merged 9 commits into
mainfrom
fix/correctness-and-safety-improvements
May 2, 2026
Merged

fix: Harden parser, schema input, layout, diff, and CLI output gating#133
mhiro2 merged 9 commits into
mainfrom
fix/correctness-and-safety-improvements

Conversation

@mhiro2

@mhiro2 mhiro2 commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • COMMENT ON COLUMN with an unsupported parts shape is now reported as a parse_unsupported diagnostic instead of crashing the parser via unreachable!.
  • Schema input pipeline forwards structural validation as warnings and no longer reaches for block_in_place, which would panic on a current-thread Tokio runtime.
  • Spatial-grid layout repulsion accumulates forces in deterministic order, eliminating run-to-run jitter caused by HashMap iteration combined with floating-point non-associativity.
  • sqlx connect errors are scrubbed of DSN credentials before being logged; the surfaced connect_error continues to use the existing redaction path.
  • --out now writes its artifact before --fail-on-warning / --deny are evaluated, so a warning no longer leaves the requested file empty.
  • Column, FK, and index diffs treat column identifiers case-insensitively and unnamed-index keys are length-prefixed, removing two classes of spurious diff entries.
  • Misleading 0..min(input.len(), 100) fallback span on tokenize/parse errors is dropped, the deprecated parse_schema shim is removed, and unused codes::lint_no_pk / codes::lint_orphan_table are deleted in favor of the canonical LintRuleId::diagnostic_code() mapping.

Changes

  • 384e595 : fix(parser): replace COMMENT ON COLUMN unreachable! with diagnostic
    • Replace the _ => unreachable!(...) arm in parse_comment with an explicit fallback that emits a parse_unsupported warning and returns, so malformed COMMENT ON COLUMN statements no longer panic the parser.
  • 824342c : fix(app): surface schema validation in input pipeline and drop unsafe runtime fallback
    • Wire Schema::validate() into schema_from_input_with_context and translate each ValidationError into a SCHEMA-4 warning diagnostic alongside an existing schema load.
    • Remove the Handle::try_current() / block_in_place branch from schema_from_db_url, which would panic when called from a current-thread Tokio runtime.
  • 8e32f0f : fix(introspect): sanitize sqlx connect errors before logging
    • Run sqlx error strings through sanitize_error_message in the Postgres/MySQL/SQLite connect-failure handlers so DSN credentials cannot leak into structured logs even when the user's URL embeds them.
  • bb3e203 : fix(layout): make spatial-grid repulsion deterministic
    • Collect grid-driven repulsion forces into a Vec, sort by node id, then apply, mirroring resolve_force_overlaps so layouts no longer drift across runs due to HashMap iteration order.
  • 3aa8eb4 : fix(cli): write output before evaluating diagnostic gates
    • Reorder write_output ahead of check_diagnostics / check_diagnostics_at_or_above in render, diff, doc, export, inspect, and lint so --out is honored even when --fail-on-warning or --deny ultimately trips the exit code.
  • 42f1230 : fix(diff): treat column references case-insensitively
    • Lowercase column names when keying diff_columns_with and when forming FK column pairs, so identifier case differences no longer surface as add/remove or modified entries.
    • Rebuild the unnamed-index key with push_key_part length prefixes, fixing a pre-existing collision where [\"a_b\", \"c\"] and [\"a\", \"b_c\"] shared the same idx_a_b_c bucket.
  • a19b8d4 : fix(parser): drop misleading SourceSpan fallback for parse errors
    • Remove the SourceSpan::new(0, input.len().min(100)) fallback that pointed at the start of input rather than the actual error site, attaching only the resolved span when one is available.
  • 0de8e85 : refactor(parser): remove deprecated parse_schema shim
    • Delete the #[deprecated] parse_schema re-export; no in-tree callers remain and the project's policy disallows backwards-compat shims for the public library.
  • b632287 : refactor(core): drop unused lint diagnostic code shims
    • Remove codes::lint_no_pk and codes::lint_orphan_table, which duplicated and had drifted from the canonical numbering provided by LintRuleId::diagnostic_code().
    • Update the affected output tests to call LintRuleId::OrphanTable.diagnostic_code() directly so they reflect the live LINT004 mapping.

mhiro2 added 9 commits May 3, 2026 05:47
The wildcard arm in `parse_comment` panicked via `unreachable!` if the
table-parts slice ever turned out to be empty. The early `parts.len() <
2` guard makes that flow unreachable today, but the implicit invariant
is fragile against future refactors and violates the workspace policy
of avoiding panics in production paths. Emit a `parse_unsupported`
warning and bail out instead so unexpected `ObjectName` shapes degrade
gracefully rather than aborting the CLI/WASM process.
… runtime fallback

`Schema::validate()` was previously exercised only by unit tests, so
structural inconsistencies (duplicate tables, FKs to unknown
columns/tables, malformed enums, etc.) silently slipped through into
downstream stages. Run validation on every successfully loaded schema
and translate each `ValidationError` into a `SCHEMA-4` warning
diagnostic so the CLI/WASM/action surfaces them like any other
parser-level issue.

Also remove the `block_in_place` branch from `schema_from_db_url`. The
sync entry point is only used by the CLI, which never runs inside an
existing Tokio runtime, and the fallback would have panicked on a
current-thread runtime if any future async caller picked it up. Async
callers should use `schema_from_db_url_async` instead.

Two existing fixtures intentionally point a foreign key at a removed
target to exercise lock-risk rules; lock down their new validation
warnings with `expected_diagnostics.json`.
`tracing::error!(error = %e, ...)` previously rendered the raw
`sqlx::Error` Display, which can include the original DSN for the
`Configuration`, `Tls`, and `Io` variants. That meant a credential-
bearing URL could leak into CI logs or downstream SIEM systems even
though the structured `IntrospectError` returned to the caller was
already sanitized.

Pipe the rendered error message through the existing
`crate::url::sanitize_error_message` before emitting the log line so
all three drivers strip credentials and query-string secrets at the
logging boundary, matching the protection already applied to the
returned error.
`compute_repulsion_with_grid` walked the `HashMap<(i32, i32),
Vec<usize>>` directly and accumulated repulsion forces in iteration
order. Because floating-point addition is not associative, the
resulting `forces` vector — and therefore the rendered SVG/HTML — was
sensitive to the per-process `RandomState` seed once a graph crossed
the 64-node spatial-grid threshold.

Mirror the pattern already used by `resolve_force_overlaps`: collect
forward-only candidate pairs into a `Vec`, sort with `sort_unstable`,
then apply repulsion in that fixed order. The graph traversal is
unchanged (each pair still appears at most once thanks to the four
forward-neighbour offsets), but the per-pair traversal sequence is now
independent of `HashMap` iteration order, restoring byte-for-byte
determinism for snapshots, CI, and the playground.
Reordering keeps --out and --fail-on-warning/--deny independent: previously a
warning aborted the command before write_output ran, leaving --out empty even
though the requested artifact had been produced successfully.
Column names in column, FK, and index diffs are now lowercased before keying
and comparison, so identifier case differences no longer manifest as spurious
add/remove/modified entries. The unnamed-index key is also rebuilt with a
length-prefixed encoding to avoid the existing '_'-join collision (e.g.,
['a_b', 'c'] vs ['a', 'b_c']).
The fallback span (0..min(input.len(), 100)) pointed at the start of the
input rather than the actual error site, which made diagnostics misleading.
Now we either attach the resolved span when available or omit the span
entirely so the message stands on its own.
The shim has had no remaining callers in the workspace. Per the project's
no-back-compat-for-public-library policy, drop it instead of carrying it
forward.
codes::lint_no_pk and codes::lint_orphan_table duplicated the canonical
mapping in LintRuleId::diagnostic_code() and were already drifting (the
shims encoded an older numbering: LINT001/LINT002 instead of the current
LINT001/LINT004). Remove both shims and update the affected output tests
to call the canonical accessor directly.
@mhiro2 mhiro2 self-assigned this May 2, 2026
@mhiro2 mhiro2 added bug Something isn't working refactor labels May 2, 2026
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown

Code Metrics Report

main (9f42bad) #133 (ffad881) +/-
Coverage 94.8% 94.9% +0.0%
Test Execution Time 1m33s 1m34s +1s
Details
  |                     | main (9f42bad) | #133 (ffad881) |  +/-  |
  |---------------------|----------------|----------------|-------|
+ | Coverage            |          94.8% |          94.9% | +0.0% |
  |   Files             |             81 |             81 |     0 |
  |   Lines             |          39201 |          39233 |   +32 |
+ |   Covered           |          37186 |          37238 |   +52 |
- | Test Execution Time |          1m33s |          1m34s |   +1s |

Code coverage of files in pull request scope (94.5% → 94.6%)

Files Coverage +/- Status
crates/relune-app/src/schema_input.rs 86.1% +1.3% modified
crates/relune-app/src/usecases/inspect.rs 95.7% +2.5% affected
crates/relune-cli/src/commands/diff.rs 92.6% 0.0% modified
crates/relune-cli/src/commands/doc.rs 80.9% +4.7% modified
crates/relune-cli/src/commands/export.rs 92.1% 0.0% modified
crates/relune-cli/src/commands/inspect.rs 100.0% 0.0% modified
crates/relune-cli/src/commands/lint.rs 78.3% +1.3% modified
crates/relune-cli/src/commands/render.rs 98.4% 0.0% modified
crates/relune-cli/src/output.rs 94.5% +0.0% modified
crates/relune-core/src/diagnostic.rs 89.2% +2.7% modified
crates/relune-core/src/diff.rs 94.4% +0.0% modified
crates/relune-core/src/lint.rs 95.4% +0.3% affected
crates/relune-core/src/model.rs 97.0% +0.4% affected
crates/relune-introspect/src/mysql/mod.rs 84.7% -1.9% modified
crates/relune-introspect/src/postgres/mod.rs 72.9% -2.1% modified
crates/relune-introspect/src/sqlite/mod.rs 82.5% -2.2% modified
crates/relune-layout/src/layout.rs 95.8% +0.0% modified
crates/relune-parser-sql/src/lib.rs 93.3% -0.3% modified

Reported by octocov

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown

Schema review

Tip

✅ No risk findings — schema changes look safe to merge.

@mhiro2
mhiro2 merged commit d4ccc77 into main May 2, 2026
6 checks passed
@mhiro2
mhiro2 deleted the fix/correctness-and-safety-improvements branch May 2, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant