Skip to content

Commit f9f69be

Browse files
committed
fix(cli): keep the unmanaged-table sweep as it was, and state the ceremony change as measured
The sweep is os migrate plan's, not the ceremony's, and the schema boot never plans SqliteWasmDriver, so the change had no reach there. Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
1 parent 91084b5 commit f9f69be

4 files changed

Lines changed: 24 additions & 59 deletions

File tree

‎.changeset/23558-ceremony-dialect-resolver.md‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@
33
'@objectstack/metadata-core': minor
44
---
55

6-
fix(cli): `os migrate organization-ownership` reads its SQL dialect through the driver resolver it shares with metadata-core, `dialectName` first, and refuses an unresolved dialect, naming the driver, before any statement runs
6+
fix(cli): `os migrate organization-ownership` reads its SQL dialect once, through the driver resolver it shares with metadata-core, and refuses an unresolved dialect before any statement, naming the driver, with no SQLite default left
77

88
Clause-②: yes (widening)
99

10-
**Who this is for.** An operator who runs the organization-ownership ceremony (plan or `--apply`) on a database whose driver's knex `config.client` is not a client string the ceremony recognises: a client CLASS (knex's bring-your-own-dialect hatch, and `SqliteWasmDriver`'s own config), or an unrecognised spelling.
10+
**Who this is for.** An operator who runs the organization-ownership ceremony (plan or `--apply`) on a PostgreSQL or MySQL database whose driver's knex `config.client` is a client CLASS (knex's bring-your-own-dialect hatch) or a client string the ceremony does not recognise.
1111

12-
**FROM.** Both arms took the dialect from `String(config.client)` alone.
12+
**FROM.** Both arms took the dialect from `String(config.client)` alone, with a reader of their own.
1313

14-
- A client class was stringified, so on `SqliteWasmDriver` (a `wasm-sqlite://` or `*.wasm.db` database, or the development SQLite fallback) the ceremony refused a SQLite database it can migrate, with `driver-unsupported` and the class's source text as the driver's name.
15-
- The apply step and its template lookup carried a `'sqlite'` default for an unresolved dialect, unreachable today only because the plan, which runs first, refused before them.
14+
- Such a run was refused (`driver-unsupported`, exit 1) by the plan step, which runs first. A client class was stringified, so the refusal printed the class's source text in place of the driver's name.
15+
- The apply step and its template lookup each carried a `'sqlite'` default for an unresolved dialect. Neither was reachable, only because the plan step had already refused.
1616

17-
**TO.** The dialect is resolved once, from the stack's driver, before either arm runs a statement. It uses the same resolver metadata-core's legacy-row reader uses: the driver's `dialectName` first, then a recognised `config.client` string.
17+
**TO.** The dialect is resolved once, from the stack's driver, before either arm runs a statement. The resolver is the one metadata-core's legacy-row reader uses: the driver's `dialectName` first, then a recognised `config.client` string.
1818

19-
- `SqliteWasmDriver` (`dialectName: 'sqlite'`) runs the ceremony as SQLite.
20-
- A client class with no recognised `dialectName`, an unrecognised client string, `dialectName: 'unknown'` (what `SqlDriver` answers for either), and a driver with no dialect at all are refused: `driver-unsupported`, exit 1, no statement run, no plan file written. The refusal names the driver (its name, its `dialectName`, its client string or client class name).
21-
- No `'sqlite'` default remains: the plan and the apply take the resolved dialect and never re-derive it.
19+
- Refused: a client class with no recognised `dialectName`, an unrecognised client string, `dialectName: 'unknown'` (what `SqlDriver` answers for either), and a driver with no dialect at all. Each gets `driver-unsupported`, exit 1, with no statement run and no plan file written. The refusal names the driver: its name, its `dialectName`, and its client string or client class name.
20+
- No `'sqlite'` default remains. The plan and the apply take the resolved dialect and never re-derive it.
2221
- The plan file's `client` field records the client only when it is a string.
23-
- `os migrate plan`'s unmanaged-table sweep reads its catalog dialect through the same resolver. On `SqliteWasmDriver` it now reads the catalog instead of reporting the sweep unreadable.
2422

25-
Every in-repo driver, measured, resolves as before or better: `SqlDriver` on better-sqlite3, pg and mysql2, and `TursoDriver` (remote and file), are unchanged. `SqliteWasmDriver` moves from refused to SQLite.
23+
Measured on each in-repo SQL driver: `SqlDriver` on better-sqlite3, pg and mysql2, and `TursoDriver`, resolve to the dialect they did before.
2624

27-
**metadata-core.** `dialectOfDriver(driver)` is exported. It is the one resolver behind `readLegacyOrganizationRows`: `'sqlite' | 'postgres' | 'mysql'`, `'none'` for a driver that is not a SQL driver, or `'unresolved'`. A `dialectName` string is the whole answer, recognised or not. A `config.client` is read only when there is no `dialectName`, and only as a string.
25+
**metadata-core.** `dialectOfDriver(driver)` is exported. It is the one resolver behind `readLegacyOrganizationRows`, and answers:
26+
27+
- `'sqlite' | 'postgres' | 'mysql'`;
28+
- `'none'` for a driver that is not a SQL driver;
29+
- `'unresolved'` otherwise.
30+
31+
A `dialectName` string is the whole answer, recognised or not. A `config.client` is read only when there is no `dialectName`, and only as a string.

‎packages/cli/src/commands/migrate/organization-ownership.dialect.test.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
* themselves: `SqliteWasmDriver`'s knex `client` is a dialect CLASS and its
1616
* `dialectName` is `'sqlite'`; `SqlDriver` answers `dialectName: 'unknown'`
1717
* for a client it does not recognise, a client class included.
18+
*
19+
* The `SqliteWasmDriver` row pins the resolver's answer for that SHAPE. The
20+
* command's boot does not hand that driver over today (the schema boot looks
21+
* a SQL driver up by service names that do not include it, so a
22+
* `wasm-sqlite://` run is refused earlier, as having no SQL driver at all);
23+
* the row keeps the answer right for the day it does.
1824
*/
1925

2026
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';

‎packages/cli/src/utils/unmanaged-tables.test.ts‎

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -366,43 +366,6 @@ describe('collectUnmanagedTables — every no-answer path is unreadable, not emp
366366
});
367367
});
368368

369-
/**
370-
* The dialect comes from the one resolver the organization-ownership ceremony
371-
* shares (`@objectstack/metadata-core`'s `dialectOfDriver`): `dialectName`
372-
* first, then a recognised client string. The shapes are the drivers' own,
373-
* measured: `SqliteWasmDriver`'s knex `client` is a dialect CLASS and its
374-
* `dialectName` is `'sqlite'`; `SqlDriver` answers `'unknown'` for a client
375-
* class it does not recognise.
376-
*/
377-
describe('collectUnmanagedTables — the dialect is read through dialectName first', () => {
378-
const statements: string[] = [];
379-
const driverOf = (shape: { dialectName?: string; client: unknown }) => ({
380-
managedObjectFields: new Map(),
381-
...(shape.dialectName !== undefined ? { dialectName: shape.dialectName } : {}),
382-
config: { client: shape.client },
383-
execute: async (sql: string) => {
384-
statements.push(sql);
385-
return rows('sys_user');
386-
},
387-
});
388-
const run = (driver: unknown) =>
389-
collectUnmanagedTables({ drivers: [driver], declaredObjects: [], composition: MIRRORED, normalize });
390-
391-
it('reads SqliteWasmDriver (a client class, dialectName sqlite) with the SQLite catalog statement', async () => {
392-
statements.length = 0;
393-
const report = await run(driverOf({ dialectName: 'sqlite', client: class Client_WasmSqlite {} }));
394-
expect(report).toMatchObject({ status: 'read', physicalTables: 1 });
395-
expect(statements).toEqual([physicalTableListSql('sqlite')]);
396-
});
397-
398-
it('a client class whose dialectName is unknown stays unreadable, and nothing is run', async () => {
399-
statements.length = 0;
400-
const report = await run(driverOf({ dialectName: 'unknown', client: class Client_PG {} }));
401-
expect(report.status).toBe('unreadable');
402-
expect(statements).toEqual([]);
403-
});
404-
});
405-
406369
/**
407370
* [#22580] Every database the plan covers. The plan diffs the primary and,
408371
* where the serving boot keeps one, the `telemetry` sibling; a `read` answer

‎packages/cli/src/utils/unmanaged-tables.ts‎

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@
113113
* byte-identical to a reader.
114114
*/
115115

116-
import { dialectOfDriver } from '@objectstack/metadata-core';
117116
import { PLATFORM_OBJECT_PREFIXES, StorageNameMapping, hasPlatformObjectPrefix } from '@objectstack/spec/system';
118117
// The result-set/no-answer predicate `os migrate duplicates` already publishes
119118
// (#10677). Imported rather than re-spelled: a second copy would drift, and the
@@ -371,16 +370,9 @@ async function readPhysicalTableNames(
371370
return { detail: 'the planned driver exposes no raw SQL seam, so its table catalog could not be read' };
372371
}
373372

374-
const named = driver as { dialectName?: unknown; config?: { client?: unknown } } | null | undefined;
375-
const client = named?.config?.client;
373+
const client = (driver as { config?: { client?: unknown } } | null | undefined)?.config?.client;
376374
const clientName = typeof client === 'string' ? client : '';
377-
// The one driver-dialect resolver (`@objectstack/metadata-core`), shared
378-
// with the organization-ownership ceremony: `dialectName` first, so
379-
// `SqliteWasmDriver`, whose knex `client` is a dialect CLASS, is read as the
380-
// SQLite it is; then a recognised client string. Anything else stays
381-
// unreadable, never a guessed catalog statement.
382-
const dialect = dialectOfDriver(named);
383-
const sql = dialect === 'sqlite' || dialect === 'postgres' || dialect === 'mysql' ? physicalTableListSql(dialect) : null;
375+
const sql = physicalTableListSql(clientName);
384376
if (sql === null) {
385377
return {
386378
detail: `the connected dialect (${clientName || 'unnamed client'}) is not one this sweep enumerates tables on `

0 commit comments

Comments
 (0)