mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat(base): add baseSchemaName helper for duckdb schema naming
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { baseSchemaName } from './schema-name';
|
||||
|
||||
describe('baseSchemaName', () => {
|
||||
it('converts a uuid to a DuckDB-safe identifier with a b_ prefix', () => {
|
||||
expect(baseSchemaName('019c69a5-1d84-7985-a7f6-8ee2871d8669')).toBe(
|
||||
'b_019c69a51d847985a7f68ee2871d8669',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects a non-uuid string (preserves the quoting contract)', () => {
|
||||
expect(() => baseSchemaName('not-a-uuid')).toThrow(/invalid base id/i);
|
||||
expect(() => baseSchemaName('')).toThrow(/invalid base id/i);
|
||||
expect(() => baseSchemaName('b_019c69a5; DROP TABLE rows; --')).toThrow(
|
||||
/invalid base id/i,
|
||||
);
|
||||
});
|
||||
|
||||
it('is deterministic', () => {
|
||||
const id = '019c70b3-dd47-7014-8b87-ec8f167577ee';
|
||||
expect(baseSchemaName(id)).toBe(baseSchemaName(id));
|
||||
});
|
||||
|
||||
it('accepts mixed-case hex and normalises to lowercase', () => {
|
||||
expect(baseSchemaName('019C69A5-1D84-7985-A7F6-8EE2871D8669')).toBe(
|
||||
'b_019c69a51d847985a7f68ee2871d8669',
|
||||
);
|
||||
});
|
||||
|
||||
it('produces names that parse as SQL identifiers without quoting', () => {
|
||||
const name = baseSchemaName('019c69a5-1d84-7985-a7f6-8ee2871d8669');
|
||||
// Must match DuckDB's unquoted-identifier grammar: [a-zA-Z_][a-zA-Z0-9_]*
|
||||
expect(name).toMatch(/^[a-zA-Z_][a-zA-Z0-9_]*$/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// Matches the UUID regex pattern in `loader-sql.ts`. We use a handwritten
|
||||
// regex rather than importing `validate` from the `uuid` package because
|
||||
// that package is ESM-only and Jest's ts-jest config cannot transform it
|
||||
// in this repo.
|
||||
const UUID =
|
||||
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
||||
|
||||
const UUID_DASHES = /-/g;
|
||||
|
||||
/*
|
||||
* Turns a base UUID into a DuckDB-safe schema name.
|
||||
*
|
||||
* '019c69a5-1d84-7985-a7f6-8ee2871d8669'
|
||||
* -> 'b_019c69a51d847985a7f68ee2871d8669'
|
||||
*
|
||||
* The `b_` prefix is required because DuckDB unquoted identifiers must start
|
||||
* with a letter or underscore — a bare hex UUID starts with a digit and would
|
||||
* have to be double-quoted everywhere. The strip-dashes step makes the rest
|
||||
* of the identifier hex-only, which is always safe.
|
||||
*
|
||||
* All attached database names, `DETACH DATABASE` targets, and schema-qualified
|
||||
* references (`<schema>.rows`) run through this function. Validation is
|
||||
* strict: if the input isn't a real UUID, we throw rather than produce a
|
||||
* "safe-looking" identifier that might leak through to user-facing SQL.
|
||||
*/
|
||||
export function baseSchemaName(baseId: string): string {
|
||||
if (!UUID.test(baseId)) {
|
||||
throw new Error(`Invalid base id "${baseId}"`);
|
||||
}
|
||||
return `b_${baseId.toLowerCase().replace(UUID_DASHES, '')}`;
|
||||
}
|
||||
Reference in New Issue
Block a user