@quereus/quereus 0.4.8 → 0.4.10
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.
- package/dist/src/config/loader.d.ts +41 -0
- package/dist/src/config/loader.d.ts.map +1 -0
- package/dist/src/config/loader.js +102 -0
- package/dist/src/config/loader.js.map +1 -0
- package/dist/src/core/database.d.ts +0 -7
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +48 -28
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +32 -6
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/planner/building/create-view.d.ts.map +1 -1
- package/dist/src/planner/building/create-view.js +3 -25
- package/dist/src/planner/building/create-view.js.map +1 -1
- package/dist/src/planner/building/select.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +20 -1
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +18 -3
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/schema-differ.js +3 -3
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/util/ast-stringify.d.ts +1 -0
- package/dist/src/util/ast-stringify.d.ts.map +1 -1
- package/dist/src/util/ast-stringify.js +1 -1
- package/dist/src/util/ast-stringify.js.map +1 -1
- package/package.json +1 -1
- package/src/config/loader.ts +140 -0
- package/src/core/database.ts +54 -34
- package/src/index.ts +9 -0
- package/src/parser/parser.ts +28 -6
- package/src/planner/building/create-view.ts +3 -30
- package/src/planner/building/select.ts +29 -1
- package/src/schema/catalog.ts +17 -3
- package/src/schema/schema-differ.ts +3 -3
- package/src/util/ast-stringify.ts +1 -1
package/src/core/database.ts
CHANGED
|
@@ -217,6 +217,34 @@ export class Database {
|
|
|
217
217
|
* @returns A Promise resolving when execution completes.
|
|
218
218
|
* @throws QuereusError on failure.
|
|
219
219
|
*/
|
|
220
|
+
/**
|
|
221
|
+
* @internal
|
|
222
|
+
* Executes a single AST statement without transaction management.
|
|
223
|
+
* Used by both exec() and eval() to avoid code duplication.
|
|
224
|
+
*/
|
|
225
|
+
private async _executeStatement(statementAst: AST.Statement, params?: SqlParameters | SqlValue[]): Promise<void> {
|
|
226
|
+
const plan = this._buildPlan([statementAst], params);
|
|
227
|
+
|
|
228
|
+
if (plan.statements.length === 0) return; // No-op for this AST
|
|
229
|
+
|
|
230
|
+
const optimizedPlan = this.optimizer.optimize(plan, this) as BlockNode;
|
|
231
|
+
const emissionContext = new EmissionContext(this);
|
|
232
|
+
const rootInstruction = emitPlanNode(optimizedPlan, emissionContext);
|
|
233
|
+
const scheduler = new Scheduler(rootInstruction);
|
|
234
|
+
|
|
235
|
+
const runtimeCtx: RuntimeContext = {
|
|
236
|
+
db: this,
|
|
237
|
+
stmt: undefined,
|
|
238
|
+
params: params ?? {},
|
|
239
|
+
context: new Map(),
|
|
240
|
+
tableContexts: new Map(),
|
|
241
|
+
tracer: this.instructionTracer,
|
|
242
|
+
enableMetrics: this.options.getBooleanOption('runtime_stats'),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
await scheduler.run(runtimeCtx);
|
|
246
|
+
}
|
|
247
|
+
|
|
220
248
|
async exec(
|
|
221
249
|
sql: string,
|
|
222
250
|
params?: SqlParameters,
|
|
@@ -250,39 +278,13 @@ export class Database {
|
|
|
250
278
|
}
|
|
251
279
|
|
|
252
280
|
for (let i = 0; i < batch.length; i++) {
|
|
253
|
-
const statementAst = batch[i];
|
|
254
|
-
let plan: BlockNode;
|
|
255
|
-
|
|
256
281
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (plan.statements.length === 0) continue; // No-op for this AST
|
|
260
|
-
|
|
261
|
-
const optimizedPlan = this.optimizer.optimize(plan, this) as BlockNode;
|
|
262
|
-
|
|
263
|
-
const emissionContext = new EmissionContext(this);
|
|
264
|
-
const rootInstruction = emitPlanNode(optimizedPlan, emissionContext);
|
|
265
|
-
|
|
266
|
-
const scheduler = new Scheduler(rootInstruction);
|
|
267
|
-
|
|
268
|
-
const runtimeCtx: RuntimeContext = {
|
|
269
|
-
db: this,
|
|
270
|
-
stmt: undefined,
|
|
271
|
-
params: params ?? {},
|
|
272
|
-
context: new Map(),
|
|
273
|
-
tableContexts: new Map(),
|
|
274
|
-
tracer: this.instructionTracer,
|
|
275
|
-
enableMetrics: this.options.getBooleanOption('runtime_stats'),
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
void await scheduler.run(runtimeCtx);
|
|
279
|
-
// Nothing to do with the result, this is executed for side effects only
|
|
282
|
+
await this._executeStatement(batch[i], params);
|
|
280
283
|
} catch (err) {
|
|
281
284
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
282
285
|
executionError = error instanceof QuereusError ? error : new QuereusError(error.message, StatusCode.ERROR, error);
|
|
283
286
|
break; // Stop processing further statements on error
|
|
284
287
|
}
|
|
285
|
-
// No explicit finalize for transient plan/scheduler used in exec loop
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
} finally {
|
|
@@ -755,17 +757,35 @@ export class Database {
|
|
|
755
757
|
let stmt: Statement | null = null;
|
|
756
758
|
try {
|
|
757
759
|
stmt = this.prepare(sql);
|
|
758
|
-
if (stmt.astBatch.length > 1) {
|
|
759
|
-
warnLog(`Database.eval called with multi-statement SQL. Only results from the first statement will be yielded.`);
|
|
760
|
-
}
|
|
761
760
|
|
|
762
|
-
if (stmt.astBatch.length
|
|
763
|
-
// If currentAstIndex defaults to 0 and astBatch is not empty, this will run the first statement.
|
|
764
|
-
yield* stmt.all(params);
|
|
765
|
-
} else {
|
|
761
|
+
if (stmt.astBatch.length === 0) {
|
|
766
762
|
// No statements, yield nothing.
|
|
767
763
|
return;
|
|
768
764
|
}
|
|
765
|
+
|
|
766
|
+
if (stmt.astBatch.length > 1) {
|
|
767
|
+
// Multi-statement batch: execute all but the last statement,
|
|
768
|
+
// then yield results from the last statement
|
|
769
|
+
const parser = new Parser();
|
|
770
|
+
const batch = parser.parseAll(sql);
|
|
771
|
+
|
|
772
|
+
// Execute all statements except the last one
|
|
773
|
+
for (let i = 0; i < batch.length - 1; i++) {
|
|
774
|
+
await this._executeStatement(batch[i], params);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Now prepare and execute the last statement to yield its results
|
|
778
|
+
const lastStmt = new Statement(this, [batch[batch.length - 1]]);
|
|
779
|
+
this.statements.add(lastStmt);
|
|
780
|
+
try {
|
|
781
|
+
yield* lastStmt.all(params);
|
|
782
|
+
} finally {
|
|
783
|
+
await lastStmt.finalize();
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
// Single statement: execute and yield results
|
|
787
|
+
yield* stmt.all(params);
|
|
788
|
+
}
|
|
769
789
|
} finally {
|
|
770
790
|
if (stmt) { await stmt.finalize(); }
|
|
771
791
|
}
|
package/src/index.ts
CHANGED
|
@@ -93,6 +93,15 @@ export type {
|
|
|
93
93
|
PluginRegistrations
|
|
94
94
|
} from './vtab/manifest.js';
|
|
95
95
|
|
|
96
|
+
// Re-export config system
|
|
97
|
+
export {
|
|
98
|
+
interpolateEnvVars,
|
|
99
|
+
interpolateConfigEnvVars,
|
|
100
|
+
loadPluginsFromConfig,
|
|
101
|
+
validateConfig
|
|
102
|
+
} from './config/loader.js';
|
|
103
|
+
export type { QuoombConfig, PluginConfig } from './config/loader.js';
|
|
104
|
+
|
|
96
105
|
// Re-export virtual table framework
|
|
97
106
|
export type { VirtualTableModule } from './vtab/module.js';
|
|
98
107
|
|
package/src/parser/parser.ts
CHANGED
|
@@ -2048,12 +2048,23 @@ export class Parser {
|
|
|
2048
2048
|
if (this.matchKeyword('USING')) {
|
|
2049
2049
|
moduleName = this.consumeIdentifier("Expected module name after 'USING'.");
|
|
2050
2050
|
if (this.match(TokenType.LPAREN)) {
|
|
2051
|
+
let positionalIndex = 0;
|
|
2051
2052
|
if (!this.check(TokenType.RPAREN)) {
|
|
2052
2053
|
do {
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2054
|
+
// Check if this is a positional argument (string/number literal) or named argument (identifier=value)
|
|
2055
|
+
if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
|
|
2056
|
+
// Positional argument
|
|
2057
|
+
const token = this.advance();
|
|
2058
|
+
moduleArgs[String(positionalIndex++)] = token.literal;
|
|
2059
|
+
} else if (this.check(TokenType.IDENTIFIER)) {
|
|
2060
|
+
// Could be named argument or identifier value
|
|
2061
|
+
const nameValue = this.nameValueItem('module argument');
|
|
2062
|
+
moduleArgs[nameValue.name] = nameValue.value && nameValue.value.type === 'literal'
|
|
2063
|
+
? getSyncLiteral(nameValue.value)
|
|
2064
|
+
: (nameValue.value && nameValue.value.type === 'identifier' ? nameValue.value.name : nameValue.name);
|
|
2065
|
+
} else {
|
|
2066
|
+
throw this.error(this.peek(), "Expected module argument (string, number, or name=value pair).");
|
|
2067
|
+
}
|
|
2057
2068
|
} while (this.match(TokenType.COMMA));
|
|
2058
2069
|
}
|
|
2059
2070
|
this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
|
|
@@ -2452,10 +2463,21 @@ export class Parser {
|
|
|
2452
2463
|
}
|
|
2453
2464
|
if (this.match(TokenType.LPAREN)) {
|
|
2454
2465
|
moduleArgs = {};
|
|
2466
|
+
let positionalIndex = 0;
|
|
2455
2467
|
if (!this.check(TokenType.RPAREN)) {
|
|
2456
2468
|
do {
|
|
2457
|
-
|
|
2458
|
-
|
|
2469
|
+
// Check if this is a positional argument (string/number literal) or named argument (identifier=value)
|
|
2470
|
+
if (this.check(TokenType.STRING) || this.check(TokenType.INTEGER) || this.check(TokenType.FLOAT)) {
|
|
2471
|
+
// Positional argument
|
|
2472
|
+
const token = this.advance();
|
|
2473
|
+
moduleArgs[String(positionalIndex++)] = token.literal;
|
|
2474
|
+
} else if (this.check(TokenType.IDENTIFIER)) {
|
|
2475
|
+
// Could be named argument or identifier value
|
|
2476
|
+
const nv = this.nameValueItem('module argument');
|
|
2477
|
+
moduleArgs[nv.name] = nv.value && nv.value.type === 'literal' ? getSyncLiteral(nv.value) : (nv.value && nv.value.type === 'identifier' ? nv.value.name : null);
|
|
2478
|
+
} else {
|
|
2479
|
+
throw this.error(this.peek(), "Expected module argument (string, number, or name=value pair).");
|
|
2480
|
+
}
|
|
2459
2481
|
} while (this.match(TokenType.COMMA));
|
|
2460
2482
|
}
|
|
2461
2483
|
this.consume(TokenType.RPAREN, "Expected ')' after module arguments.");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type * as AST from '../../parser/ast.js';
|
|
2
2
|
import type { PlanningContext } from '../planning-context.js';
|
|
3
3
|
import { CreateViewNode } from '../nodes/create-view-node.js';
|
|
4
|
+
import { createViewToString } from '../../util/ast-stringify.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Builds a plan node for CREATE VIEW statements.
|
|
@@ -11,9 +12,8 @@ export function buildCreateViewStmt(ctx: PlanningContext, stmt: AST.CreateViewSt
|
|
|
11
12
|
const viewName = stmt.view.name;
|
|
12
13
|
|
|
13
14
|
// The original SQL text is needed for the view definition
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
const sql = reconstructViewSQL(stmt);
|
|
15
|
+
// Reconstruct it from the AST using the proper stringifier
|
|
16
|
+
const sql = createViewToString(stmt);
|
|
17
17
|
|
|
18
18
|
return new CreateViewNode(
|
|
19
19
|
ctx.scope,
|
|
@@ -26,31 +26,4 @@ export function buildCreateViewStmt(ctx: PlanningContext, stmt: AST.CreateViewSt
|
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* Reconstructs the original SQL text for a CREATE VIEW statement.
|
|
31
|
-
* This is a simplified version - in production, the original text should be preserved.
|
|
32
|
-
*/
|
|
33
|
-
function reconstructViewSQL(stmt: AST.CreateViewStmt): string {
|
|
34
|
-
let sql = 'CREATE VIEW ';
|
|
35
|
-
|
|
36
|
-
if (stmt.ifNotExists) {
|
|
37
|
-
sql += 'IF NOT EXISTS ';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (stmt.view.schema) {
|
|
41
|
-
sql += `${stmt.view.schema}.`;
|
|
42
|
-
}
|
|
43
|
-
sql += stmt.view.name;
|
|
44
29
|
|
|
45
|
-
if (stmt.columns && stmt.columns.length > 0) {
|
|
46
|
-
sql += ` (${stmt.columns.join(', ')})`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
sql += ' AS ';
|
|
50
|
-
|
|
51
|
-
// For now, just add a placeholder for the SELECT statement
|
|
52
|
-
// In a full implementation, this would reconstruct the full SELECT
|
|
53
|
-
sql += '(SELECT statement)';
|
|
54
|
-
|
|
55
|
-
return sql;
|
|
56
|
-
}
|
|
@@ -333,7 +333,35 @@ export function buildFrom(fromClause: AST.FromClause, parentContext: PlanningCon
|
|
|
333
333
|
|
|
334
334
|
if (viewSchema) {
|
|
335
335
|
// Build the view's SELECT statement
|
|
336
|
-
|
|
336
|
+
let viewSelectNode = buildSelectStmt(parentContext, viewSchema.selectAst, cteNodes) as RelationalPlanNode;
|
|
337
|
+
|
|
338
|
+
// If the view has explicit column names, wrap with a projection to rename columns
|
|
339
|
+
if (viewSchema.columns && viewSchema.columns.length > 0) {
|
|
340
|
+
const viewAttributes = viewSelectNode.getAttributes();
|
|
341
|
+
const projections = viewSchema.columns.map((columnName, i) => {
|
|
342
|
+
if (i >= viewAttributes.length) {
|
|
343
|
+
throw new QuereusError(
|
|
344
|
+
`View '${viewSchema.name}' has more explicit column names than SELECT columns`,
|
|
345
|
+
StatusCode.ERROR
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
const attr = viewAttributes[i];
|
|
349
|
+
const columnRef = new ColumnReferenceNode(
|
|
350
|
+
parentContext.scope,
|
|
351
|
+
{ type: 'column', name: attr.name } as AST.ColumnExpr,
|
|
352
|
+
attr.type,
|
|
353
|
+
attr.id,
|
|
354
|
+
i
|
|
355
|
+
);
|
|
356
|
+
return {
|
|
357
|
+
node: columnRef,
|
|
358
|
+
alias: columnName
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
fromTable = new ProjectNode(parentContext.scope, viewSelectNode, projections);
|
|
362
|
+
} else {
|
|
363
|
+
fromTable = viewSelectNode;
|
|
364
|
+
}
|
|
337
365
|
|
|
338
366
|
// Create scope for view columns
|
|
339
367
|
const viewScope = new RegisteredScope(parentContext.scope);
|
package/src/schema/catalog.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Database } from '../core/database.js';
|
|
|
2
2
|
import type { TableSchema } from './table.js';
|
|
3
3
|
import type { ViewSchema } from './view.js';
|
|
4
4
|
import type { IntegrityAssertionSchema } from './assertion.js';
|
|
5
|
-
import { createTableToString } from '../util/ast-stringify.js';
|
|
5
|
+
import { createTableToString, createViewToString } from '../util/ast-stringify.js';
|
|
6
6
|
import type * as AST from '../parser/ast.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -196,9 +196,23 @@ export function generateDeclaredDDL(declaredSchema: AST.DeclareSchemaStmt, targe
|
|
|
196
196
|
case 'declaredIndex':
|
|
197
197
|
// TODO: Implement index DDL generation
|
|
198
198
|
break;
|
|
199
|
-
case 'declaredView':
|
|
200
|
-
//
|
|
199
|
+
case 'declaredView': {
|
|
200
|
+
// Qualify view name with schema if specified
|
|
201
|
+
const viewStmt = item.viewStmt;
|
|
202
|
+
if (targetSchema && targetSchema !== 'main' && !viewStmt.view.schema) {
|
|
203
|
+
const qualifiedStmt: AST.CreateViewStmt = {
|
|
204
|
+
...viewStmt,
|
|
205
|
+
view: {
|
|
206
|
+
...viewStmt.view,
|
|
207
|
+
schema: targetSchema
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
ddlStatements.push(createViewToString(qualifiedStmt));
|
|
211
|
+
} else {
|
|
212
|
+
ddlStatements.push(createViewToString(viewStmt));
|
|
213
|
+
}
|
|
201
214
|
break;
|
|
215
|
+
}
|
|
202
216
|
}
|
|
203
217
|
}
|
|
204
218
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SchemaCatalog } from './catalog.js';
|
|
2
2
|
import type * as AST from '../parser/ast.js';
|
|
3
|
-
import { createTableToString } from '../util/ast-stringify.js';
|
|
3
|
+
import { createTableToString, createViewToString } from '../util/ast-stringify.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Represents the difference between a declared schema and actual database state
|
|
@@ -104,8 +104,8 @@ export function computeSchemaDiff(
|
|
|
104
104
|
// Find views to create/drop
|
|
105
105
|
for (const [name, declaredView] of declaredViews) {
|
|
106
106
|
if (!actualViews.has(name)) {
|
|
107
|
-
//
|
|
108
|
-
diff.viewsToCreate.push(
|
|
107
|
+
// Generate proper view DDL using AST stringifier
|
|
108
|
+
diff.viewsToCreate.push(createViewToString(declaredView.viewStmt));
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -629,7 +629,7 @@ function createIndexToString(stmt: AST.CreateIndexStmt): string {
|
|
|
629
629
|
return parts.join(' ');
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
function createViewToString(stmt: AST.CreateViewStmt): string {
|
|
632
|
+
export function createViewToString(stmt: AST.CreateViewStmt): string {
|
|
633
633
|
const parts: string[] = ['create'];
|
|
634
634
|
if (stmt.isTemporary) parts.push('temp');
|
|
635
635
|
parts.push('view');
|