Skip to content

import-runner's sanitizeRowError keeps its own three-dialect unique regexes — deferred from #6250 to avoid a merge race with #4633 #6544

Description

@os-project-manager

Blocked-by: #6250 (the shared predicate must land first — PR #6541)

Filed by the #6250 developer agent. This consumer was in #6250's declared scope and was removed mid-task by a PM scope narrowing, so it is recorded here rather than silently dropped.

Why it was deferred (not forgotten)

#4633 was dispatched in parallel and its consuming half lands squarely in the import path, packages/rest/src/import-runner.ts included. Two agents editing that file is a merge race that costs more than serialising. The migration was always the incidental half of #6250, never the user-visible fix — that half (MySQL conflicts returning 409 UNIQUE_VIOLATION) shipped in PR #6541.

What is left

sanitizeRowError in import-runner.ts:

const unique =
  /unique constraint failed:\s*([^\s,)]+)/i.exec(msg) ??          // sqlite
  /duplicate entry .* for key '([^']+)'/i.exec(msg) ??            // mysql
  /duplicate key value violates unique constraint.*?[Kk]ey \(([^)]+)\)/is.exec(msg); // postgres

Correction to #6250's inventory, for whoever picks this up: that issue's table records this row as "一条 Postgres 专用正则 ... 仅 Postgres". That has drifted. All three dialect limbs have been present since #3572 (git log -S "duplicate entry .* for key" confirms) — the line number the issue cites is the third limb of a three-way chain, not the whole of it. The row is still a private vocabulary; it is just not the narrowest one.

Why this is not a mechanical swap

This site does more than answer yes/no: it extracts the offending column so the importer can say "A record with this email already exists." isUniqueViolationError deliberately answers the boolean only — #6250's ruling put the structured conflict-column export out of scope as a new contract surface.

So this migration needs one of:

  • (a) keep the column-extracting regexes and use the predicate only as the gate — buys little, and leaves the vocabulary forked; or
  • (b) land a uniqueViolationColumn(error) sibling in @objectstack/types first, which is the actual convergence. PR fix(types,rest): one named unique-violation predicate — MySQL conflicts return 409 UNIQUE_VIOLATION, not 500 (#6250) #6541's structure already separates the channels per dialect, so the shape slots in without restructuring — but it is a contract decision, not a refactor: when a dialect names an index (for key 'idx_email_unique') rather than a column (Postgres' Key (email)=(...)), which does the export promise? A caller that renders "already exists" into a form field needs the column; an index name mis-rendered there is worse than no answer.

Recommend (b), and note that #5495's autonumber-retry branch wants the same export for a different reason (retry only when the conflicting column IS the autonumber field), so the two callers should agree on the answer before either is built. That decision is the maintainer's, per #6250's ruling — this card should not settle it unilaterally.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions