@quereus/quereus 0.4.11 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +40 -16
  2. package/dist/src/core/database.d.ts +18 -0
  3. package/dist/src/core/database.d.ts.map +1 -1
  4. package/dist/src/core/database.js +22 -0
  5. package/dist/src/core/database.js.map +1 -1
  6. package/dist/src/func/builtins/conversion.d.ts +51 -0
  7. package/dist/src/func/builtins/conversion.d.ts.map +1 -0
  8. package/dist/src/func/builtins/conversion.js +152 -0
  9. package/dist/src/func/builtins/conversion.js.map +1 -0
  10. package/dist/src/func/builtins/index.d.ts.map +1 -1
  11. package/dist/src/func/builtins/index.js +20 -1
  12. package/dist/src/func/builtins/index.js.map +1 -1
  13. package/dist/src/func/builtins/json.d.ts +1 -0
  14. package/dist/src/func/builtins/json.d.ts.map +1 -1
  15. package/dist/src/func/builtins/json.js +100 -0
  16. package/dist/src/func/builtins/json.js.map +1 -1
  17. package/dist/src/func/builtins/schema.js +1 -1
  18. package/dist/src/func/builtins/schema.js.map +1 -1
  19. package/dist/src/index.d.ts +8 -1
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +6 -0
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/runtime/emit/binary.d.ts.map +1 -1
  24. package/dist/src/runtime/emit/binary.js +6 -2
  25. package/dist/src/runtime/emit/binary.js.map +1 -1
  26. package/dist/src/runtime/emit/constraint-check.d.ts.map +1 -1
  27. package/dist/src/runtime/emit/constraint-check.js +8 -1
  28. package/dist/src/runtime/emit/constraint-check.js.map +1 -1
  29. package/dist/src/runtime/emit/insert.d.ts.map +1 -1
  30. package/dist/src/runtime/emit/insert.js +4 -24
  31. package/dist/src/runtime/emit/insert.js.map +1 -1
  32. package/dist/src/runtime/emit/scalar-function.d.ts +9 -0
  33. package/dist/src/runtime/emit/scalar-function.d.ts.map +1 -1
  34. package/dist/src/runtime/emit/scalar-function.js +23 -1
  35. package/dist/src/runtime/emit/scalar-function.js.map +1 -1
  36. package/dist/src/runtime/emit/unary.d.ts.map +1 -1
  37. package/dist/src/runtime/emit/unary.js +3 -1
  38. package/dist/src/runtime/emit/unary.js.map +1 -1
  39. package/dist/src/schema/column.d.ts +4 -1
  40. package/dist/src/schema/column.d.ts.map +1 -1
  41. package/dist/src/schema/column.js +3 -0
  42. package/dist/src/schema/column.js.map +1 -1
  43. package/dist/src/schema/function.d.ts +15 -0
  44. package/dist/src/schema/function.d.ts.map +1 -1
  45. package/dist/src/schema/function.js.map +1 -1
  46. package/dist/src/schema/table.d.ts.map +1 -1
  47. package/dist/src/schema/table.js +10 -1
  48. package/dist/src/schema/table.js.map +1 -1
  49. package/dist/src/types/builtin-types.d.ts +37 -0
  50. package/dist/src/types/builtin-types.d.ts.map +1 -0
  51. package/dist/src/types/builtin-types.js +359 -0
  52. package/dist/src/types/builtin-types.js.map +1 -0
  53. package/dist/src/types/index.d.ts +7 -0
  54. package/dist/src/types/index.d.ts.map +1 -0
  55. package/dist/src/types/index.js +13 -0
  56. package/dist/src/types/index.js.map +1 -0
  57. package/dist/src/types/json-type.d.ts +8 -0
  58. package/dist/src/types/json-type.d.ts.map +1 -0
  59. package/dist/src/types/json-type.js +143 -0
  60. package/dist/src/types/json-type.js.map +1 -0
  61. package/dist/src/types/logical-type.d.ts +52 -0
  62. package/dist/src/types/logical-type.d.ts.map +1 -0
  63. package/dist/src/types/logical-type.js +34 -0
  64. package/dist/src/types/logical-type.js.map +1 -0
  65. package/dist/src/types/plugin-interface.d.ts +9 -0
  66. package/dist/src/types/plugin-interface.d.ts.map +1 -0
  67. package/dist/src/types/plugin-interface.js +2 -0
  68. package/dist/src/types/plugin-interface.js.map +1 -0
  69. package/dist/src/types/registry.d.ts +72 -0
  70. package/dist/src/types/registry.d.ts.map +1 -0
  71. package/dist/src/types/registry.js +168 -0
  72. package/dist/src/types/registry.js.map +1 -0
  73. package/dist/src/types/temporal-types.d.ts +17 -0
  74. package/dist/src/types/temporal-types.d.ts.map +1 -0
  75. package/dist/src/types/temporal-types.js +178 -0
  76. package/dist/src/types/temporal-types.js.map +1 -0
  77. package/dist/src/types/validation.d.ts +52 -0
  78. package/dist/src/types/validation.d.ts.map +1 -0
  79. package/dist/src/types/validation.js +96 -0
  80. package/dist/src/types/validation.js.map +1 -0
  81. package/dist/src/util/comparison.d.ts +24 -5
  82. package/dist/src/util/comparison.d.ts.map +1 -1
  83. package/dist/src/util/comparison.js +71 -9
  84. package/dist/src/util/comparison.js.map +1 -1
  85. package/dist/src/util/plugin-loader.d.ts.map +1 -1
  86. package/dist/src/util/plugin-loader.js +17 -0
  87. package/dist/src/util/plugin-loader.js.map +1 -1
  88. package/dist/src/vtab/manifest.d.ts +4 -0
  89. package/dist/src/vtab/manifest.d.ts.map +1 -1
  90. package/dist/src/vtab/memory/index.d.ts +1 -0
  91. package/dist/src/vtab/memory/index.d.ts.map +1 -1
  92. package/dist/src/vtab/memory/index.js +15 -4
  93. package/dist/src/vtab/memory/index.js.map +1 -1
  94. package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
  95. package/dist/src/vtab/memory/layer/manager.js +20 -2
  96. package/dist/src/vtab/memory/layer/manager.js.map +1 -1
  97. package/dist/src/vtab/memory/utils/primary-key.d.ts.map +1 -1
  98. package/dist/src/vtab/memory/utils/primary-key.js +17 -12
  99. package/dist/src/vtab/memory/utils/primary-key.js.map +1 -1
  100. package/package.json +5 -4
  101. package/src/core/database.ts +24 -0
  102. package/src/func/builtins/conversion.ts +201 -0
  103. package/src/func/builtins/index.ts +20 -1
  104. package/src/func/builtins/json.ts +121 -0
  105. package/src/func/builtins/schema.ts +1 -1
  106. package/src/index.ts +35 -0
  107. package/src/runtime/emit/binary.ts +8 -2
  108. package/src/runtime/emit/constraint-check.ts +9 -1
  109. package/src/runtime/emit/insert.ts +4 -16
  110. package/src/runtime/emit/scalar-function.ts +27 -1
  111. package/src/runtime/emit/unary.ts +4 -1
  112. package/src/schema/column.ts +8 -1
  113. package/src/schema/function.ts +18 -0
  114. package/src/schema/table.ts +411 -398
  115. package/src/types/builtin-types.ts +350 -0
  116. package/src/types/index.ts +17 -0
  117. package/src/types/json-type.ts +152 -0
  118. package/src/types/logical-type.ts +75 -0
  119. package/src/types/plugin-interface.ts +10 -0
  120. package/src/types/registry.ts +200 -0
  121. package/src/types/temporal-types.ts +167 -0
  122. package/src/types/validation.ts +120 -0
  123. package/src/util/comparison.ts +87 -14
  124. package/src/util/plugin-loader.ts +19 -0
  125. package/src/vtab/manifest.ts +7 -1
  126. package/src/vtab/memory/index.ts +191 -178
  127. package/src/vtab/memory/layer/manager.ts +28 -2
  128. package/src/vtab/memory/utils/primary-key.ts +19 -14
@@ -1,398 +1,411 @@
1
- import type { ColumnSchema } from './column.js';
2
- import type { AnyVirtualTableModule } from '../vtab/module.js';
3
- import { MemoryTableModule } from '../vtab/memory/module.js';
4
- import type { Expression } from '../parser/ast.js';
5
- import { type ColumnDef, type TableConstraint } from '../parser/ast.js';
6
- import { getAffinity } from '../common/type-inference.js';
7
- import { RowOp, SqlDataType, StatusCode, type SqlValue } from '../common/types.js';
8
- import type * as AST from '../parser/ast.js';
9
- import { quereusError, QuereusError } from '../common/errors.js';
10
- import { createLogger } from '../common/logger.js';
11
-
12
- const log = createLogger('schema:table');
13
- const warnLog = log.extend('warn');
14
-
15
- /**
16
- * Represents the schema definition of a table (real or virtual).
17
- */
18
- export interface TableSchema {
19
- /** Table name */
20
- name: string;
21
- /** Schema name (e.g., "main", "temp") */
22
- schemaName: string;
23
- /** Ordered list of column definitions */
24
- columns: ReadonlyArray<ColumnSchema>;
25
- /** Map from column name (lowercase) to column index */
26
- columnIndexMap: ReadonlyMap<string, number>;
27
- /** Definition of the primary key, including order and direction */
28
- primaryKeyDefinition: ReadonlyArray<PrimaryKeyColumnDefinition>;
29
- /** CHECK constraints defined on the table or its columns */
30
- checkConstraints: ReadonlyArray<RowConstraintSchema>;
31
- /** Reference to the registered module */
32
- vtabModule: AnyVirtualTableModule;
33
- /** If virtual, aux data passed during module registration */
34
- vtabAuxData?: unknown;
35
- /** If virtual, the arguments passed in CREATE VIRTUAL TABLE */
36
- vtabArgs?: Record<string, SqlValue>;
37
- /** If virtual, the name the module was registered with */
38
- vtabModuleName: string;
39
- /** Whether the table is a temporary table */
40
- isTemporary?: boolean;
41
- /** Whether the table is a view */
42
- isView: boolean;
43
- /** Whether the table is a subquery source */
44
- subqueryAST?: AST.SelectStmt;
45
- /** If virtual, the view definition */
46
- viewDefinition?: AST.SelectStmt;
47
- /** Table-level constraints */
48
- tableConstraints?: readonly TableConstraint[];
49
- /** Definitions of secondary indexes (relevant for planning) */
50
- indexes?: ReadonlyArray<IndexSchema>;
51
- /** Estimated number of rows in the table (for query planning) */
52
- readonly estimatedRows?: number;
53
- /** Whether the table is read-only */
54
- isReadOnly?: boolean; // default false
55
- /** Mutation context variables for this table */
56
- mutationContext?: ReadonlyArray<MutationContextDefinition>;
57
- /** Foreign key constraints (parsed but not yet enforced by engine) */
58
- // foreignKeys?: ReadonlyArray<ForeignKeyConstraintSchema>;
59
- /** Unique constraints (beyond primary key) */
60
- // uniqueConstraints?: ReadonlyArray<ConstraintSchema>;
61
- }
62
-
63
- /**
64
- * Builds a map from column names to their indices in the columns array
65
- *
66
- * @param columns Array of column schemas
67
- * @returns Map of lowercase column names to their indices
68
- */
69
- export function buildColumnIndexMap(columns: ReadonlyArray<ColumnSchema>): Map<string, number> {
70
- const map = new Map<string, number>();
71
- columns.forEach((col, index) => {
72
- map.set(col.name.toLowerCase(), index);
73
- });
74
- return map;
75
- }
76
-
77
- /**
78
- * Extracts just the column indices from a primary key definition
79
- *
80
- * @param pkDef Primary key definition array
81
- * @returns Array of column indices that form the primary key
82
- */
83
- export function getPrimaryKeyIndices(pkDef: ReadonlyArray<PrimaryKeyColumnDefinition>): ReadonlyArray<number> {
84
- return Object.freeze(pkDef.map(def => def.index));
85
- }
86
-
87
- /**
88
- * Converts a parsed ColumnDef AST node into a runtime ColumnSchema object
89
- *
90
- * @param def Column definition AST node
91
- * @param defaultNotNull Whether columns should be NOT NULL by default (Third Manifesto approach)
92
- * @returns A runtime ColumnSchema object
93
- */
94
- export function columnDefToSchema(def: ColumnDef, defaultNotNull: boolean = true): ColumnSchema {
95
- const schema: Partial<ColumnSchema> & { name: string } = {
96
- name: def.name,
97
- affinity: getAffinity(def.dataType),
98
- notNull: defaultNotNull, // Default based on Third Manifesto principles
99
- primaryKey: false,
100
- pkOrder: 0,
101
- defaultValue: null,
102
- collation: 'BINARY',
103
- generated: false,
104
- };
105
-
106
- for (const constraint of def.constraints ?? []) {
107
- switch (constraint.type) {
108
- case 'primaryKey':
109
- schema.primaryKey = true;
110
- schema.pkDirection = constraint.direction;
111
- break;
112
- case 'notNull':
113
- schema.notNull = true;
114
- break;
115
- case 'null':
116
- schema.notNull = false;
117
- break;
118
- case 'unique':
119
- break;
120
- case 'default':
121
- schema.defaultValue = constraint.expr;
122
- break;
123
- case 'collate':
124
- schema.collation = constraint.collation;
125
- break;
126
- case 'generated':
127
- schema.generated = true;
128
- break;
129
- }
130
- }
131
-
132
- // PK implies NOT NULL (always, regardless of default)
133
- if (schema.primaryKey) {
134
- schema.notNull = true;
135
- }
136
-
137
- // If no explicit nullability constraint and default is nullable,
138
- // we need to check if there's an explicit NULL declaration
139
- // Note: SQL doesn't have explicit NULL constraints in standard syntax,
140
- // so this primarily affects the default behavior
141
-
142
- // Assign a default pkOrder if it's a PK but order isn't specified elsewhere
143
- if (schema.primaryKey && schema.pkOrder === 0) {
144
- schema.pkOrder = 1;
145
- }
146
-
147
- return schema as ColumnSchema;
148
- }
149
-
150
- /**
151
- * Mutation context variable definition
152
- */
153
- export interface MutationContextDefinition {
154
- /** Variable name */
155
- name: string;
156
- /** Type affinity of the variable */
157
- affinity: SqlDataType;
158
- /** Whether the variable is NOT NULL */
159
- notNull: boolean;
160
- }
161
-
162
- /**
163
- * Converts AST mutation context variable to schema definition
164
- *
165
- * @param varDef AST mutation context variable definition
166
- * @param defaultNotNull Whether variables should be NOT NULL by default
167
- * @returns Mutation context definition schema object
168
- */
169
- export function mutationContextVarToSchema(varDef: AST.MutationContextVar, defaultNotNull: boolean = true): MutationContextDefinition {
170
- return {
171
- name: varDef.name,
172
- affinity: getAffinity(varDef.dataType),
173
- notNull: varDef.notNull !== undefined ? varDef.notNull : defaultNotNull,
174
- };
175
- }
176
-
177
- /**
178
- * Defines a column in an index
179
- */
180
- export interface IndexColumnSchema {
181
- /** Column index in TableSchema.columns */
182
- index: number;
183
- /** Whether the index should sort in descending order */
184
- desc?: boolean; // default false
185
- /** Optional collation sequence for the column */
186
- collation?: string;
187
- }
188
-
189
- /**
190
- * Represents an index definition
191
- */
192
- export interface IndexSchema {
193
- /** Index name */
194
- name: string;
195
- /** Columns in the index */
196
- columns: ReadonlyArray<IndexColumnSchema>;
197
- }
198
-
199
- /**
200
- * Creates a basic TableSchema with minimal configuration
201
- *
202
- * @param name Table name
203
- * @param columns Array of column name and type objects
204
- * @param pkColNames Optional array of primary key column names
205
- * @param defaultNotNull Whether columns should be NOT NULL by default (defaults to true for Third Manifesto compliance)
206
- * @returns A frozen TableSchema object
207
- */
208
- export function createBasicSchema(name: string, columns: { name: string, type: string }[], pkColNames?: string[], defaultNotNull: boolean = true): Readonly<TableSchema> {
209
- const columnSchemas = columns.map(c => columnDefToSchema({
210
- name: c.name,
211
- dataType: c.type,
212
- constraints: []
213
- }, defaultNotNull));
214
- const columnIndexMap = buildColumnIndexMap(columnSchemas);
215
- const pkDef = pkColNames
216
- ? pkColNames.map(pkName => {
217
- const idx = columnIndexMap.get(pkName.toLowerCase());
218
- if (idx === undefined) quereusError(`PK column ${pkName} not found`);
219
- return { index: idx, desc: false };
220
- })
221
- : [];
222
-
223
- const defaultMemoryModule = new MemoryTableModule();
224
-
225
- return Object.freeze({
226
- name: name,
227
- schemaName: 'main',
228
- columns: columnSchemas,
229
- columnIndexMap: columnIndexMap,
230
- primaryKeyDefinition: pkDef,
231
- checkConstraints: [] as RowConstraintSchema[],
232
- indexes: [],
233
- vtabModule: defaultMemoryModule,
234
- vtabAuxData: null,
235
- vtabArgs: {},
236
- vtabModuleName: 'memory',
237
- isTemporary: false,
238
- isView: false,
239
- subqueryAST: undefined,
240
- viewDefinition: undefined,
241
- tableConstraints: [],
242
- primaryKey: pkDef.map(def => columnSchemas[def.index].name),
243
- });
244
- }
245
-
246
- /** Bitmask for row operations */
247
- export const enum RowOpFlag {
248
- INSERT = 1,
249
- UPDATE = 2,
250
- DELETE = 4
251
- }
252
- export type RowOpMask = RowOpFlag;
253
- export const DEFAULT_ROWOP_MASK = RowOpFlag.INSERT | RowOpFlag.UPDATE;
254
-
255
- /**
256
- * Converts an array of row operations to a bitmask
257
- *
258
- * @param list Optional array of operation types
259
- * @returns A bitmask representing the operations
260
- */
261
- export function opsToMask(list?: RowOp[]): RowOpMask {
262
- if (!list || list.length === 0) {
263
- return DEFAULT_ROWOP_MASK;
264
- }
265
- let mask: RowOpMask = 0 as RowOpMask;
266
- list.forEach(op => {
267
- switch (op) {
268
- case 'insert': mask |= RowOpFlag.INSERT; break;
269
- case 'update': mask |= RowOpFlag.UPDATE; break;
270
- case 'delete': mask |= RowOpFlag.DELETE; break;
271
- }
272
- });
273
- return mask;
274
- }
275
-
276
- /**
277
- * Represents a CHECK constraint with operation flags
278
- */
279
- export interface RowConstraintSchema {
280
- /** Optional constraint name */
281
- name?: string;
282
- /** Constraint expression */
283
- expr: Expression;
284
- /** Bitmask of operations the constraint applies to */
285
- operations: RowOpMask;
286
- /** Whether the constraint is deferrable */
287
- deferrable?: boolean;
288
- /** Whether the constraint is initially deferred */
289
- initiallyDeferred?: boolean;
290
- }
291
-
292
- export interface PrimaryKeyColumnDefinition {
293
- index: number;
294
- desc?: boolean; // default false
295
- autoIncrement?: boolean;
296
- collation?: string;
297
- }
298
-
299
- /**
300
- * Helper to parse primary key from AST column and table constraints.
301
- * @param columns Parsed column definitions from AST.
302
- * @param constraints Parsed table constraints from AST.
303
- * @returns A ReadonlyArray defining the primary key columns (index and direction), or undefined.
304
- * @throws QuereusError if multiple primary keys are defined or PK column not found.
305
- */
306
- export function findPKDefinition(
307
- columns: ReadonlyArray<ColumnSchema>,
308
- constraints: ReadonlyArray<AST.TableConstraint> | undefined,
309
- ): ReadonlyArray<PrimaryKeyColumnDefinition> {
310
- const columnPK = findColumnPKDefinition(columns);
311
- const constraintPK = findConstraintPKDefinition(columns, constraints);
312
-
313
- if (constraintPK && columnPK) {
314
- throw new QuereusError("Cannot define both table-level and column-level PRIMARY KEYs", StatusCode.CONSTRAINT);
315
- }
316
-
317
- let finalPkDef = constraintPK ?? columnPK;
318
-
319
- if (!finalPkDef) {
320
- // Quereus-specific behavior: Include all columns in the primary key when no explicit primary key is defined
321
- // This differs from SQLite which would use the first INTEGER column or an implicit rowid
322
- // This design choice ensures predictable behavior and avoids potential confusion with SQLite's implicit rules
323
- warnLog(`No PRIMARY KEY explicitly defined. Including all columns in primary key.`);
324
- finalPkDef = Object.freeze(
325
- columns.map((col, index) => ({
326
- index,
327
- desc: false,
328
- collation: col.collation || 'BINARY'
329
- }))
330
- );
331
- }
332
-
333
- // Don't require NOT NULL, we want to be more flexible
334
-
335
- return finalPkDef as ReadonlyArray<PrimaryKeyColumnDefinition>;
336
- }
337
-
338
- function findConstraintPKDefinition(
339
- columns: readonly ColumnSchema[],
340
- constraints: readonly TableConstraint[] | undefined
341
- ): PrimaryKeyColumnDefinition[] | undefined {
342
- const colMap = buildColumnIndexMap(columns);
343
- let constraintPKs: PrimaryKeyColumnDefinition[] | undefined;
344
-
345
- if (constraints) {
346
- for (const constraint of constraints) {
347
- if (constraint.type === 'primaryKey') {
348
- if (constraintPKs) {
349
- throw new QuereusError("Multiple table-level PRIMARY KEY constraints defined", StatusCode.CONSTRAINT);
350
- }
351
- if (!constraint.columns || constraint.columns.length === 0) {
352
- // An empty column list is fine; means table can have 0-1 rows
353
- constraintPKs = [];
354
- } else {
355
- constraintPKs = constraint.columns.map(colInfo => {
356
- const colIndex = colMap.get(colInfo.name.toLowerCase());
357
- if (colIndex === undefined) {
358
- throw new QuereusError(`PRIMARY KEY column '${colInfo.name}' not found in table definition`, StatusCode.ERROR);
359
- }
360
- return {
361
- index: colIndex,
362
- desc: colInfo.direction === 'desc',
363
- collation: columns[colIndex].collation || 'BINARY'
364
- };
365
- });
366
- }
367
- }
368
- }
369
- }
370
- return constraintPKs;
371
- }
372
-
373
- function findColumnPKDefinition(columns: ReadonlyArray<ColumnSchema>): ReadonlyArray<PrimaryKeyColumnDefinition> | undefined {
374
- const pkCols = columns
375
- .map((col, index) => ({ ...col, originalIndex: index }))
376
- .filter(col => col.primaryKey)
377
- .sort((a, b) => a.pkOrder - b.pkOrder);
378
-
379
- if (pkCols.length > 1 && pkCols.some(col => col.pkOrder === 0)) {
380
- warnLog("Multiple column-level PRIMARY KEYs defined without explicit pkOrder; consider a table-level PRIMARY KEY for composite keys.");
381
- }
382
-
383
- if (pkCols.length > 1) {
384
- warnLog('Multiple columns defined as PRIMARY KEY at column level. Forming a composite key.');
385
- }
386
-
387
- if (pkCols.length === 0) {
388
- return undefined;
389
- }
390
-
391
- return Object.freeze(pkCols.map(col => ({
392
- index: col.originalIndex,
393
- desc: col.affinity === SqlDataType.INTEGER && col.pkDirection === 'desc',
394
- autoIncrement: col.affinity === SqlDataType.INTEGER,
395
- collation: col.collation || 'BINARY'
396
- })));
397
- }
398
-
1
+ import type { ColumnSchema } from './column.js';
2
+ import type { AnyVirtualTableModule } from '../vtab/module.js';
3
+ import { MemoryTableModule } from '../vtab/memory/module.js';
4
+ import type { Expression } from '../parser/ast.js';
5
+ import { type ColumnDef, type TableConstraint } from '../parser/ast.js';
6
+ import { getAffinity } from '../common/type-inference.js';
7
+ import { RowOp, SqlDataType, StatusCode, type SqlValue } from '../common/types.js';
8
+ import type * as AST from '../parser/ast.js';
9
+ import { quereusError, QuereusError } from '../common/errors.js';
10
+ import { createLogger } from '../common/logger.js';
11
+ import { inferType } from '../types/registry.js';
12
+
13
+ const log = createLogger('schema:table');
14
+ const warnLog = log.extend('warn');
15
+
16
+ /**
17
+ * Represents the schema definition of a table (real or virtual).
18
+ */
19
+ export interface TableSchema {
20
+ /** Table name */
21
+ name: string;
22
+ /** Schema name (e.g., "main", "temp") */
23
+ schemaName: string;
24
+ /** Ordered list of column definitions */
25
+ columns: ReadonlyArray<ColumnSchema>;
26
+ /** Map from column name (lowercase) to column index */
27
+ columnIndexMap: ReadonlyMap<string, number>;
28
+ /** Definition of the primary key, including order and direction */
29
+ primaryKeyDefinition: ReadonlyArray<PrimaryKeyColumnDefinition>;
30
+ /** CHECK constraints defined on the table or its columns */
31
+ checkConstraints: ReadonlyArray<RowConstraintSchema>;
32
+ /** Reference to the registered module */
33
+ vtabModule: AnyVirtualTableModule;
34
+ /** If virtual, aux data passed during module registration */
35
+ vtabAuxData?: unknown;
36
+ /** If virtual, the arguments passed in CREATE VIRTUAL TABLE */
37
+ vtabArgs?: Record<string, SqlValue>;
38
+ /** If virtual, the name the module was registered with */
39
+ vtabModuleName: string;
40
+ /** Whether the table is a temporary table */
41
+ isTemporary?: boolean;
42
+ /** Whether the table is a view */
43
+ isView: boolean;
44
+ /** Whether the table is a subquery source */
45
+ subqueryAST?: AST.SelectStmt;
46
+ /** If virtual, the view definition */
47
+ viewDefinition?: AST.SelectStmt;
48
+ /** Table-level constraints */
49
+ tableConstraints?: readonly TableConstraint[];
50
+ /** Definitions of secondary indexes (relevant for planning) */
51
+ indexes?: ReadonlyArray<IndexSchema>;
52
+ /** Estimated number of rows in the table (for query planning) */
53
+ readonly estimatedRows?: number;
54
+ /** Whether the table is read-only */
55
+ isReadOnly?: boolean; // default false
56
+ /** Mutation context variables for this table */
57
+ mutationContext?: ReadonlyArray<MutationContextDefinition>;
58
+ /** Foreign key constraints (parsed but not yet enforced by engine) */
59
+ // foreignKeys?: ReadonlyArray<ForeignKeyConstraintSchema>;
60
+ /** Unique constraints (beyond primary key) */
61
+ // uniqueConstraints?: ReadonlyArray<ConstraintSchema>;
62
+ }
63
+
64
+ /**
65
+ * Builds a map from column names to their indices in the columns array
66
+ *
67
+ * @param columns Array of column schemas
68
+ * @returns Map of lowercase column names to their indices
69
+ */
70
+ export function buildColumnIndexMap(columns: ReadonlyArray<ColumnSchema>): Map<string, number> {
71
+ const map = new Map<string, number>();
72
+ columns.forEach((col, index) => {
73
+ map.set(col.name.toLowerCase(), index);
74
+ });
75
+ return map;
76
+ }
77
+
78
+ /**
79
+ * Extracts just the column indices from a primary key definition
80
+ *
81
+ * @param pkDef Primary key definition array
82
+ * @returns Array of column indices that form the primary key
83
+ */
84
+ export function getPrimaryKeyIndices(pkDef: ReadonlyArray<PrimaryKeyColumnDefinition>): ReadonlyArray<number> {
85
+ return Object.freeze(pkDef.map(def => def.index));
86
+ }
87
+
88
+ /**
89
+ * Converts a parsed ColumnDef AST node into a runtime ColumnSchema object
90
+ *
91
+ * @param def Column definition AST node
92
+ * @param defaultNotNull Whether columns should be NOT NULL by default (Third Manifesto approach)
93
+ * @returns A runtime ColumnSchema object
94
+ */
95
+ export function columnDefToSchema(def: ColumnDef, defaultNotNull: boolean = true): ColumnSchema {
96
+ // Infer logical type from the declared type name
97
+ const logicalType = inferType(def.dataType);
98
+
99
+ const schema: Partial<ColumnSchema> & { name: string } = {
100
+ name: def.name,
101
+ logicalType: logicalType,
102
+ affinity: getAffinity(def.dataType), // Keep for backward compatibility during transition
103
+ notNull: defaultNotNull, // Default based on Third Manifesto principles
104
+ primaryKey: false,
105
+ pkOrder: 0,
106
+ defaultValue: null,
107
+ collation: 'BINARY',
108
+ generated: false,
109
+ };
110
+
111
+ for (const constraint of def.constraints ?? []) {
112
+ switch (constraint.type) {
113
+ case 'primaryKey':
114
+ schema.primaryKey = true;
115
+ schema.pkDirection = constraint.direction;
116
+ break;
117
+ case 'notNull':
118
+ schema.notNull = true;
119
+ break;
120
+ case 'null':
121
+ schema.notNull = false;
122
+ break;
123
+ case 'unique':
124
+ break;
125
+ case 'default':
126
+ schema.defaultValue = constraint.expr;
127
+ break;
128
+ case 'collate':
129
+ schema.collation = constraint.collation;
130
+ // Validate collation compatibility with type
131
+ if (constraint.collation && logicalType.supportedCollations &&
132
+ !logicalType.supportedCollations.includes(constraint.collation)) {
133
+ throw new QuereusError(
134
+ `Collation '${constraint.collation}' is not supported for type '${logicalType.name}' on column '${def.name}'`,
135
+ StatusCode.ERROR
136
+ );
137
+ }
138
+ break;
139
+ case 'generated':
140
+ schema.generated = true;
141
+ break;
142
+ }
143
+ }
144
+
145
+ // PK implies NOT NULL (always, regardless of default)
146
+ if (schema.primaryKey) {
147
+ schema.notNull = true;
148
+ }
149
+
150
+ // If no explicit nullability constraint and default is nullable,
151
+ // we need to check if there's an explicit NULL declaration
152
+ // Note: SQL doesn't have explicit NULL constraints in standard syntax,
153
+ // so this primarily affects the default behavior
154
+
155
+ // Assign a default pkOrder if it's a PK but order isn't specified elsewhere
156
+ if (schema.primaryKey && schema.pkOrder === 0) {
157
+ schema.pkOrder = 1;
158
+ }
159
+
160
+ return schema as ColumnSchema;
161
+ }
162
+
163
+ /**
164
+ * Mutation context variable definition
165
+ */
166
+ export interface MutationContextDefinition {
167
+ /** Variable name */
168
+ name: string;
169
+ /** Type affinity of the variable */
170
+ affinity: SqlDataType;
171
+ /** Whether the variable is NOT NULL */
172
+ notNull: boolean;
173
+ }
174
+
175
+ /**
176
+ * Converts AST mutation context variable to schema definition
177
+ *
178
+ * @param varDef AST mutation context variable definition
179
+ * @param defaultNotNull Whether variables should be NOT NULL by default
180
+ * @returns Mutation context definition schema object
181
+ */
182
+ export function mutationContextVarToSchema(varDef: AST.MutationContextVar, defaultNotNull: boolean = true): MutationContextDefinition {
183
+ return {
184
+ name: varDef.name,
185
+ affinity: getAffinity(varDef.dataType),
186
+ notNull: varDef.notNull !== undefined ? varDef.notNull : defaultNotNull,
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Defines a column in an index
192
+ */
193
+ export interface IndexColumnSchema {
194
+ /** Column index in TableSchema.columns */
195
+ index: number;
196
+ /** Whether the index should sort in descending order */
197
+ desc?: boolean; // default false
198
+ /** Optional collation sequence for the column */
199
+ collation?: string;
200
+ }
201
+
202
+ /**
203
+ * Represents an index definition
204
+ */
205
+ export interface IndexSchema {
206
+ /** Index name */
207
+ name: string;
208
+ /** Columns in the index */
209
+ columns: ReadonlyArray<IndexColumnSchema>;
210
+ }
211
+
212
+ /**
213
+ * Creates a basic TableSchema with minimal configuration
214
+ *
215
+ * @param name Table name
216
+ * @param columns Array of column name and type objects
217
+ * @param pkColNames Optional array of primary key column names
218
+ * @param defaultNotNull Whether columns should be NOT NULL by default (defaults to true for Third Manifesto compliance)
219
+ * @returns A frozen TableSchema object
220
+ */
221
+ export function createBasicSchema(name: string, columns: { name: string, type: string }[], pkColNames?: string[], defaultNotNull: boolean = true): Readonly<TableSchema> {
222
+ const columnSchemas = columns.map(c => columnDefToSchema({
223
+ name: c.name,
224
+ dataType: c.type,
225
+ constraints: []
226
+ }, defaultNotNull));
227
+ const columnIndexMap = buildColumnIndexMap(columnSchemas);
228
+ const pkDef = pkColNames
229
+ ? pkColNames.map(pkName => {
230
+ const idx = columnIndexMap.get(pkName.toLowerCase());
231
+ if (idx === undefined) quereusError(`PK column ${pkName} not found`);
232
+ return { index: idx, desc: false };
233
+ })
234
+ : [];
235
+
236
+ const defaultMemoryModule = new MemoryTableModule();
237
+
238
+ return Object.freeze({
239
+ name: name,
240
+ schemaName: 'main',
241
+ columns: columnSchemas,
242
+ columnIndexMap: columnIndexMap,
243
+ primaryKeyDefinition: pkDef,
244
+ checkConstraints: [] as RowConstraintSchema[],
245
+ indexes: [],
246
+ vtabModule: defaultMemoryModule,
247
+ vtabAuxData: null,
248
+ vtabArgs: {},
249
+ vtabModuleName: 'memory',
250
+ isTemporary: false,
251
+ isView: false,
252
+ subqueryAST: undefined,
253
+ viewDefinition: undefined,
254
+ tableConstraints: [],
255
+ primaryKey: pkDef.map(def => columnSchemas[def.index].name),
256
+ });
257
+ }
258
+
259
+ /** Bitmask for row operations */
260
+ export const enum RowOpFlag {
261
+ INSERT = 1,
262
+ UPDATE = 2,
263
+ DELETE = 4
264
+ }
265
+ export type RowOpMask = RowOpFlag;
266
+ export const DEFAULT_ROWOP_MASK = RowOpFlag.INSERT | RowOpFlag.UPDATE;
267
+
268
+ /**
269
+ * Converts an array of row operations to a bitmask
270
+ *
271
+ * @param list Optional array of operation types
272
+ * @returns A bitmask representing the operations
273
+ */
274
+ export function opsToMask(list?: RowOp[]): RowOpMask {
275
+ if (!list || list.length === 0) {
276
+ return DEFAULT_ROWOP_MASK;
277
+ }
278
+ let mask: RowOpMask = 0 as RowOpMask;
279
+ list.forEach(op => {
280
+ switch (op) {
281
+ case 'insert': mask |= RowOpFlag.INSERT; break;
282
+ case 'update': mask |= RowOpFlag.UPDATE; break;
283
+ case 'delete': mask |= RowOpFlag.DELETE; break;
284
+ }
285
+ });
286
+ return mask;
287
+ }
288
+
289
+ /**
290
+ * Represents a CHECK constraint with operation flags
291
+ */
292
+ export interface RowConstraintSchema {
293
+ /** Optional constraint name */
294
+ name?: string;
295
+ /** Constraint expression */
296
+ expr: Expression;
297
+ /** Bitmask of operations the constraint applies to */
298
+ operations: RowOpMask;
299
+ /** Whether the constraint is deferrable */
300
+ deferrable?: boolean;
301
+ /** Whether the constraint is initially deferred */
302
+ initiallyDeferred?: boolean;
303
+ }
304
+
305
+ export interface PrimaryKeyColumnDefinition {
306
+ index: number;
307
+ desc?: boolean; // default false
308
+ autoIncrement?: boolean;
309
+ collation?: string;
310
+ }
311
+
312
+ /**
313
+ * Helper to parse primary key from AST column and table constraints.
314
+ * @param columns Parsed column definitions from AST.
315
+ * @param constraints Parsed table constraints from AST.
316
+ * @returns A ReadonlyArray defining the primary key columns (index and direction), or undefined.
317
+ * @throws QuereusError if multiple primary keys are defined or PK column not found.
318
+ */
319
+ export function findPKDefinition(
320
+ columns: ReadonlyArray<ColumnSchema>,
321
+ constraints: ReadonlyArray<AST.TableConstraint> | undefined,
322
+ ): ReadonlyArray<PrimaryKeyColumnDefinition> {
323
+ const columnPK = findColumnPKDefinition(columns);
324
+ const constraintPK = findConstraintPKDefinition(columns, constraints);
325
+
326
+ if (constraintPK && columnPK) {
327
+ throw new QuereusError("Cannot define both table-level and column-level PRIMARY KEYs", StatusCode.CONSTRAINT);
328
+ }
329
+
330
+ let finalPkDef = constraintPK ?? columnPK;
331
+
332
+ if (!finalPkDef) {
333
+ // Quereus-specific behavior: Include all columns in the primary key when no explicit primary key is defined
334
+ // This differs from SQLite which would use the first INTEGER column or an implicit rowid
335
+ // This design choice ensures predictable behavior and avoids potential confusion with SQLite's implicit rules
336
+ warnLog(`No PRIMARY KEY explicitly defined. Including all columns in primary key.`);
337
+ finalPkDef = Object.freeze(
338
+ columns.map((col, index) => ({
339
+ index,
340
+ desc: false,
341
+ collation: col.collation || 'BINARY'
342
+ }))
343
+ );
344
+ }
345
+
346
+ // Don't require NOT NULL, we want to be more flexible
347
+
348
+ return finalPkDef as ReadonlyArray<PrimaryKeyColumnDefinition>;
349
+ }
350
+
351
+ function findConstraintPKDefinition(
352
+ columns: readonly ColumnSchema[],
353
+ constraints: readonly TableConstraint[] | undefined
354
+ ): PrimaryKeyColumnDefinition[] | undefined {
355
+ const colMap = buildColumnIndexMap(columns);
356
+ let constraintPKs: PrimaryKeyColumnDefinition[] | undefined;
357
+
358
+ if (constraints) {
359
+ for (const constraint of constraints) {
360
+ if (constraint.type === 'primaryKey') {
361
+ if (constraintPKs) {
362
+ throw new QuereusError("Multiple table-level PRIMARY KEY constraints defined", StatusCode.CONSTRAINT);
363
+ }
364
+ if (!constraint.columns || constraint.columns.length === 0) {
365
+ // An empty column list is fine; means table can have 0-1 rows
366
+ constraintPKs = [];
367
+ } else {
368
+ constraintPKs = constraint.columns.map(colInfo => {
369
+ const colIndex = colMap.get(colInfo.name.toLowerCase());
370
+ if (colIndex === undefined) {
371
+ throw new QuereusError(`PRIMARY KEY column '${colInfo.name}' not found in table definition`, StatusCode.ERROR);
372
+ }
373
+ return {
374
+ index: colIndex,
375
+ desc: colInfo.direction === 'desc',
376
+ collation: columns[colIndex].collation || 'BINARY'
377
+ };
378
+ });
379
+ }
380
+ }
381
+ }
382
+ }
383
+ return constraintPKs;
384
+ }
385
+
386
+ function findColumnPKDefinition(columns: ReadonlyArray<ColumnSchema>): ReadonlyArray<PrimaryKeyColumnDefinition> | undefined {
387
+ const pkCols = columns
388
+ .map((col, index) => ({ ...col, originalIndex: index }))
389
+ .filter(col => col.primaryKey)
390
+ .sort((a, b) => a.pkOrder - b.pkOrder);
391
+
392
+ if (pkCols.length > 1 && pkCols.some(col => col.pkOrder === 0)) {
393
+ warnLog("Multiple column-level PRIMARY KEYs defined without explicit pkOrder; consider a table-level PRIMARY KEY for composite keys.");
394
+ }
395
+
396
+ if (pkCols.length > 1) {
397
+ warnLog('Multiple columns defined as PRIMARY KEY at column level. Forming a composite key.');
398
+ }
399
+
400
+ if (pkCols.length === 0) {
401
+ return undefined;
402
+ }
403
+
404
+ return Object.freeze(pkCols.map(col => ({
405
+ index: col.originalIndex,
406
+ desc: col.affinity === SqlDataType.INTEGER && col.pkDirection === 'desc',
407
+ autoIncrement: col.affinity === SqlDataType.INTEGER,
408
+ collation: col.collation || 'BINARY'
409
+ })));
410
+ }
411
+