@quereus/quereus 0.6.2 → 0.6.3

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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Hash and encoding utilities
3
+ */
4
+
5
+ /**
6
+ * Computes FNV-1a hash of a string (64-bit variant)
7
+ * Returns an 8-byte array representing the hash
8
+ *
9
+ * FNV-1a is a fast, non-cryptographic hash function with good distribution.
10
+ * See: http://www.isthe.com/chongo/tech/comp/fnv/
11
+ */
12
+ export function fnv1aHash(str: string): Uint8Array {
13
+ // FNV-1a 64-bit parameters
14
+ // Using two 32-bit integers to represent 64-bit hash (JavaScript limitation)
15
+ let hashHigh = 0xcbf29ce4; // Upper 32 bits of FNV offset basis
16
+ let hashLow = 0x84222325; // Lower 32 bits of FNV offset basis
17
+
18
+ const fnvPrimeHigh = 0x00000100; // Upper 32 bits of FNV prime
19
+ const fnvPrimeLow = 0x000001b3; // Lower 32 bits of FNV prime
20
+
21
+ for (let i = 0; i < str.length; i++) {
22
+ const charCode = str.charCodeAt(i);
23
+
24
+ // XOR with byte (handle multi-byte characters)
25
+ hashLow ^= charCode & 0xff;
26
+
27
+ // Multiply by FNV prime (64-bit multiplication using 32-bit parts)
28
+ const aHigh = hashHigh;
29
+ const aLow = hashLow;
30
+
31
+ hashLow = (aLow * fnvPrimeLow) >>> 0;
32
+ hashHigh = (aHigh * fnvPrimeLow + aLow * fnvPrimeHigh + (hashLow / 0x100000000)) >>> 0;
33
+ hashLow = hashLow >>> 0;
34
+
35
+ // Handle high byte of character if present
36
+ if (charCode > 0xff) {
37
+ hashLow ^= (charCode >>> 8) & 0xff;
38
+ const bHigh = hashHigh;
39
+ const bLow = hashLow;
40
+ hashLow = (bLow * fnvPrimeLow) >>> 0;
41
+ hashHigh = (bHigh * fnvPrimeLow + bLow * fnvPrimeHigh + (hashLow / 0x100000000)) >>> 0;
42
+ hashLow = hashLow >>> 0;
43
+ }
44
+ }
45
+
46
+ // Convert to 8-byte array
47
+ const bytes = new Uint8Array(8);
48
+ bytes[0] = (hashHigh >>> 24) & 0xff;
49
+ bytes[1] = (hashHigh >>> 16) & 0xff;
50
+ bytes[2] = (hashHigh >>> 8) & 0xff;
51
+ bytes[3] = hashHigh & 0xff;
52
+ bytes[4] = (hashLow >>> 24) & 0xff;
53
+ bytes[5] = (hashLow >>> 16) & 0xff;
54
+ bytes[6] = (hashLow >>> 8) & 0xff;
55
+ bytes[7] = hashLow & 0xff;
56
+
57
+ return bytes;
58
+ }
59
+
60
+ /**
61
+ * Converts a byte array to base64url encoding
62
+ * (URL-safe base64 without padding: uses - and _ instead of + and /, no = padding)
63
+ *
64
+ * Base64url is defined in RFC 4648 Section 5.
65
+ */
66
+ export function toBase64Url(bytes: Uint8Array): string {
67
+ const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
68
+ let result = '';
69
+
70
+ for (let i = 0; i < bytes.length; i += 3) {
71
+ // Get 3 bytes (24 bits) and convert to 4 base64 characters (6 bits each)
72
+ const byte1 = bytes[i];
73
+ const byte2 = i + 1 < bytes.length ? bytes[i + 1] : 0;
74
+ const byte3 = i + 2 < bytes.length ? bytes[i + 2] : 0;
75
+
76
+ const triplet = (byte1 << 16) | (byte2 << 8) | byte3;
77
+
78
+ result += base64Chars[(triplet >>> 18) & 0x3f];
79
+ result += base64Chars[(triplet >>> 12) & 0x3f];
80
+ if (i + 1 < bytes.length) {
81
+ result += base64Chars[(triplet >>> 6) & 0x3f];
82
+ }
83
+ if (i + 2 < bytes.length) {
84
+ result += base64Chars[triplet & 0x3f];
85
+ }
86
+ }
87
+
88
+ return result;
89
+ }
90
+
@@ -1,256 +1,256 @@
1
- import { VirtualTable } from '../table.js';
2
- import type { AnyVirtualTableModule, SchemaChangeInfo } from '../module.js';
3
- import type { Database } from '../../core/database.js';
4
- import type { Row } from '../../common/types.js';
5
- import { type IndexSchema, type TableSchema } from '../../schema/table.js';
6
- import { MemoryTableManager } from './layer/manager.js';
7
- import type { MemoryTableConnection } from './layer/connection.js';
8
- import { QuereusError } from '../../common/errors.js';
9
- import { StatusCode } from '../../common/types.js';
10
- import type { FilterInfo } from '../filter-info.js';
11
- import { buildScanPlanFromFilterInfo } from './layer/scan-plan.js';
12
- import type { ColumnDef as ASTColumnDef } from '../../parser/ast.js'; // Assuming this will be updated for renameColumn
13
- import { createMemoryTableLoggers } from './utils/logging.js';
14
- import { safeJsonStringify } from '../../util/serialization.js';
15
- import type { VirtualTableConnection } from '../connection.js';
16
- import { MemoryVirtualTableConnection } from './connection.js';
17
- import type { ConflictResolution } from '../../common/constants.js';
18
-
19
- const logger = createMemoryTableLoggers('table');
20
-
21
- /**
22
- * Represents a connection-specific instance of an in-memory table using the layer-based MVCC model.
23
- * This class acts as a thin wrapper around the shared MemoryTableManager,
24
- * holding the connection state.
25
- */
26
- export class MemoryTable extends VirtualTable {
27
- /** @internal The shared manager handling layers, schema, and global state */
28
- public readonly manager: MemoryTableManager;
29
- /** @internal Connection state specific to this table instance (lazily initialized) */
30
- private connection: MemoryTableConnection | null = null;
31
-
32
- /**
33
- * @internal - Use MemoryTableModule.connect or create
34
- * Creates a connection-specific instance linked to a manager.
35
- */
36
- constructor(
37
- db: Database,
38
- module: AnyVirtualTableModule,
39
- manager: MemoryTableManager // Pass the shared manager instance
40
- ) {
41
- // Use manager's schema and name for the base class constructor
42
- super(db, module, manager.schemaName, manager.tableName);
43
- this.manager = manager;
44
- // Set the tableSchema directly from the manager's current canonical schema
45
- // This ensures the VirtualTable base class has the correct schema reference.
46
- this.tableSchema = manager.tableSchema;
47
- }
48
-
49
- /** Returns the canonical schema from the manager */
50
- getSchema(): TableSchema | undefined {
51
- // Always return the potentially updated schema from the manager
52
- return this.manager.tableSchema;
53
- }
54
-
55
- /** Checks read-only status via the manager */
56
- isReadOnly(): boolean {
57
- // Access readOnly via a public method on the manager
58
- return this.manager.isReadOnly;
59
- }
60
-
61
- /** Ensures the connection to the manager is established */
62
- private async ensureConnection(): Promise<MemoryTableConnection> {
63
- if (!this.connection) {
64
- // Check if there's already an active connection for this table in the database
65
- const existingConnections = this.db.getConnectionsForTable(this.tableName);
66
- if (existingConnections.length > 0 && existingConnections[0] instanceof MemoryVirtualTableConnection) {
67
- const memoryVirtualConnection = existingConnections[0] as MemoryVirtualTableConnection;
68
- this.connection = memoryVirtualConnection.getMemoryConnection();
69
- logger.debugLog(`ensureConnection: Reused existing connection ${this.connection.connectionId} for table ${this.tableName}`);
70
- } else {
71
- // Establish connection state with the manager upon first use
72
- this.connection = this.manager.connect();
73
-
74
- // Create a VirtualTableConnection wrapper and register it with the database
75
- const vtabConnection = new MemoryVirtualTableConnection(this.tableName, this.connection);
76
- await this.db.registerConnection(vtabConnection);
77
-
78
- logger.debugLog(`ensureConnection: Created and registered new connection ${this.connection.connectionId} for table ${this.tableName}`);
79
- }
80
- }
81
- return this.connection;
82
- }
83
-
84
- /** Sets an existing connection for this table instance (for transaction reuse) */
85
- setConnection(memoryConnection: MemoryTableConnection): void {
86
- logger.debugLog(`Setting connection ${memoryConnection.connectionId} for table ${this.tableName}`);
87
- this.connection = memoryConnection;
88
- }
89
-
90
- /** Creates a new VirtualTableConnection for transaction support */
91
- createConnection(): VirtualTableConnection {
92
- const memoryConnection = this.manager.connect();
93
- return new MemoryVirtualTableConnection(this.tableName, memoryConnection);
94
- }
95
-
96
- /** Gets the current connection if this table maintains one internally */
97
- getConnection(): VirtualTableConnection | undefined {
98
- if (!this.connection) {
99
- return undefined;
100
- }
101
- return new MemoryVirtualTableConnection(this.tableName, this.connection);
102
- }
103
-
104
- // Direct async iteration for query execution
105
- async* query(filterInfo: FilterInfo): AsyncIterable<Row> {
106
- const conn = await this.ensureConnection();
107
- logger.debugLog(`query using connection ${conn.connectionId} (pending: ${conn.pendingTransactionLayer?.getLayerId()}, read: ${conn.readLayer.getLayerId()})`);
108
- const currentSchema = this.manager.tableSchema;
109
- if (!currentSchema) {
110
- logger.error('query', this.tableName, 'Table schema is undefined');
111
- return;
112
- }
113
- const plan = buildScanPlanFromFilterInfo(filterInfo, currentSchema);
114
- logger.debugLog(`query invoked for ${this.tableName} with plan: ${safeJsonStringify(plan)}`);
115
-
116
- const startLayer = conn.pendingTransactionLayer ?? conn.readLayer;
117
- logger.debugLog(`query reading from layer ${startLayer.getLayerId()}`);
118
-
119
- // Delegate scanning to the manager, which handles layer recursion
120
- yield* this.manager.scanLayer(startLayer, plan);
121
- }
122
-
123
- // Note: getBestAccessPlan is handled by the MemoryTableModule, not the table instance.
124
-
125
- /** Performs mutation through the connection's transaction layer */
126
- async update(
127
- operation: 'insert' | 'update' | 'delete',
128
- values: Row | undefined,
129
- oldKeyValues?: Row,
130
- onConflict?: ConflictResolution
131
- ): Promise<Row | undefined> {
132
- const conn = await this.ensureConnection();
133
- // Delegate mutation to the manager.
134
- // This assumes manager.performMutation will be updated to this signature and logic.
135
- return this.manager.performMutation(conn, operation, values, oldKeyValues, onConflict);
136
- }
137
-
138
- /** Begins a transaction for this connection */
139
- async begin(): Promise<void> {
140
- (await this.ensureConnection()).begin();
141
- }
142
-
143
- /** Commits this connection's transaction */
144
- async commit(): Promise<void> {
145
- // Only commit if a connection has actually been established
146
- if (this.connection) {
147
- await this.connection.commit();
148
- }
149
- }
150
-
151
- /** Rolls back this connection's transaction */
152
- async rollback(): Promise<void> {
153
- // Only rollback if a connection has actually been established
154
- if (this.connection) {
155
- this.connection.rollback();
156
- }
157
- }
158
-
159
- /** Sync operation (currently no-op for memory table layers) */
160
- async sync(): Promise<void> {
161
- // This might trigger background collapse in the manager in the future
162
- // await this.manager.tryCollapseLayers(); // Optional: trigger collapse on sync?
163
- return Promise.resolve();
164
- }
165
-
166
- /** Renames the underlying table via the manager */
167
- async rename(newName: string): Promise<void> {
168
- logger.operation('Rename', this.tableName, { newName });
169
- await this.manager.renameTable(newName);
170
- // Update this instance's schema reference after rename
171
- this.tableSchema = this.manager.tableSchema;
172
- }
173
-
174
- // --- Savepoint operations ---
175
- async savepoint(savepointIndex: number): Promise<void> {
176
- const conn = await this.ensureConnection();
177
- conn.createSavepoint(savepointIndex);
178
- }
179
-
180
- async release(savepointIndex: number): Promise<void> {
181
- if (!this.connection) return; // No connection, no savepoints to release
182
- this.connection.releaseSavepoint(savepointIndex);
183
- }
184
-
185
- async rollbackTo(savepointIndex: number): Promise<void> {
186
- if (!this.connection) return; // No connection, no savepoints to rollback to
187
- this.connection.rollbackToSavepoint(savepointIndex);
188
- }
189
- // --- End Savepoint operations ---
190
-
191
-
192
- /** Handles schema changes via the manager */
193
- async alterSchema(changeInfo: SchemaChangeInfo): Promise<void> {
194
- const originalManagerSchema = this.manager.tableSchema; // For potential error recovery
195
- try {
196
- switch (changeInfo.type) {
197
- case 'addColumn':
198
- await this.manager.addColumn(changeInfo.columnDef);
199
- break;
200
- case 'dropColumn':
201
- await this.manager.dropColumn(changeInfo.columnName);
202
- break;
203
- case 'renameColumn':
204
- if (!('newColumnDefAst' in changeInfo)) {
205
- throw new QuereusError('SchemaChangeInfo for renameColumn missing newColumnDefAst', StatusCode.INTERNAL);
206
- }
207
- await this.manager.renameColumn(changeInfo.oldName, changeInfo.newColumnDefAst as ASTColumnDef);
208
- break;
209
- default: {
210
- const exhaustiveCheck: never = changeInfo;
211
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
- throw new QuereusError(`Unhandled schema change: ${(exhaustiveCheck as any)?.type}`, StatusCode.INTERNAL);
213
- }
214
- }
215
- this.tableSchema = this.manager.tableSchema; // Refresh local schema ref
216
- } catch (e) {
217
- logger.error('Schema Change', this.tableName, e);
218
- // Manager DDL methods should handle reverting their own BaseLayer schema updates on error.
219
- // Refresh local schema ref to ensure it's consistent with manager after potential error/revert.
220
- this.tableSchema = originalManagerSchema;
221
- // It might be safer for manager DDL to not alter its own this.tableSchema until baseLayer op succeeds.
222
- // And if baseLayer op fails, manager DDL reverts baseLayer.tableSchema.
223
- // Then here, we always sync from manager: this.tableSchema = this.manager.tableSchema;
224
- throw e;
225
- }
226
- }
227
-
228
- /** Disconnects this connection instance from the manager */
229
- async disconnect(): Promise<void> {
230
- if (this.connection) {
231
- // Manager handles cleanup and potential layer collapse trigger
232
- await this.manager.disconnect(this.connection.connectionId);
233
- this.connection = null; // Clear connection reference on this instance
234
- }
235
- }
236
-
237
- // --- Index DDL methods delegate to the manager ---
238
- async createIndex(indexSchema: IndexSchema): Promise<void> {
239
- logger.operation('Create Index', this.tableName, { indexName: indexSchema.name });
240
- await this.manager.createIndex(indexSchema);
241
- this.tableSchema = this.manager.tableSchema; // Refresh local schema ref
242
- }
243
-
244
- async dropIndex(indexName: string): Promise<void> {
245
- logger.operation('Drop Index', this.tableName, { indexName });
246
- await this.manager.dropIndex(indexName);
247
- // Update schema reference
248
- this.tableSchema = this.manager.tableSchema;
249
- }
250
- // --- End Index DDL methods ---
251
- }
252
-
253
- // Helper function (moved from MemoryTableCursor and adapted)
254
- // function buildScanPlanInternal(filterInfo: FilterInfo, tableSchema: TableSchema): ScanPlan { ... MOVED ... }
255
-
256
-
1
+ import { VirtualTable } from '../table.js';
2
+ import type { AnyVirtualTableModule, SchemaChangeInfo } from '../module.js';
3
+ import type { Database } from '../../core/database.js';
4
+ import type { Row } from '../../common/types.js';
5
+ import { type IndexSchema, type TableSchema } from '../../schema/table.js';
6
+ import { MemoryTableManager } from './layer/manager.js';
7
+ import type { MemoryTableConnection } from './layer/connection.js';
8
+ import { QuereusError } from '../../common/errors.js';
9
+ import { StatusCode } from '../../common/types.js';
10
+ import type { FilterInfo } from '../filter-info.js';
11
+ import { buildScanPlanFromFilterInfo } from './layer/scan-plan.js';
12
+ import type { ColumnDef as ASTColumnDef } from '../../parser/ast.js'; // Assuming this will be updated for renameColumn
13
+ import { createMemoryTableLoggers } from './utils/logging.js';
14
+ import { safeJsonStringify } from '../../util/serialization.js';
15
+ import type { VirtualTableConnection } from '../connection.js';
16
+ import { MemoryVirtualTableConnection } from './connection.js';
17
+ import type { ConflictResolution } from '../../common/constants.js';
18
+
19
+ const logger = createMemoryTableLoggers('table');
20
+
21
+ /**
22
+ * Represents a connection-specific instance of an in-memory table using the layer-based MVCC model.
23
+ * This class acts as a thin wrapper around the shared MemoryTableManager,
24
+ * holding the connection state.
25
+ */
26
+ export class MemoryTable extends VirtualTable {
27
+ /** @internal The shared manager handling layers, schema, and global state */
28
+ public readonly manager: MemoryTableManager;
29
+ /** @internal Connection state specific to this table instance (lazily initialized) */
30
+ private connection: MemoryTableConnection | null = null;
31
+
32
+ /**
33
+ * @internal - Use MemoryTableModule.connect or create
34
+ * Creates a connection-specific instance linked to a manager.
35
+ */
36
+ constructor(
37
+ db: Database,
38
+ module: AnyVirtualTableModule,
39
+ manager: MemoryTableManager // Pass the shared manager instance
40
+ ) {
41
+ // Use manager's schema and name for the base class constructor
42
+ super(db, module, manager.schemaName, manager.tableName);
43
+ this.manager = manager;
44
+ // Set the tableSchema directly from the manager's current canonical schema
45
+ // This ensures the VirtualTable base class has the correct schema reference.
46
+ this.tableSchema = manager.tableSchema;
47
+ }
48
+
49
+ /** Returns the canonical schema from the manager */
50
+ getSchema(): TableSchema | undefined {
51
+ // Always return the potentially updated schema from the manager
52
+ return this.manager.tableSchema;
53
+ }
54
+
55
+ /** Checks read-only status via the manager */
56
+ isReadOnly(): boolean {
57
+ // Access readOnly via a public method on the manager
58
+ return this.manager.isReadOnly;
59
+ }
60
+
61
+ /** Ensures the connection to the manager is established */
62
+ private async ensureConnection(): Promise<MemoryTableConnection> {
63
+ if (!this.connection) {
64
+ // Check if there's already an active connection for this table in the database
65
+ const existingConnections = this.db.getConnectionsForTable(this.tableName);
66
+ if (existingConnections.length > 0 && existingConnections[0] instanceof MemoryVirtualTableConnection) {
67
+ const memoryVirtualConnection = existingConnections[0] as MemoryVirtualTableConnection;
68
+ this.connection = memoryVirtualConnection.getMemoryConnection();
69
+ logger.debugLog(`ensureConnection: Reused existing connection ${this.connection.connectionId} for table ${this.tableName}`);
70
+ } else {
71
+ // Establish connection state with the manager upon first use
72
+ this.connection = this.manager.connect();
73
+
74
+ // Create a VirtualTableConnection wrapper and register it with the database
75
+ const vtabConnection = new MemoryVirtualTableConnection(this.tableName, this.connection);
76
+ await this.db.registerConnection(vtabConnection);
77
+
78
+ logger.debugLog(`ensureConnection: Created and registered new connection ${this.connection.connectionId} for table ${this.tableName}`);
79
+ }
80
+ }
81
+ return this.connection;
82
+ }
83
+
84
+ /** Sets an existing connection for this table instance (for transaction reuse) */
85
+ setConnection(memoryConnection: MemoryTableConnection): void {
86
+ logger.debugLog(`Setting connection ${memoryConnection.connectionId} for table ${this.tableName}`);
87
+ this.connection = memoryConnection;
88
+ }
89
+
90
+ /** Creates a new VirtualTableConnection for transaction support */
91
+ createConnection(): VirtualTableConnection {
92
+ const memoryConnection = this.manager.connect();
93
+ return new MemoryVirtualTableConnection(this.tableName, memoryConnection);
94
+ }
95
+
96
+ /** Gets the current connection if this table maintains one internally */
97
+ getConnection(): VirtualTableConnection | undefined {
98
+ if (!this.connection) {
99
+ return undefined;
100
+ }
101
+ return new MemoryVirtualTableConnection(this.tableName, this.connection);
102
+ }
103
+
104
+ // Direct async iteration for query execution
105
+ async* query(filterInfo: FilterInfo): AsyncIterable<Row> {
106
+ const conn = await this.ensureConnection();
107
+ logger.debugLog(`query using connection ${conn.connectionId} (pending: ${conn.pendingTransactionLayer?.getLayerId()}, read: ${conn.readLayer.getLayerId()})`);
108
+ const currentSchema = this.manager.tableSchema;
109
+ if (!currentSchema) {
110
+ logger.error('query', this.tableName, 'Table schema is undefined');
111
+ return;
112
+ }
113
+ const plan = buildScanPlanFromFilterInfo(filterInfo, currentSchema);
114
+ logger.debugLog(`query invoked for ${this.tableName} with plan: ${safeJsonStringify(plan)}`);
115
+
116
+ const startLayer = conn.pendingTransactionLayer ?? conn.readLayer;
117
+ logger.debugLog(`query reading from layer ${startLayer.getLayerId()}`);
118
+
119
+ // Delegate scanning to the manager, which handles layer recursion
120
+ yield* this.manager.scanLayer(startLayer, plan);
121
+ }
122
+
123
+ // Note: getBestAccessPlan is handled by the MemoryTableModule, not the table instance.
124
+
125
+ /** Performs mutation through the connection's transaction layer */
126
+ async update(
127
+ operation: 'insert' | 'update' | 'delete',
128
+ values: Row | undefined,
129
+ oldKeyValues?: Row,
130
+ onConflict?: ConflictResolution
131
+ ): Promise<Row | undefined> {
132
+ const conn = await this.ensureConnection();
133
+ // Delegate mutation to the manager.
134
+ // This assumes manager.performMutation will be updated to this signature and logic.
135
+ return this.manager.performMutation(conn, operation, values, oldKeyValues, onConflict);
136
+ }
137
+
138
+ /** Begins a transaction for this connection */
139
+ async begin(): Promise<void> {
140
+ (await this.ensureConnection()).begin();
141
+ }
142
+
143
+ /** Commits this connection's transaction */
144
+ async commit(): Promise<void> {
145
+ // Only commit if a connection has actually been established
146
+ if (this.connection) {
147
+ await this.connection.commit();
148
+ }
149
+ }
150
+
151
+ /** Rolls back this connection's transaction */
152
+ async rollback(): Promise<void> {
153
+ // Only rollback if a connection has actually been established
154
+ if (this.connection) {
155
+ this.connection.rollback();
156
+ }
157
+ }
158
+
159
+ /** Sync operation (currently no-op for memory table layers) */
160
+ async sync(): Promise<void> {
161
+ // This might trigger background collapse in the manager in the future
162
+ // await this.manager.tryCollapseLayers(); // Optional: trigger collapse on sync?
163
+ return Promise.resolve();
164
+ }
165
+
166
+ /** Renames the underlying table via the manager */
167
+ async rename(newName: string): Promise<void> {
168
+ logger.operation('Rename', this.tableName, { newName });
169
+ await this.manager.renameTable(newName);
170
+ // Update this instance's schema reference after rename
171
+ this.tableSchema = this.manager.tableSchema;
172
+ }
173
+
174
+ // --- Savepoint operations ---
175
+ async savepoint(savepointIndex: number): Promise<void> {
176
+ const conn = await this.ensureConnection();
177
+ conn.createSavepoint(savepointIndex);
178
+ }
179
+
180
+ async release(savepointIndex: number): Promise<void> {
181
+ if (!this.connection) return; // No connection, no savepoints to release
182
+ this.connection.releaseSavepoint(savepointIndex);
183
+ }
184
+
185
+ async rollbackTo(savepointIndex: number): Promise<void> {
186
+ if (!this.connection) return; // No connection, no savepoints to rollback to
187
+ this.connection.rollbackToSavepoint(savepointIndex);
188
+ }
189
+ // --- End Savepoint operations ---
190
+
191
+
192
+ /** Handles schema changes via the manager */
193
+ async alterSchema(changeInfo: SchemaChangeInfo): Promise<void> {
194
+ const originalManagerSchema = this.manager.tableSchema; // For potential error recovery
195
+ try {
196
+ switch (changeInfo.type) {
197
+ case 'addColumn':
198
+ await this.manager.addColumn(changeInfo.columnDef);
199
+ break;
200
+ case 'dropColumn':
201
+ await this.manager.dropColumn(changeInfo.columnName);
202
+ break;
203
+ case 'renameColumn':
204
+ if (!('newColumnDefAst' in changeInfo)) {
205
+ throw new QuereusError('SchemaChangeInfo for renameColumn missing newColumnDefAst', StatusCode.INTERNAL);
206
+ }
207
+ await this.manager.renameColumn(changeInfo.oldName, changeInfo.newColumnDefAst as ASTColumnDef);
208
+ break;
209
+ default: {
210
+ const exhaustiveCheck: never = changeInfo;
211
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
+ throw new QuereusError(`Unhandled schema change: ${(exhaustiveCheck as any)?.type}`, StatusCode.INTERNAL);
213
+ }
214
+ }
215
+ this.tableSchema = this.manager.tableSchema; // Refresh local schema ref
216
+ } catch (e) {
217
+ logger.error('Schema Change', this.tableName, e);
218
+ // Manager DDL methods should handle reverting their own BaseLayer schema updates on error.
219
+ // Refresh local schema ref to ensure it's consistent with manager after potential error/revert.
220
+ this.tableSchema = originalManagerSchema;
221
+ // It might be safer for manager DDL to not alter its own this.tableSchema until baseLayer op succeeds.
222
+ // And if baseLayer op fails, manager DDL reverts baseLayer.tableSchema.
223
+ // Then here, we always sync from manager: this.tableSchema = this.manager.tableSchema;
224
+ throw e;
225
+ }
226
+ }
227
+
228
+ /** Disconnects this connection instance from the manager */
229
+ async disconnect(): Promise<void> {
230
+ if (this.connection) {
231
+ // Manager handles cleanup and potential layer collapse trigger
232
+ await this.manager.disconnect(this.connection.connectionId);
233
+ this.connection = null; // Clear connection reference on this instance
234
+ }
235
+ }
236
+
237
+ // --- Index DDL methods delegate to the manager ---
238
+ async createIndex(indexSchema: IndexSchema): Promise<void> {
239
+ logger.operation('Create Index', this.tableName, { indexName: indexSchema.name });
240
+ await this.manager.createIndex(indexSchema);
241
+ this.tableSchema = this.manager.tableSchema; // Refresh local schema ref
242
+ }
243
+
244
+ async dropIndex(indexName: string): Promise<void> {
245
+ logger.operation('Drop Index', this.tableName, { indexName });
246
+ await this.manager.dropIndex(indexName);
247
+ // Update schema reference
248
+ this.tableSchema = this.manager.tableSchema;
249
+ }
250
+ // --- End Index DDL methods ---
251
+ }
252
+
253
+ // Helper function (moved from MemoryTableCursor and adapted)
254
+ // function buildScanPlanInternal(filterInfo: FilterInfo, tableSchema: TableSchema): ScanPlan { ... MOVED ... }
255
+
256
+