@quereus/isolation 0.2.0
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/README.md +216 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/isolated-connection.d.ts +32 -0
- package/dist/src/isolated-connection.d.ts.map +1 -0
- package/dist/src/isolated-connection.js +71 -0
- package/dist/src/isolated-connection.js.map +1 -0
- package/dist/src/isolated-table.d.ts +219 -0
- package/dist/src/isolated-table.d.ts.map +1 -0
- package/dist/src/isolated-table.js +785 -0
- package/dist/src/isolated-table.js.map +1 -0
- package/dist/src/isolation-module.d.ts +134 -0
- package/dist/src/isolation-module.d.ts.map +1 -0
- package/dist/src/isolation-module.js +231 -0
- package/dist/src/isolation-module.js.map +1 -0
- package/dist/src/isolation-types.d.ts +34 -0
- package/dist/src/isolation-types.d.ts.map +1 -0
- package/dist/src/isolation-types.js +2 -0
- package/dist/src/isolation-types.js.map +1 -0
- package/dist/src/merge-iterator.d.ts +37 -0
- package/dist/src/merge-iterator.d.ts.map +1 -0
- package/dist/src/merge-iterator.js +98 -0
- package/dist/src/merge-iterator.js.map +1 -0
- package/dist/src/merge-types.d.ts +61 -0
- package/dist/src/merge-types.d.ts.map +1 -0
- package/dist/src/merge-types.js +2 -0
- package/dist/src/merge-types.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# @quereus/isolation
|
|
2
|
+
|
|
3
|
+
Generic transaction isolation layer for Quereus virtual table modules.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@quereus/isolation` package provides MVCC-style transaction isolation semantics for any Quereus virtual table module. It wraps existing modules to add:
|
|
8
|
+
|
|
9
|
+
- **Read-your-own-writes** — See uncommitted changes within your transaction
|
|
10
|
+
- **Snapshot isolation** — Consistent reads throughout the transaction
|
|
11
|
+
- **Savepoint support** — Nested transaction control
|
|
12
|
+
- **ACID semantics** — Full transaction guarantees
|
|
13
|
+
|
|
14
|
+
This allows module authors to focus on storage concerns while getting isolation "for free."
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
yarn add @quereus/isolation @quereus/quereus
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Database, MemoryTableModule } from '@quereus/quereus';
|
|
26
|
+
import { IsolationModule } from '@quereus/isolation';
|
|
27
|
+
|
|
28
|
+
const db = new Database();
|
|
29
|
+
|
|
30
|
+
// Create any underlying module (memory, store, custom, etc.)
|
|
31
|
+
const memoryModule = new MemoryTableModule();
|
|
32
|
+
|
|
33
|
+
// Wrap it with the isolation layer
|
|
34
|
+
const isolatedModule = new IsolationModule({
|
|
35
|
+
underlying: memoryModule,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
db.registerModule('isolated', isolatedModule);
|
|
39
|
+
|
|
40
|
+
// Use it like any other module, but with full isolation
|
|
41
|
+
await db.exec(`CREATE TABLE users (
|
|
42
|
+
id INTEGER PRIMARY KEY,
|
|
43
|
+
name TEXT
|
|
44
|
+
) USING isolated`);
|
|
45
|
+
|
|
46
|
+
await db.exec('BEGIN');
|
|
47
|
+
await db.exec(`INSERT INTO users VALUES (1, 'Alice')`);
|
|
48
|
+
|
|
49
|
+
// Reads see uncommitted changes
|
|
50
|
+
const user = await db.get('SELECT * FROM users WHERE id = 1');
|
|
51
|
+
console.log(user.name); // 'Alice'
|
|
52
|
+
|
|
53
|
+
await db.exec('COMMIT'); // Or ROLLBACK
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Architecture
|
|
57
|
+
|
|
58
|
+
The isolation layer operates at the **row level**, merging query results from two modules:
|
|
59
|
+
|
|
60
|
+
1. **Overlay module** — Stores uncommitted changes (inserts, updates, deletes as tombstones)
|
|
61
|
+
2. **Underlying module** — Stores committed data
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
┌─────────────────────────────────────────────────────────┐
|
|
65
|
+
│ IsolationModule │
|
|
66
|
+
│ │
|
|
67
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
68
|
+
│ │ Overlay Module (e.g., memory vtab) │ │
|
|
69
|
+
│ │ - Stores pending inserts, updates, tombstones │ │
|
|
70
|
+
│ │ - Per-connection isolation │ │
|
|
71
|
+
│ └────────────────────────────────────────────────────┘ │
|
|
72
|
+
│ │ │
|
|
73
|
+
│ │ row-level merge │
|
|
74
|
+
│ ▼ │
|
|
75
|
+
│ ┌────────────────────────────────────────────────────┐ │
|
|
76
|
+
│ │ Underlying Module (any) │ │
|
|
77
|
+
│ │ - LevelDB / IndexedDB store │ │
|
|
78
|
+
│ │ - Custom module without isolation │ │
|
|
79
|
+
│ └────────────────────────────────────────────────────┘ │
|
|
80
|
+
└─────────────────────────────────────────────────────────┘
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Key Features
|
|
84
|
+
|
|
85
|
+
**Per-connection overlay** — Each database instance gets its own overlay storage, ensuring proper isolation between connections.
|
|
86
|
+
|
|
87
|
+
**Lazy overlay creation** — No memory overhead until the first write in a transaction.
|
|
88
|
+
|
|
89
|
+
**Configurable overlay module** — Use memory for fast transactions, or persistent storage for large transactions:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { IsolationModule } from '@quereus/isolation';
|
|
93
|
+
import { MemoryTableModule } from '@quereus/quereus';
|
|
94
|
+
import { StoreModule } from '@quereus/store';
|
|
95
|
+
|
|
96
|
+
// Fast, ephemeral overlay (default)
|
|
97
|
+
const isolatedModule = new IsolationModule({
|
|
98
|
+
underlying: myStoreModule,
|
|
99
|
+
overlay: new MemoryTableModule(),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Or use persistent overlay for large transactions
|
|
103
|
+
const isolatedModule = new IsolationModule({
|
|
104
|
+
underlying: myStoreModule,
|
|
105
|
+
overlay: new StoreModule(tempStoreProvider),
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API
|
|
110
|
+
|
|
111
|
+
### `IsolationModule`
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
class IsolationModule implements VirtualTableModule {
|
|
115
|
+
constructor(config: IsolationModuleConfig);
|
|
116
|
+
getCapabilities(): ModuleCapabilities;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Configuration
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
interface IsolationModuleConfig {
|
|
124
|
+
/** Module to wrap with isolation semantics */
|
|
125
|
+
underlying: VirtualTableModule<any, any>;
|
|
126
|
+
|
|
127
|
+
/** Optional overlay module (defaults to MemoryTableModule) */
|
|
128
|
+
overlay?: VirtualTableModule<any, any>;
|
|
129
|
+
|
|
130
|
+
/** Optional tombstone column name (defaults to '_tombstone') */
|
|
131
|
+
tombstoneColumn?: string;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Merge Utilities
|
|
136
|
+
|
|
137
|
+
The package also exports low-level utilities for merging sorted streams:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { mergeStreams, createMergeEntry, createTombstone } from '@quereus/isolation';
|
|
141
|
+
|
|
142
|
+
// Merge two sorted streams (overlay and underlying)
|
|
143
|
+
const merged = mergeStreams(overlayStream, underlyingStream, {
|
|
144
|
+
comparePK: (a, b) => /* compare primary keys */,
|
|
145
|
+
extractPK: (row) => /* extract PK from row */,
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
See the [design document](https://github.com/gotchoices/quereus/blob/main/packages/quereus/docs/design-isolation-layer.md) for detailed architecture and implementation notes.
|
|
150
|
+
|
|
151
|
+
## Use Cases
|
|
152
|
+
|
|
153
|
+
### Store Module Isolation
|
|
154
|
+
|
|
155
|
+
The `@quereus/store` package provides a convenience function:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { createIsolatedStoreModule } from '@quereus/store';
|
|
159
|
+
import { createLevelDBProvider } from '@quereus/plugin-leveldb';
|
|
160
|
+
|
|
161
|
+
const provider = createLevelDBProvider({ basePath: './data' });
|
|
162
|
+
const module = createIsolatedStoreModule({ provider });
|
|
163
|
+
|
|
164
|
+
db.registerModule('store', module);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Custom Module Isolation
|
|
168
|
+
|
|
169
|
+
Wrap any custom module:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { IsolationModule } from '@quereus/isolation';
|
|
173
|
+
import { MyCustomModule } from './my-module';
|
|
174
|
+
|
|
175
|
+
const isolatedModule = new IsolationModule({
|
|
176
|
+
underlying: new MyCustomModule(),
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Checking Capabilities
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const caps = isolatedModule.getCapabilities();
|
|
184
|
+
console.log(caps.isolation); // true
|
|
185
|
+
console.log(caps.savepoints); // true
|
|
186
|
+
console.log(caps.persistent); // (from underlying module)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Performance
|
|
190
|
+
|
|
191
|
+
The isolation layer adds minimal overhead:
|
|
192
|
+
|
|
193
|
+
- **Fast path** — No overlay merging if no writes have occurred
|
|
194
|
+
- **Point lookups** — O(log n) overlay check + underlying lookup
|
|
195
|
+
- **Range scans** — Streaming merge of sorted results
|
|
196
|
+
|
|
197
|
+
For performance-critical applications, consider:
|
|
198
|
+
- Using memory overlay for small transactions
|
|
199
|
+
- The memory vtab uses integrated isolation (no separate layer)
|
|
200
|
+
|
|
201
|
+
## Testing
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
yarn test
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
210
|
+
|
|
211
|
+
## Related Packages
|
|
212
|
+
|
|
213
|
+
- [@quereus/quereus](https://www.npmjs.com/package/@quereus/quereus) — Core SQL engine
|
|
214
|
+
- [@quereus/store](https://www.npmjs.com/package/@quereus/store) — Abstract key-value storage
|
|
215
|
+
- [@quereus/plugin-leveldb](https://www.npmjs.com/package/@quereus/plugin-leveldb) — LevelDB storage
|
|
216
|
+
- [@quereus/plugin-indexeddb](https://www.npmjs.com/package/@quereus/plugin-indexeddb) — IndexedDB storage
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { MergeEntry, MergeConfig, PKComparator, PKExtractor, SortKeyComparator, SortKeyExtractor } from './merge-types.js';
|
|
2
|
+
export { mergeStreams, createMergeEntry, createTombstone } from './merge-iterator.js';
|
|
3
|
+
export type { IsolationModuleConfig } from './isolation-types.js';
|
|
4
|
+
export type { UnderlyingTableState } from './isolation-module.js';
|
|
5
|
+
export { IsolationModule } from './isolation-module.js';
|
|
6
|
+
export { IsolatedTable } from './isolated-table.js';
|
|
7
|
+
export { IsolatedConnection } from './isolated-connection.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAChI,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtF,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAGlE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { mergeStreams, createMergeEntry, createTombstone } from './merge-iterator.js';
|
|
2
|
+
// Isolation layer classes
|
|
3
|
+
export { IsolationModule } from './isolation-module.js';
|
|
4
|
+
export { IsolatedTable } from './isolated-table.js';
|
|
5
|
+
export { IsolatedConnection } from './isolated-connection.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAMtF,0BAA0B;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { VirtualTableConnection } from '@quereus/quereus';
|
|
2
|
+
/**
|
|
3
|
+
* Callback interface for IsolatedTable to receive transaction events.
|
|
4
|
+
*/
|
|
5
|
+
export interface IsolatedTableCallback {
|
|
6
|
+
onConnectionCommit(): Promise<void>;
|
|
7
|
+
onConnectionRollback(): Promise<void>;
|
|
8
|
+
onConnectionSavepoint(index: number): Promise<void>;
|
|
9
|
+
onConnectionReleaseSavepoint(index: number): Promise<void>;
|
|
10
|
+
onConnectionRollbackToSavepoint(index: number): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Connection wrapper that coordinates transactions across overlay and underlying.
|
|
14
|
+
* When commit/rollback are called by the database, this connection calls back
|
|
15
|
+
* to the IsolatedTable to perform flush/clear operations.
|
|
16
|
+
*/
|
|
17
|
+
export declare class IsolatedConnection implements VirtualTableConnection {
|
|
18
|
+
readonly connectionId: string;
|
|
19
|
+
readonly tableName: string;
|
|
20
|
+
private readonly underlyingConnection;
|
|
21
|
+
private readonly overlayConnection;
|
|
22
|
+
private readonly tableCallback;
|
|
23
|
+
constructor(tableName: string, underlyingConnection: VirtualTableConnection | undefined, overlayConnection: VirtualTableConnection | undefined, tableCallback?: IsolatedTableCallback);
|
|
24
|
+
begin(): Promise<void>;
|
|
25
|
+
commit(): Promise<void>;
|
|
26
|
+
rollback(): Promise<void>;
|
|
27
|
+
createSavepoint(index: number): Promise<void>;
|
|
28
|
+
releaseSavepoint(index: number): Promise<void>;
|
|
29
|
+
rollbackToSavepoint(index: number): Promise<void>;
|
|
30
|
+
disconnect(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=isolated-connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isolated-connection.d.ts","sourceRoot":"","sources":["../../src/isolated-connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAK/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,+BAA+B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,sBAAsB;IAChE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqC;IAC1E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqC;IACvE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoC;gBAGjE,SAAS,EAAE,MAAM,EACjB,oBAAoB,EAAE,sBAAsB,GAAG,SAAS,EACxD,iBAAiB,EAAE,sBAAsB,GAAG,SAAS,EACrD,aAAa,CAAC,EAAE,qBAAqB;IAShC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAUvB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAazB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7C,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ9C,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQjD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAIjC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
let connectionIdCounter = 0;
|
|
2
|
+
/**
|
|
3
|
+
* Connection wrapper that coordinates transactions across overlay and underlying.
|
|
4
|
+
* When commit/rollback are called by the database, this connection calls back
|
|
5
|
+
* to the IsolatedTable to perform flush/clear operations.
|
|
6
|
+
*/
|
|
7
|
+
export class IsolatedConnection {
|
|
8
|
+
connectionId;
|
|
9
|
+
tableName;
|
|
10
|
+
underlyingConnection;
|
|
11
|
+
overlayConnection;
|
|
12
|
+
tableCallback;
|
|
13
|
+
constructor(tableName, underlyingConnection, overlayConnection, tableCallback) {
|
|
14
|
+
this.connectionId = `isolated-${++connectionIdCounter}`;
|
|
15
|
+
this.tableName = tableName;
|
|
16
|
+
this.underlyingConnection = underlyingConnection;
|
|
17
|
+
this.overlayConnection = overlayConnection;
|
|
18
|
+
this.tableCallback = tableCallback;
|
|
19
|
+
}
|
|
20
|
+
async begin() {
|
|
21
|
+
await this.underlyingConnection?.begin();
|
|
22
|
+
await this.overlayConnection?.begin();
|
|
23
|
+
}
|
|
24
|
+
async commit() {
|
|
25
|
+
// Call table's commit logic (flush overlay to underlying)
|
|
26
|
+
if (this.tableCallback) {
|
|
27
|
+
await this.tableCallback.onConnectionCommit();
|
|
28
|
+
}
|
|
29
|
+
// Then commit the underlying connections
|
|
30
|
+
await this.overlayConnection?.commit();
|
|
31
|
+
await this.underlyingConnection?.commit();
|
|
32
|
+
}
|
|
33
|
+
async rollback() {
|
|
34
|
+
// Call table's rollback logic (clear overlay)
|
|
35
|
+
if (this.tableCallback) {
|
|
36
|
+
await this.tableCallback.onConnectionRollback();
|
|
37
|
+
}
|
|
38
|
+
// Only rollback the overlay - the underlying table should NOT be rolled back
|
|
39
|
+
// because it only contains committed data (flushed during commit)
|
|
40
|
+
await this.overlayConnection?.rollback();
|
|
41
|
+
// Note: We intentionally do NOT rollback underlyingConnection here.
|
|
42
|
+
// The underlying table only receives writes during commit flush,
|
|
43
|
+
// and those writes should be committed, not rolled back.
|
|
44
|
+
}
|
|
45
|
+
async createSavepoint(index) {
|
|
46
|
+
if (this.tableCallback) {
|
|
47
|
+
await this.tableCallback.onConnectionSavepoint(index);
|
|
48
|
+
}
|
|
49
|
+
await this.underlyingConnection?.createSavepoint(index);
|
|
50
|
+
await this.overlayConnection?.createSavepoint(index);
|
|
51
|
+
}
|
|
52
|
+
async releaseSavepoint(index) {
|
|
53
|
+
if (this.tableCallback) {
|
|
54
|
+
await this.tableCallback.onConnectionReleaseSavepoint(index);
|
|
55
|
+
}
|
|
56
|
+
await this.underlyingConnection?.releaseSavepoint(index);
|
|
57
|
+
await this.overlayConnection?.releaseSavepoint(index);
|
|
58
|
+
}
|
|
59
|
+
async rollbackToSavepoint(index) {
|
|
60
|
+
if (this.tableCallback) {
|
|
61
|
+
await this.tableCallback.onConnectionRollbackToSavepoint(index);
|
|
62
|
+
}
|
|
63
|
+
await this.overlayConnection?.rollbackToSavepoint(index);
|
|
64
|
+
await this.underlyingConnection?.rollbackToSavepoint(index);
|
|
65
|
+
}
|
|
66
|
+
async disconnect() {
|
|
67
|
+
await this.overlayConnection?.disconnect();
|
|
68
|
+
await this.underlyingConnection?.disconnect();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=isolated-connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isolated-connection.js","sourceRoot":"","sources":["../../src/isolated-connection.ts"],"names":[],"mappings":"AAGA,IAAI,mBAAmB,GAAG,CAAC,CAAC;AAa5B;;;;GAIG;AACH,MAAM,OAAO,kBAAkB;IACrB,YAAY,CAAS;IACrB,SAAS,CAAS;IAEV,oBAAoB,CAAqC;IACzD,iBAAiB,CAAqC;IACtD,aAAa,CAAoC;IAElE,YACC,SAAiB,EACjB,oBAAwD,EACxD,iBAAqD,EACrD,aAAqC;QAErC,IAAI,CAAC,YAAY,GAAG,YAAY,EAAE,mBAAmB,EAAE,CAAC;QACxD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,oBAAoB,GAAG,oBAAoB,CAAC;QACjD,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;QAC3C,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,KAAK;QACV,MAAM,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC;QACzC,MAAM,IAAI,CAAC,iBAAiB,EAAE,KAAK,EAAE,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,MAAM;QACX,0DAA0D;QAC1D,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,aAAa,CAAC,kBAAkB,EAAE,CAAC;QAC/C,CAAC;QACD,yCAAyC;QACzC,MAAM,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC;QACvC,MAAM,IAAI,CAAC,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,QAAQ;QACb,8CAA8C;QAC9C,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,aAAa,CAAC,oBAAoB,EAAE,CAAC;QACjD,CAAC;QACD,6EAA6E;QAC7E,kEAAkE;QAClE,MAAM,IAAI,CAAC,iBAAiB,EAAE,QAAQ,EAAE,CAAC;QACzC,oEAAoE;QACpE,iEAAiE;QACjE,yDAAyD;IAC1D,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,KAAa;QAClC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;QACvD,CAAC;QACD,MAAM,IAAI,CAAC,oBAAoB,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,KAAa;QACnC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,aAAa,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,IAAI,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,KAAa;QACtC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,aAAa,CAAC,+BAA+B,CAAC,KAAK,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,IAAI,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,UAAU;QACf,MAAM,IAAI,CAAC,iBAAiB,EAAE,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,CAAC,oBAAoB,EAAE,UAAU,EAAE,CAAC;IAC/C,CAAC;CACD"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { Database, MaybePromise, Row, TableIndexSchema as IndexSchema, FilterInfo, SchemaChangeInfo, UpdateArgs, VirtualTableConnection } from '@quereus/quereus';
|
|
2
|
+
import { VirtualTable } from '@quereus/quereus';
|
|
3
|
+
import type { IsolationModule } from './isolation-module.js';
|
|
4
|
+
import { type IsolatedTableCallback } from './isolated-connection.js';
|
|
5
|
+
/**
|
|
6
|
+
* A table wrapper that provides transaction isolation via an overlay.
|
|
7
|
+
*
|
|
8
|
+
* Each IsolatedTable instance accesses a connection-scoped overlay that is:
|
|
9
|
+
* - Created lazily on first write
|
|
10
|
+
* - Shared across all IsolatedTable instances in the same transaction
|
|
11
|
+
* - Stored in the IsolationModule's connection overlay map
|
|
12
|
+
*
|
|
13
|
+
* This provides true per-connection isolation - each connection's uncommitted
|
|
14
|
+
* changes are invisible to other connections, but visible to all queries
|
|
15
|
+
* within the same connection.
|
|
16
|
+
*
|
|
17
|
+
* Reads merge overlay changes with underlying data.
|
|
18
|
+
* Writes go to overlay only until commit.
|
|
19
|
+
*/
|
|
20
|
+
export declare class IsolatedTable extends VirtualTable implements IsolatedTableCallback {
|
|
21
|
+
private readonly isolationModule;
|
|
22
|
+
private readonly underlyingTable;
|
|
23
|
+
private registeredConnection;
|
|
24
|
+
constructor(db: Database, module: IsolationModule, underlyingTable: VirtualTable);
|
|
25
|
+
/**
|
|
26
|
+
* Gets the tombstone column name from the module.
|
|
27
|
+
*/
|
|
28
|
+
private get tombstoneColumn();
|
|
29
|
+
/**
|
|
30
|
+
* Gets the connection-scoped overlay state, or undefined if no overlay exists yet.
|
|
31
|
+
*/
|
|
32
|
+
private getOverlayState;
|
|
33
|
+
/**
|
|
34
|
+
* Gets the overlay table, or undefined if no overlay exists yet.
|
|
35
|
+
*/
|
|
36
|
+
private get overlayTable();
|
|
37
|
+
/**
|
|
38
|
+
* Gets whether this connection has uncommitted changes.
|
|
39
|
+
*/
|
|
40
|
+
private get hasChanges();
|
|
41
|
+
/**
|
|
42
|
+
* Sets the hasChanges flag in the connection-scoped overlay state.
|
|
43
|
+
*/
|
|
44
|
+
private setHasChanges;
|
|
45
|
+
/**
|
|
46
|
+
* Lazily creates the overlay table on first write.
|
|
47
|
+
*
|
|
48
|
+
* The overlay is stored in connection-scoped storage, so it persists
|
|
49
|
+
* across multiple IsolatedTable instances within the same transaction.
|
|
50
|
+
*
|
|
51
|
+
* The schema is obtained from the underlying table at this point,
|
|
52
|
+
* supporting scenarios where schema is discovered lazily from storage.
|
|
53
|
+
*/
|
|
54
|
+
private ensureOverlay;
|
|
55
|
+
/**
|
|
56
|
+
* Ensures a connection is registered with the database for transaction coordination.
|
|
57
|
+
* This is called before any read or write operation.
|
|
58
|
+
*/
|
|
59
|
+
private ensureConnection;
|
|
60
|
+
/**
|
|
61
|
+
* Creates a new isolated connection for transaction support.
|
|
62
|
+
*/
|
|
63
|
+
createConnection(): MaybePromise<VirtualTableConnection>;
|
|
64
|
+
private createConnectionAsync;
|
|
65
|
+
/**
|
|
66
|
+
* Query the table, merging overlay with underlying.
|
|
67
|
+
*
|
|
68
|
+
* When overlay is empty or doesn't exist, delegates directly to underlying for efficiency.
|
|
69
|
+
* When overlay has changes, merges both streams using the appropriate key order.
|
|
70
|
+
*
|
|
71
|
+
* For primary key scans: merge by PK order
|
|
72
|
+
* For secondary index scans: merge by (indexKey, PK) order
|
|
73
|
+
*/
|
|
74
|
+
query(filterInfo: FilterInfo): AsyncIterable<Row>;
|
|
75
|
+
/**
|
|
76
|
+
* Wrapper that ensures connection before merging.
|
|
77
|
+
*/
|
|
78
|
+
private mergedQueryWithConnection;
|
|
79
|
+
/**
|
|
80
|
+
* Performs merged query combining overlay and underlying data.
|
|
81
|
+
*
|
|
82
|
+
* Both overlay and underlying are queried with the same FilterInfo (for the overlay,
|
|
83
|
+
* adjusted for its schema with tombstone column). Both streams return rows in the
|
|
84
|
+
* same order (by PK for primary scans, by index key for secondary index scans).
|
|
85
|
+
*
|
|
86
|
+
* The merge iterator combines them using the appropriate sort key comparator.
|
|
87
|
+
*/
|
|
88
|
+
private mergedQuery;
|
|
89
|
+
/**
|
|
90
|
+
* Parses FilterInfo to determine which index is being used.
|
|
91
|
+
* Returns null for full table scan or primary key scan, index name for secondary indexes.
|
|
92
|
+
*/
|
|
93
|
+
private parseIndexFromFilterInfo;
|
|
94
|
+
/**
|
|
95
|
+
* Gets the column indices for a secondary index.
|
|
96
|
+
*/
|
|
97
|
+
private getIndexColumnIndices;
|
|
98
|
+
/**
|
|
99
|
+
* Adapts FilterInfo for the overlay table schema (which has an extra tombstone column).
|
|
100
|
+
* The constraints and index references remain the same since the overlay has matching indexes.
|
|
101
|
+
*/
|
|
102
|
+
private adaptFilterInfoForOverlay;
|
|
103
|
+
/**
|
|
104
|
+
* Queries the overlay table and converts rows to MergeEntry format.
|
|
105
|
+
*
|
|
106
|
+
* Uses the same FilterInfo as the underlying query so both streams are in the same order.
|
|
107
|
+
* For secondary index scans, the sort key includes both the index key and primary key.
|
|
108
|
+
*/
|
|
109
|
+
private queryOverlayAsMergeEntries;
|
|
110
|
+
/**
|
|
111
|
+
* Builds the sort key for a row based on the index being scanned.
|
|
112
|
+
*
|
|
113
|
+
* For primary key scans: sort key is the PK
|
|
114
|
+
* For secondary index scans: sort key is [indexKeyParts..., pkParts...]
|
|
115
|
+
*/
|
|
116
|
+
private buildSortKey;
|
|
117
|
+
/**
|
|
118
|
+
* Builds the merge configuration using this table's key functions.
|
|
119
|
+
*
|
|
120
|
+
* For primary key scans: compare by PK
|
|
121
|
+
* For secondary index scans: compare by (indexKey, PK) using underlying's comparator
|
|
122
|
+
*
|
|
123
|
+
* @param indexInfo Which index is being scanned. Defaults to primary key scan.
|
|
124
|
+
*/
|
|
125
|
+
private buildMergeConfig;
|
|
126
|
+
/**
|
|
127
|
+
* Gets the primary key comparator, preferring the underlying table's comparator.
|
|
128
|
+
*/
|
|
129
|
+
private getComparePK;
|
|
130
|
+
/**
|
|
131
|
+
* Builds a sort key comparator for secondary index scans.
|
|
132
|
+
*
|
|
133
|
+
* Compares by index key columns first, then by PK columns.
|
|
134
|
+
*/
|
|
135
|
+
private buildCompareSortKey;
|
|
136
|
+
/**
|
|
137
|
+
* Gets the index of the tombstone column in overlay rows.
|
|
138
|
+
*/
|
|
139
|
+
private getTombstoneColumnIndex;
|
|
140
|
+
/**
|
|
141
|
+
* Gets the primary key column indices from the underlying table schema.
|
|
142
|
+
*/
|
|
143
|
+
getPrimaryKeyIndices(): number[];
|
|
144
|
+
/**
|
|
145
|
+
* Performs INSERT, UPDATE, or DELETE on the overlay.
|
|
146
|
+
* Changes are not visible to underlying until commit.
|
|
147
|
+
*
|
|
148
|
+
* The overlay is created lazily on first write, using schema from the underlying table.
|
|
149
|
+
*/
|
|
150
|
+
update(args: UpdateArgs): Promise<Row | undefined>;
|
|
151
|
+
/**
|
|
152
|
+
* Checks if a row with the given primary key exists in the overlay.
|
|
153
|
+
*/
|
|
154
|
+
private rowExistsInOverlay;
|
|
155
|
+
/**
|
|
156
|
+
* Gets a row from the overlay by primary key.
|
|
157
|
+
*/
|
|
158
|
+
private getOverlayRow;
|
|
159
|
+
/**
|
|
160
|
+
* Creates a FilterInfo for a full table scan (no constraints).
|
|
161
|
+
*/
|
|
162
|
+
private createFullScanFilterInfo;
|
|
163
|
+
begin(): Promise<void>;
|
|
164
|
+
sync(): Promise<void>;
|
|
165
|
+
commit(): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Flushes all overlay changes to the underlying table.
|
|
168
|
+
* Called during commit to persist changes.
|
|
169
|
+
*
|
|
170
|
+
* This method manages the underlying table's transaction lifecycle independently
|
|
171
|
+
* to ensure that flushed data is committed and won't be rolled back by subsequent
|
|
172
|
+
* transaction rollbacks.
|
|
173
|
+
*/
|
|
174
|
+
private flushOverlayToUnderlying;
|
|
175
|
+
/**
|
|
176
|
+
* Checks if a row with the given primary key exists in the underlying table.
|
|
177
|
+
*/
|
|
178
|
+
private rowExistsInUnderlying;
|
|
179
|
+
rollback(): Promise<void>;
|
|
180
|
+
/**
|
|
181
|
+
* Clears the connection-scoped overlay and resets state.
|
|
182
|
+
*/
|
|
183
|
+
private clearOverlay;
|
|
184
|
+
savepoint(index: number): Promise<void>;
|
|
185
|
+
release(index: number): Promise<void>;
|
|
186
|
+
rollbackTo(index: number): Promise<void>;
|
|
187
|
+
disconnect(): Promise<void>;
|
|
188
|
+
rename(newName: string): Promise<void>;
|
|
189
|
+
alterSchema(changeInfo: SchemaChangeInfo): Promise<void>;
|
|
190
|
+
createIndex(indexInfo: IndexSchema): Promise<void>;
|
|
191
|
+
dropIndex(indexName: string): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Returns whether there are pending uncommitted changes.
|
|
194
|
+
*/
|
|
195
|
+
hasPendingChanges(): boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Called by IsolatedConnection when the database commits.
|
|
198
|
+
* Flushes overlay to underlying and clears overlay.
|
|
199
|
+
*/
|
|
200
|
+
onConnectionCommit(): Promise<void>;
|
|
201
|
+
/**
|
|
202
|
+
* Called by IsolatedConnection when the database rolls back.
|
|
203
|
+
* Clears overlay without flushing.
|
|
204
|
+
*/
|
|
205
|
+
onConnectionRollback(): Promise<void>;
|
|
206
|
+
/**
|
|
207
|
+
* Called by IsolatedConnection when a savepoint is created.
|
|
208
|
+
*/
|
|
209
|
+
onConnectionSavepoint(index: number): Promise<void>;
|
|
210
|
+
/**
|
|
211
|
+
* Called by IsolatedConnection when a savepoint is released.
|
|
212
|
+
*/
|
|
213
|
+
onConnectionReleaseSavepoint(index: number): Promise<void>;
|
|
214
|
+
/**
|
|
215
|
+
* Called by IsolatedConnection when rolling back to a savepoint.
|
|
216
|
+
*/
|
|
217
|
+
onConnectionRollbackToSavepoint(index: number): Promise<void>;
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=isolated-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isolated-table.d.ts","sourceRoot":"","sources":["../../src/isolated-table.ts"],"names":[],"mappings":"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"}
|