Skip to content

Commit 088a1b8

Browse files
committed
refactor(driver-sql): read the autonumber default from the contract, not a hardcoded fallback (#7263)
Execution half 3/3 of the route-3 ruling on #6555. `{0000}` is a declared contract default now (`DEFAULT_AUTONUMBER_FORMAT` / `resolveAutonumberFormat`, landed in `@objectstack/spec/data` by #7265), so this driver stops keeping its own copy of it. `initObjects` and the external-object registration path each spelled the same four lines by hand — canonical `autonumberFormat`, then the `format` shorthand, then `|| '{0000}'`. Both are now `resolveAutonumberFormat(field)`: one symbol added to an import this file already had, no new dependency. Behaviour-neutral by construction and by measurement. The resolver's precedence and its non-empty-string test were taken from these very lines, so a differential check over 484 field documents (both spellings x 22 value shapes) returns the identical string in every case — `format: ''`, `autonumberFormat: ''` and the non-string values included. All eight driver-sql autonumber suites (64 tests) and the cross-side parity integration test stay green unedited; the one test-file change is prose, re-pointing a comment at the declared default while keeping true its note that the engine still emits a bare counter until #7262. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TcsV4ES5JJjCZ9BLrfhTy8
1 parent 06be54e commit 088a1b8

3 files changed

Lines changed: 54 additions & 16 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
"@objectstack/driver-sql": patch
3+
---
4+
5+
refactor(driver-sql): read the autonumber default from the contract instead of a hardcoded fallback (#7263)
6+
7+
Execution half 3/3 of the maintainer's route-3 ruling on #6555. `{0000}` is now a
8+
declared contract default (`DEFAULT_AUTONUMBER_FORMAT`, landed with
9+
`resolveAutonumberFormat` in `@objectstack/spec/data`), so this driver stops
10+
writing the default down for itself.
11+
12+
Two sites in `sql-driver.ts``initObjects` and the external-object
13+
registration path — each spelled the same four lines by hand:
14+
15+
```ts
16+
const rawFmt = (typeof field.autonumberFormat === 'string' && field.autonumberFormat)
17+
? field.autonumberFormat
18+
: (typeof field.format === 'string' && field.format ? field.format : '');
19+
const fmt = rawFmt || '{0000}';
20+
```
21+
22+
Both are now `const fmt = resolveAutonumberFormat(field);`. That is the whole
23+
change: one symbol added to an import this file already had, no new dependency,
24+
and the `#1603` comment about honouring both spellings retired to the resolver's
25+
own docstring, which carries it.
26+
27+
**Behaviour-neutral, by construction and by measurement.** `resolveAutonumberFormat`'s
28+
precedence — canonical `autonumberFormat`, then the `format` shorthand, then the
29+
declared default, with anything that is not a **non-empty string** counting as
30+
undeclared — was deliberately taken from these very lines, including their
31+
truthiness rule (not the engine's `??`). A differential check over 484 field
32+
documents, spanning both spellings across 22 value shapes (absent key,
33+
`undefined`, `null`, `''`, non-empty strings, numbers, booleans, `NaN`, arrays,
34+
objects, a boxed `String`, `Symbol`, function, `BigInt`), found the old
35+
expressions and the resolver returning the identical string in every case —
36+
`format: ''`, `autonumberFormat: ''` and the non-string values included, not just
37+
the happy path.
38+
39+
Compatibility note, per the ruling: choosing {0000} keeps stored driver-sql data
40+
undisturbed; engine-fallback deployments flip from bare 1 to 0001 for newly
41+
issued numbers. Counter continuity itself is unaffected (#6468 pinned it).
42+
43+
The engine half of the same ruling is #7262; #6555 stays open until it lands, so
44+
a format-less field still renders `0001` on SQL and a bare `1` on the engine's
45+
in-memory fallback until then. This half moves neither.

packages/drivers/driver-sql/src/sql-driver-autonumber-suffix.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,12 @@ describe('SqlDriver autonumber seeding — the counter is located by the declare
165165
// lexicographic one — so the counter continues at 11.
166166
//
167167
// The RENDERING of a format-less field is a separate, pre-existing matter
168-
// this fix does not touch: this driver substitutes `{0000}` for a missing
169-
// format (see `initObjects`), so 11 renders `0011` here while the engine's
170-
// fallback emits the bare `11`. That divergence is in the render default,
171-
// not in the seeding parse #6468 is about, so the cross-side parity test
172-
// uses explicitly-formatted fields.
168+
// this fix does not touch: a format-less field resolves to the contract
169+
// default `{0000}` (`resolveAutonumberFormat`, #6555), so 11 renders
170+
// `0011` here — while the engine's fallback still emits the bare `11`
171+
// until #7262 lands the other half. That divergence is in the render
172+
// default, not in the seeding parse #6468 is about, so the cross-side
173+
// parity test uses explicitly-formatted fields.
173174
await initRec();
174175
await seedRows(['1', '2', '10']);
175176

packages/drivers/driver-sql/src/sql-driver.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
import type { DriverOptions, FilterCondition, SchemaMode } from '@objectstack/spec/data';
11-
import { parseAutonumberFormat, renderAutonumber, readAutonumberCounter, missingFieldValues, isTenancyDisabled, type AutonumberToken } from '@objectstack/spec/data';
11+
import { parseAutonumberFormat, renderAutonumber, resolveAutonumberFormat, readAutonumberCounter, missingFieldValues, isTenancyDisabled, type AutonumberToken } from '@objectstack/spec/data';
1212
// The DECLARED aggregate vocabulary (#5907). Read from the spec so this driver's
1313
// "the protocol has no such function" refusal cannot drift from what
1414
// `AggregationNodeSchema.function` actually admits.
@@ -5353,10 +5353,7 @@ export class SqlDriver implements IDataDriver {
53535353
if (type === 'datetime') datetimeCols.push(name);
53545354
if (type === 'time') timeCols.push(name);
53555355
if (type === 'auto_number' || type === 'autonumber') {
5356-
const rawFmt = (typeof field.autonumberFormat === 'string' && field.autonumberFormat)
5357-
? field.autonumberFormat
5358-
: (typeof field.format === 'string' && field.format ? field.format : '');
5359-
const fmt = rawFmt || '{0000}';
5356+
const fmt = resolveAutonumberFormat(field);
53605357
autoNumberCols.push({ name, format: fmt, tokens: parseAutonumberFormat(fmt), tenantField });
53615358
}
53625359
}
@@ -5433,12 +5430,7 @@ export class SqlDriver implements IDataDriver {
54335430
(this.timeFields[tableName] ??= new Set()).add(name);
54345431
}
54355432
if (type === 'auto_number' || type === 'autonumber') {
5436-
// Honor either the spec-canonical `autonumberFormat` or the
5437-
// shorthand `format` (both appear in metadata) — see #1603.
5438-
const rawFmt = (typeof field.autonumberFormat === 'string' && field.autonumberFormat)
5439-
? field.autonumberFormat
5440-
: (typeof field.format === 'string' && field.format ? field.format : '');
5441-
const fmt = rawFmt || '{0000}';
5433+
const fmt = resolveAutonumberFormat(field);
54425434
// Tokenize once: the renderer resolves date tokens (`{YYYYMMDD}`),
54435435
// field interpolation (`{island_zone}`) and the sequence slot at
54445436
// fill time. The counter scopes to whatever renders before the slot.

0 commit comments

Comments
 (0)