@quereus/quereus 0.6.12 → 0.7.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.
- package/dist/src/parser/lexer.d.ts +6 -0
- package/dist/src/parser/lexer.d.ts.map +1 -1
- package/dist/src/parser/lexer.js +33 -1
- package/dist/src/parser/lexer.js.map +1 -1
- package/dist/src/parser/parser.d.ts.map +1 -1
- package/dist/src/parser/parser.js +28 -24
- package/dist/src/parser/parser.js.map +1 -1
- package/dist/src/planner/building/select-aggregates.d.ts +6 -1
- package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
- package/dist/src/planner/building/select-aggregates.js +23 -4
- package/dist/src/planner/building/select-aggregates.js.map +1 -1
- package/dist/src/planner/building/select-modifiers.js +7 -2
- package/dist/src/planner/building/select-modifiers.js.map +1 -1
- package/dist/src/planner/building/select.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +2 -2
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/building/update.d.ts.map +1 -1
- package/dist/src/planner/building/update.js +8 -4
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/nodes/join-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/join-node.js +6 -1
- package/dist/src/planner/nodes/join-node.js.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +15 -2
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/schema/manager.d.ts +30 -0
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +205 -0
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/vtab/best-access-plan.d.ts +2 -0
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/memory/layer/scan-plan.js +2 -2
- package/dist/src/vtab/memory/layer/scan-plan.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts +1 -1
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +2 -1
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/module.d.ts +2 -1
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/parser/lexer.ts +806 -771
- package/src/parser/parser.ts +3352 -3347
- package/src/planner/building/select-aggregates.ts +30 -5
- package/src/planner/building/select-modifiers.ts +8 -2
- package/src/planner/building/select.ts +567 -560
- package/src/planner/building/update.ts +9 -5
- package/src/planner/nodes/join-node.ts +6 -1
- package/src/planner/rules/access/rule-select-access-path.ts +399 -384
- package/src/schema/manager.ts +235 -1
- package/src/vtab/best-access-plan.ts +2 -0
- package/src/vtab/memory/layer/scan-plan.ts +2 -2
- package/src/vtab/memory/module.ts +2 -1
- package/src/vtab/module.ts +162 -160
package/src/schema/manager.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Schema } from './schema.js';
|
|
2
2
|
import type { IntegrityAssertionSchema } from './assertion.js';
|
|
3
3
|
import type { Database } from '../core/database.js';
|
|
4
|
-
import type { TableSchema, RowConstraintSchema, IndexSchema } from './table.js';
|
|
4
|
+
import type { TableSchema, RowConstraintSchema, IndexSchema, IndexColumnSchema } from './table.js';
|
|
5
5
|
import type { FunctionSchema } from './function.js';
|
|
6
6
|
import { quereusError, QuereusError } from '../common/errors.js';
|
|
7
7
|
import { StatusCode, type SqlValue } from '../common/types.js';
|
|
@@ -12,6 +12,7 @@ import { buildColumnIndexMap, columnDefToSchema, findPKDefinition, opsToMask, mu
|
|
|
12
12
|
import type { ViewSchema } from './view.js';
|
|
13
13
|
import { createLogger } from '../common/logger.js';
|
|
14
14
|
import type * as AST from '../parser/ast.js';
|
|
15
|
+
import { Parser } from '../parser/parser.js';
|
|
15
16
|
import { SchemaChangeNotifier } from './change-events.js';
|
|
16
17
|
import { checkDeterministic } from '../planner/validation/determinism-validator.js';
|
|
17
18
|
import { buildExpression } from '../planner/building/expression.js';
|
|
@@ -797,4 +798,237 @@ export class SchemaManager {
|
|
|
797
798
|
|
|
798
799
|
return completeTableSchema;
|
|
799
800
|
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Import catalog objects from DDL statements without triggering storage creation.
|
|
804
|
+
* Used when connecting to existing storage that already contains data.
|
|
805
|
+
*
|
|
806
|
+
* This method:
|
|
807
|
+
* 1. Parses each DDL statement
|
|
808
|
+
* 2. Registers the schema objects (tables, indexes)
|
|
809
|
+
* 3. Calls module.connect() instead of module.create()
|
|
810
|
+
* 4. Skips schema change hooks (since these are existing objects)
|
|
811
|
+
*
|
|
812
|
+
* @param ddlStatements Array of DDL strings (CREATE TABLE, CREATE INDEX, etc.)
|
|
813
|
+
* @returns Array of imported object names
|
|
814
|
+
*/
|
|
815
|
+
async importCatalog(ddlStatements: string[]): Promise<{ tables: string[]; indexes: string[] }> {
|
|
816
|
+
const imported = { tables: [] as string[], indexes: [] as string[] };
|
|
817
|
+
|
|
818
|
+
for (const ddl of ddlStatements) {
|
|
819
|
+
try {
|
|
820
|
+
const result = await this.importSingleDDL(ddl);
|
|
821
|
+
if (result.type === 'table') {
|
|
822
|
+
imported.tables.push(result.name);
|
|
823
|
+
} else if (result.type === 'index') {
|
|
824
|
+
imported.indexes.push(result.name);
|
|
825
|
+
}
|
|
826
|
+
} catch (e) {
|
|
827
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
828
|
+
errorLog('Failed to import DDL: %s - Error: %s', ddl.substring(0, 100), message);
|
|
829
|
+
throw e;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
log('Imported catalog: %d tables, %d indexes', imported.tables.length, imported.indexes.length);
|
|
834
|
+
return imported;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Import a single DDL statement without creating storage.
|
|
839
|
+
*/
|
|
840
|
+
private async importSingleDDL(ddl: string): Promise<{ type: 'table' | 'index'; name: string }> {
|
|
841
|
+
// Parse the DDL using the parser
|
|
842
|
+
const parser = new Parser();
|
|
843
|
+
const statements = parser.parseAll(ddl);
|
|
844
|
+
if (statements.length !== 1) {
|
|
845
|
+
throw new QuereusError(`importCatalog expects exactly one statement per DDL, got ${statements.length}`, StatusCode.ERROR);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const stmt = statements[0];
|
|
849
|
+
|
|
850
|
+
if (stmt.type === 'createTable') {
|
|
851
|
+
return this.importTable(stmt as AST.CreateTableStmt);
|
|
852
|
+
} else if (stmt.type === 'createIndex') {
|
|
853
|
+
return this.importIndex(stmt as AST.CreateIndexStmt);
|
|
854
|
+
} else {
|
|
855
|
+
throw new QuereusError(`importCatalog does not support statement type: ${stmt.type}`, StatusCode.ERROR);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Import a table schema without calling module.create().
|
|
861
|
+
* Uses module.connect() to bind to existing storage.
|
|
862
|
+
*/
|
|
863
|
+
private async importTable(stmt: AST.CreateTableStmt): Promise<{ type: 'table'; name: string }> {
|
|
864
|
+
const targetSchemaName = stmt.table.schema || this.getCurrentSchemaName();
|
|
865
|
+
const tableName = stmt.table.name;
|
|
866
|
+
|
|
867
|
+
let moduleName: string;
|
|
868
|
+
let effectiveModuleArgs: Record<string, SqlValue>;
|
|
869
|
+
|
|
870
|
+
if (stmt.moduleName) {
|
|
871
|
+
moduleName = stmt.moduleName;
|
|
872
|
+
effectiveModuleArgs = Object.freeze(stmt.moduleArgs || {});
|
|
873
|
+
} else {
|
|
874
|
+
const defaultVtab = this.getDefaultVTabModule();
|
|
875
|
+
moduleName = defaultVtab.name;
|
|
876
|
+
effectiveModuleArgs = Object.freeze(defaultVtab.args || {});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const moduleInfo = this.getModule(moduleName);
|
|
880
|
+
if (!moduleInfo || !moduleInfo.module) {
|
|
881
|
+
throw new QuereusError(`No virtual table module named '${moduleName}'`, StatusCode.ERROR);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Get default nullability setting from database options
|
|
885
|
+
const defaultNullability = this.db.options.getStringOption('default_column_nullability');
|
|
886
|
+
const defaultNotNull = defaultNullability === 'not_null';
|
|
887
|
+
|
|
888
|
+
const astColumnsToProcess = stmt.columns || [];
|
|
889
|
+
const astConstraintsToProcess = stmt.constraints;
|
|
890
|
+
|
|
891
|
+
const preliminaryColumnSchemas: ColumnSchema[] = astColumnsToProcess.map(colDef => columnDefToSchema(colDef, defaultNotNull));
|
|
892
|
+
const pkDefinition = findPKDefinition(preliminaryColumnSchemas, astConstraintsToProcess);
|
|
893
|
+
|
|
894
|
+
const finalColumnSchemas = preliminaryColumnSchemas.map((col, idx) => {
|
|
895
|
+
const isPkColumn = pkDefinition.some(pkCol => pkCol.index === idx);
|
|
896
|
+
let pkOrder = 0;
|
|
897
|
+
if (isPkColumn) {
|
|
898
|
+
pkOrder = pkDefinition.findIndex(pkC => pkC.index === idx) + 1;
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
...col,
|
|
902
|
+
primaryKey: isPkColumn,
|
|
903
|
+
pkOrder: pkOrder,
|
|
904
|
+
notNull: isPkColumn ? true : col.notNull,
|
|
905
|
+
};
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
const checkConstraintsSchema: RowConstraintSchema[] = [];
|
|
909
|
+
astColumnsToProcess.forEach(colDef => {
|
|
910
|
+
colDef.constraints?.forEach(con => {
|
|
911
|
+
if (con.type === 'check' && con.expr) {
|
|
912
|
+
checkConstraintsSchema.push({
|
|
913
|
+
name: con.name ?? `_check_${colDef.name}`,
|
|
914
|
+
expr: con.expr,
|
|
915
|
+
operations: opsToMask(con.operations),
|
|
916
|
+
deferrable: con.deferrable,
|
|
917
|
+
initiallyDeferred: con.initiallyDeferred
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
(astConstraintsToProcess || []).forEach(con => {
|
|
923
|
+
if (con.type === 'check' && con.expr) {
|
|
924
|
+
checkConstraintsSchema.push({
|
|
925
|
+
name: con.name,
|
|
926
|
+
expr: con.expr,
|
|
927
|
+
operations: opsToMask(con.operations),
|
|
928
|
+
deferrable: con.deferrable,
|
|
929
|
+
initiallyDeferred: con.initiallyDeferred
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// Process mutation context definitions if present
|
|
935
|
+
const mutationContextSchemas = stmt.contextDefinitions
|
|
936
|
+
? stmt.contextDefinitions.map(varDef => mutationContextVarToSchema(varDef, defaultNotNull))
|
|
937
|
+
: undefined;
|
|
938
|
+
|
|
939
|
+
const tableSchema: TableSchema = {
|
|
940
|
+
name: tableName,
|
|
941
|
+
schemaName: targetSchemaName,
|
|
942
|
+
columns: Object.freeze(finalColumnSchemas),
|
|
943
|
+
columnIndexMap: buildColumnIndexMap(finalColumnSchemas),
|
|
944
|
+
primaryKeyDefinition: pkDefinition,
|
|
945
|
+
checkConstraints: Object.freeze(checkConstraintsSchema),
|
|
946
|
+
isTemporary: !!stmt.isTemporary,
|
|
947
|
+
isView: false,
|
|
948
|
+
vtabModuleName: moduleName,
|
|
949
|
+
vtabArgs: effectiveModuleArgs,
|
|
950
|
+
vtabModule: moduleInfo.module,
|
|
951
|
+
vtabAuxData: moduleInfo.auxData,
|
|
952
|
+
estimatedRows: 0,
|
|
953
|
+
mutationContext: mutationContextSchemas ? Object.freeze(mutationContextSchemas) : undefined,
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
// Use connect() instead of create() - the storage already exists
|
|
957
|
+
try {
|
|
958
|
+
moduleInfo.module.connect(
|
|
959
|
+
this.db,
|
|
960
|
+
moduleInfo.auxData,
|
|
961
|
+
moduleName,
|
|
962
|
+
targetSchemaName,
|
|
963
|
+
tableName,
|
|
964
|
+
effectiveModuleArgs as BaseModuleConfig,
|
|
965
|
+
tableSchema // Pass the full schema so the module can use it
|
|
966
|
+
);
|
|
967
|
+
} catch (e: unknown) {
|
|
968
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
969
|
+
throw new QuereusError(`Module '${moduleName}' connect failed during import for table '${tableName}': ${message}`, StatusCode.ERROR);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Ensure schema exists
|
|
973
|
+
let schema = this.getSchema(targetSchemaName);
|
|
974
|
+
if (!schema) {
|
|
975
|
+
schema = new Schema(targetSchemaName);
|
|
976
|
+
this.schemas.set(targetSchemaName.toLowerCase(), schema);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// Register without notifying change listeners (this is an import, not a create)
|
|
980
|
+
schema.addTable(tableSchema);
|
|
981
|
+
log(`Imported table %s.%s using module %s`, targetSchemaName, tableName, moduleName);
|
|
982
|
+
|
|
983
|
+
return { type: 'table', name: `${targetSchemaName}.${tableName}` };
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Import an index schema without calling module.createIndex().
|
|
988
|
+
*/
|
|
989
|
+
private async importIndex(stmt: AST.CreateIndexStmt): Promise<{ type: 'index'; name: string }> {
|
|
990
|
+
const targetSchemaName = stmt.table.schema || this.getCurrentSchemaName();
|
|
991
|
+
const tableName = stmt.table.name;
|
|
992
|
+
const indexName = stmt.index.name;
|
|
993
|
+
|
|
994
|
+
// Find the table
|
|
995
|
+
const tableSchema = this.findTable(tableName, targetSchemaName);
|
|
996
|
+
if (!tableSchema) {
|
|
997
|
+
throw new QuereusError(`Cannot import index '${indexName}': table '${tableName}' not found`, StatusCode.ERROR);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Build index columns schema
|
|
1001
|
+
const indexColumns: IndexColumnSchema[] = stmt.columns.map(col => {
|
|
1002
|
+
const colName = col.name;
|
|
1003
|
+
if (!colName) {
|
|
1004
|
+
throw new QuereusError(`Expression-based index columns are not supported during import`, StatusCode.ERROR);
|
|
1005
|
+
}
|
|
1006
|
+
const colIdx = tableSchema.columnIndexMap.get(colName.toLowerCase());
|
|
1007
|
+
if (colIdx === undefined) {
|
|
1008
|
+
throw new QuereusError(`Column '${colName}' not found in table '${tableName}'`, StatusCode.ERROR);
|
|
1009
|
+
}
|
|
1010
|
+
return {
|
|
1011
|
+
index: colIdx,
|
|
1012
|
+
desc: col.direction === 'desc',
|
|
1013
|
+
};
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
const indexSchema: IndexSchema = {
|
|
1017
|
+
name: indexName,
|
|
1018
|
+
columns: Object.freeze(indexColumns),
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
// Add index to table without calling module.createIndex()
|
|
1022
|
+
const updatedIndexes = [...(tableSchema.indexes || []), indexSchema];
|
|
1023
|
+
const updatedTableSchema: TableSchema = {
|
|
1024
|
+
...tableSchema,
|
|
1025
|
+
indexes: Object.freeze(updatedIndexes),
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const schema = this.getSchemaOrFail(targetSchemaName);
|
|
1029
|
+
schema.addTable(updatedTableSchema);
|
|
1030
|
+
log(`Imported index %s on table %s.%s`, indexName, targetSchemaName, tableName);
|
|
1031
|
+
|
|
1032
|
+
return { type: 'index', name: `${targetSchemaName}.${tableName}.${indexName}` };
|
|
1033
|
+
}
|
|
800
1034
|
}
|
|
@@ -85,6 +85,8 @@ export interface BestAccessPlanResult {
|
|
|
85
85
|
rows: number | undefined;
|
|
86
86
|
/** Ordering guaranteed by this access plan */
|
|
87
87
|
providesOrdering?: readonly OrderingSpec[];
|
|
88
|
+
/** Name of the index that provides the ordering (if any) */
|
|
89
|
+
orderingIndexName?: string;
|
|
88
90
|
/** Whether this plan guarantees unique rows (helps DISTINCT optimization) */
|
|
89
91
|
isSet?: boolean;
|
|
90
92
|
/** Free-text explanation for debugging */
|
|
@@ -52,12 +52,12 @@ export function buildScanPlanFromFilterInfo(filterInfo: FilterInfo, tableSchema:
|
|
|
52
52
|
let upperBound: ScanPlanRangeBound | undefined = undefined;
|
|
53
53
|
const params = new Map<string, string>();
|
|
54
54
|
idxStr?.split(';').forEach(part => { const [key, value] = part.split('=', 2); if (key && value !== undefined) params.set(key, value); });
|
|
55
|
-
const idxNameMatch = params.get('idx')?.match(/^(.*?)
|
|
55
|
+
const idxNameMatch = params.get('idx')?.match(/^(.*?)\((\d+)\)$/);
|
|
56
56
|
if (idxNameMatch) indexName = idxNameMatch[1] === '_primary_' ? 'primary' : idxNameMatch[1];
|
|
57
57
|
const planType = parseInt(params.get('plan') ?? '0', 10);
|
|
58
58
|
descending = params.get('ordCons') === 'DESC' || planType === 1 || planType === 4;
|
|
59
59
|
const argvMap = new Map<number, number>();
|
|
60
|
-
params.get('argvMap')?.match(
|
|
60
|
+
params.get('argvMap')?.match(/\[(\d+),(\d+)\]/g)?.forEach(m => { const p = m.match(/\[(\d+),(\d+)\]/); if (p) argvMap.set(parseInt(p[1]), parseInt(p[2])); });
|
|
61
61
|
const currentSchema = tableSchema;
|
|
62
62
|
const indexSchemaForPlan = indexName === 'primary' ? { name: '_primary_', columns: currentSchema.primaryKeyDefinition ?? [{ index: -1, desc: false, collation: 'BINARY' }] } : currentSchema.indexes?.find(i => i.name === indexName);
|
|
63
63
|
if (planType === 2) { // EQ Plan
|
|
@@ -58,7 +58,7 @@ export class MemoryTableModule implements VirtualTableModule<MemoryTable, Memory
|
|
|
58
58
|
/**
|
|
59
59
|
* Connects to an existing memory table definition
|
|
60
60
|
*/
|
|
61
|
-
connect(db: Database, pAux: unknown, moduleName: string, schemaName: string, tableName: string, _options: MemoryTableConfig): MemoryTable {
|
|
61
|
+
connect(db: Database, pAux: unknown, moduleName: string, schemaName: string, tableName: string, _options: MemoryTableConfig, _tableSchema?: TableSchema): MemoryTable {
|
|
62
62
|
const tableKey = `${schemaName}.${tableName}`.toLowerCase();
|
|
63
63
|
const existingManager = this.tables.get(tableKey);
|
|
64
64
|
|
|
@@ -272,6 +272,7 @@ export class MemoryTableModule implements VirtualTableModule<MemoryTable, Memory
|
|
|
272
272
|
...plan,
|
|
273
273
|
cost: adjustedCost,
|
|
274
274
|
providesOrdering: request.requiredOrdering,
|
|
275
|
+
orderingIndexName: index.name,
|
|
275
276
|
explains: `${plan.explains} with ordering from ${index.name}`
|
|
276
277
|
};
|
|
277
278
|
}
|
package/src/vtab/module.ts
CHANGED
|
@@ -1,160 +1,162 @@
|
|
|
1
|
-
import type { Database } from '../core/database.js'; // Assuming Database class exists
|
|
2
|
-
import type { VirtualTable } from './table.js';
|
|
3
|
-
import type { IndexInfo } from './index-info.js';
|
|
4
|
-
import type { ColumnDef } from '../parser/ast.js'; // <-- Add parser AST import
|
|
5
|
-
import type { TableSchema, IndexSchema } from '../schema/table.js'; // Add import for TableSchema and IndexSchema
|
|
6
|
-
import type { BestAccessPlanRequest, BestAccessPlanResult } from './best-access-plan.js';
|
|
7
|
-
import type { PlanNode } from '../planner/nodes/plan-node.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Base interface for module-specific configuration passed to create/connect.
|
|
11
|
-
* Modules should define their own interface extending this if they need options.
|
|
12
|
-
*/
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
14
|
-
export interface BaseModuleConfig {}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Assessment result from a module's supports() method indicating
|
|
18
|
-
* whether it can execute a plan subtree and at what cost.
|
|
19
|
-
*/
|
|
20
|
-
export interface SupportAssessment {
|
|
21
|
-
/** Estimated cost comparable to local evaluation cost */
|
|
22
|
-
cost: number;
|
|
23
|
-
/** Optional context data persisted for the emitter */
|
|
24
|
-
ctx?: unknown;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Interface defining the methods for a virtual table module implementation.
|
|
29
|
-
* The module primarily acts as a factory for connection-specific VirtualTable instances.
|
|
30
|
-
*
|
|
31
|
-
* @template TTable The specific type of VirtualTable managed by this module.
|
|
32
|
-
* @template TConfig The type defining module-specific configuration options.
|
|
33
|
-
*/
|
|
34
|
-
export interface VirtualTableModule<
|
|
35
|
-
TTable extends VirtualTable,
|
|
36
|
-
TConfig extends BaseModuleConfig = BaseModuleConfig
|
|
37
|
-
> {
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Creates the persistent definition of a virtual table.
|
|
41
|
-
* Called by CREATE VIRTUAL TABLE to define schema and initialize storage.
|
|
42
|
-
*
|
|
43
|
-
* @param db The database connection
|
|
44
|
-
* @param tableSchema The schema definition for the table being created
|
|
45
|
-
* @returns The new VirtualTable instance
|
|
46
|
-
* @throws QuereusError on failure
|
|
47
|
-
*/
|
|
48
|
-
create(
|
|
49
|
-
db: Database,
|
|
50
|
-
tableSchema: TableSchema,
|
|
51
|
-
): TTable;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Connects to an existing virtual table definition.
|
|
55
|
-
* Called when the schema is loaded or a connection needs to interact with the table.
|
|
56
|
-
*
|
|
57
|
-
* @param db The database connection
|
|
58
|
-
* @param pAux Client data passed during module registration
|
|
59
|
-
* @param moduleName The name the module was registered with
|
|
60
|
-
* @param schemaName The name of the database schema
|
|
61
|
-
* @param tableName The name of the virtual table to connect to
|
|
62
|
-
* @param options Module-specific configuration options from the original CREATE VIRTUAL TABLE
|
|
63
|
-
* @
|
|
64
|
-
* @
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* @param
|
|
93
|
-
* @
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* @param
|
|
110
|
-
* @param
|
|
111
|
-
* @param
|
|
112
|
-
* @
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* @param
|
|
129
|
-
* @param
|
|
130
|
-
* @
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
| { type: '
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
*
|
|
159
|
-
|
|
160
|
-
|
|
1
|
+
import type { Database } from '../core/database.js'; // Assuming Database class exists
|
|
2
|
+
import type { VirtualTable } from './table.js';
|
|
3
|
+
import type { IndexInfo } from './index-info.js';
|
|
4
|
+
import type { ColumnDef } from '../parser/ast.js'; // <-- Add parser AST import
|
|
5
|
+
import type { TableSchema, IndexSchema } from '../schema/table.js'; // Add import for TableSchema and IndexSchema
|
|
6
|
+
import type { BestAccessPlanRequest, BestAccessPlanResult } from './best-access-plan.js';
|
|
7
|
+
import type { PlanNode } from '../planner/nodes/plan-node.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base interface for module-specific configuration passed to create/connect.
|
|
11
|
+
* Modules should define their own interface extending this if they need options.
|
|
12
|
+
*/
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
14
|
+
export interface BaseModuleConfig {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Assessment result from a module's supports() method indicating
|
|
18
|
+
* whether it can execute a plan subtree and at what cost.
|
|
19
|
+
*/
|
|
20
|
+
export interface SupportAssessment {
|
|
21
|
+
/** Estimated cost comparable to local evaluation cost */
|
|
22
|
+
cost: number;
|
|
23
|
+
/** Optional context data persisted for the emitter */
|
|
24
|
+
ctx?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Interface defining the methods for a virtual table module implementation.
|
|
29
|
+
* The module primarily acts as a factory for connection-specific VirtualTable instances.
|
|
30
|
+
*
|
|
31
|
+
* @template TTable The specific type of VirtualTable managed by this module.
|
|
32
|
+
* @template TConfig The type defining module-specific configuration options.
|
|
33
|
+
*/
|
|
34
|
+
export interface VirtualTableModule<
|
|
35
|
+
TTable extends VirtualTable,
|
|
36
|
+
TConfig extends BaseModuleConfig = BaseModuleConfig
|
|
37
|
+
> {
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates the persistent definition of a virtual table.
|
|
41
|
+
* Called by CREATE VIRTUAL TABLE to define schema and initialize storage.
|
|
42
|
+
*
|
|
43
|
+
* @param db The database connection
|
|
44
|
+
* @param tableSchema The schema definition for the table being created
|
|
45
|
+
* @returns The new VirtualTable instance
|
|
46
|
+
* @throws QuereusError on failure
|
|
47
|
+
*/
|
|
48
|
+
create(
|
|
49
|
+
db: Database,
|
|
50
|
+
tableSchema: TableSchema,
|
|
51
|
+
): TTable;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Connects to an existing virtual table definition.
|
|
55
|
+
* Called when the schema is loaded or a connection needs to interact with the table.
|
|
56
|
+
*
|
|
57
|
+
* @param db The database connection
|
|
58
|
+
* @param pAux Client data passed during module registration
|
|
59
|
+
* @param moduleName The name the module was registered with
|
|
60
|
+
* @param schemaName The name of the database schema
|
|
61
|
+
* @param tableName The name of the virtual table to connect to
|
|
62
|
+
* @param options Module-specific configuration options from the original CREATE VIRTUAL TABLE
|
|
63
|
+
* @param tableSchema Optional table schema when connecting during import (columns, PK, etc.)
|
|
64
|
+
* @returns The connection-specific VirtualTable instance
|
|
65
|
+
* @throws QuereusError on failure
|
|
66
|
+
*/
|
|
67
|
+
connect(
|
|
68
|
+
db: Database,
|
|
69
|
+
pAux: unknown,
|
|
70
|
+
moduleName: string,
|
|
71
|
+
schemaName: string,
|
|
72
|
+
tableName: string,
|
|
73
|
+
options: TConfig,
|
|
74
|
+
tableSchema?: TableSchema
|
|
75
|
+
): TTable;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Determines if this module can execute a plan subtree starting at the given node.
|
|
79
|
+
* Used for query push-down to virtual table modules that support arbitrary queries.
|
|
80
|
+
*
|
|
81
|
+
* @param node The root node of the subtree to evaluate
|
|
82
|
+
* @returns Assessment with cost and optional context, or undefined if not supported
|
|
83
|
+
*/
|
|
84
|
+
supports?(
|
|
85
|
+
node: PlanNode
|
|
86
|
+
): SupportAssessment | undefined;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Modern, type-safe access planning interface.
|
|
90
|
+
* Preferred over xBestIndex for new implementations.
|
|
91
|
+
*
|
|
92
|
+
* @param db The database connection
|
|
93
|
+
* @param tableInfo The schema information for the table being planned
|
|
94
|
+
* @param request Planning request with constraints and requirements
|
|
95
|
+
* @returns Access plan result describing the chosen strategy
|
|
96
|
+
*/
|
|
97
|
+
getBestAccessPlan?(
|
|
98
|
+
db: Database,
|
|
99
|
+
tableInfo: TableSchema,
|
|
100
|
+
request: BestAccessPlanRequest
|
|
101
|
+
): BestAccessPlanResult;
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Destroys the underlying persistent representation of the virtual table.
|
|
107
|
+
* Called by DROP TABLE.
|
|
108
|
+
*
|
|
109
|
+
* @param db The database connection
|
|
110
|
+
* @param pAux Client data passed during module registration
|
|
111
|
+
* @param moduleName The name the module was registered with
|
|
112
|
+
* @param schemaName The name of the database schema
|
|
113
|
+
* @param tableName The name of the virtual table being destroyed
|
|
114
|
+
* @throws QuereusError on failure
|
|
115
|
+
*/
|
|
116
|
+
destroy(
|
|
117
|
+
db: Database,
|
|
118
|
+
pAux: unknown,
|
|
119
|
+
moduleName: string,
|
|
120
|
+
schemaName: string,
|
|
121
|
+
tableName: string
|
|
122
|
+
): Promise<void>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Creates an index on a virtual table.
|
|
126
|
+
* Called by CREATE INDEX.
|
|
127
|
+
*
|
|
128
|
+
* @param db The database connection
|
|
129
|
+
* @param schemaName The name of the database schema
|
|
130
|
+
* @param tableName The name of the virtual table
|
|
131
|
+
* @param indexSchema The schema definition for the index being created
|
|
132
|
+
* @throws QuereusError on failure
|
|
133
|
+
*/
|
|
134
|
+
createIndex?(
|
|
135
|
+
db: Database,
|
|
136
|
+
schemaName: string,
|
|
137
|
+
tableName: string,
|
|
138
|
+
indexSchema: IndexSchema
|
|
139
|
+
): Promise<void>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Checks for shadow table name conflicts
|
|
143
|
+
* @param name The name to check
|
|
144
|
+
* @returns true if the name would conflict
|
|
145
|
+
*/
|
|
146
|
+
shadowName?(name: string): boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Defines the structure for schema change information passed to xAlterSchema
|
|
151
|
+
*/
|
|
152
|
+
export type SchemaChangeInfo =
|
|
153
|
+
| { type: 'addColumn'; columnDef: ColumnDef }
|
|
154
|
+
| { type: 'dropColumn'; columnName: string }
|
|
155
|
+
| { type: 'renameColumn'; oldName: string; newName: string; newColumnDefAst?: ColumnDef };
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Type alias for the common usage pattern where specific table and config types are not known.
|
|
159
|
+
* Use this for storage scenarios like the SchemaManager where modules of different types are stored together.
|
|
160
|
+
* eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
161
|
+
*/
|
|
162
|
+
export type AnyVirtualTableModule = VirtualTableModule<any, any>;
|