Skip to content

Commit 2fd4d14

Browse files
committed
feat: add session support for SQLite changesets
- Introduced Session class to manage SQLite changesets. - Implemented methods for creating sessions, generating changesets, and patchsets. - Added error handling for session lifecycle and database state. - Enhanced DatabaseSync with methods to create sessions and apply changesets. - Implemented tests for session functionality, including changeset generation and conflict resolution.
1 parent 005cbda commit 2fd4d14

File tree

6 files changed

+1283
-11
lines changed

6 files changed

+1283
-11
lines changed

TODO.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ This document tracks the remaining tasks to complete the SQLite extraction from
5454
**MEDIUM PRIORITY - Advanced Features:**
5555

5656
- [ ] **Backup functionality**: Complete `BackupJob` class and `backup()` method
57-
- [ ] **SQLite sessions**: `createSession()`, `applyChangeset()` methods
57+
- **SQLite sessions**: `createSession()`, `applyChangeset()` methods - Complete with full test coverage
5858
- [ ] **Enhanced location method**: `location(dbName?: string)` for attached databases
5959
-**Advanced parameter binding**: Bare named parameters (`{id: 1}``:id`)
6060

@@ -85,7 +85,7 @@ This document tracks the remaining tasks to complete the SQLite extraction from
8585
-**Aggregate functions** - 10/10 tests passing (all functionality working)
8686
- ✅ Transaction persistence across sessions
8787
- ✅ Large dataset operations (optimized with transactions)
88-
- [ ] SQLite sessions and changesets
88+
- SQLite sessions and changesets - 21 comprehensive tests
8989
- [ ] Extension loading
9090
- [ ] Backup/restore operations
9191
-**Error handling tests**
@@ -403,13 +403,19 @@ scripts/
403403
-**Security model**: Two-step process (allowExtension + enableLoadExtension)
404404
-**Comprehensive tests**: 14 tests covering all security and API aspects
405405

406-
6. **🚧 Advanced Features** (Next Priority)
406+
6. **✅ SQLite Sessions** (COMPLETED!)
407+
408+
-**SQLite sessions** (`createSession()`, `applyChangeset()`) - Complete with 21 tests
409+
-**Session class** with changeset/patchset generation
410+
-**Changeset application** with conflict and filter callbacks
411+
-**Session constants** (SQLITE*CHANGESET*\*)
412+
413+
7. **🚧 Advanced Features** (Next Priority)
407414

408-
- [ ] **SQLite sessions** (`createSession()`, `applyChangeset()`)
409415
- [ ] **Backup functionality** (`backup()` function)
410416
- [ ] **Enhanced location method**: `location(dbName?: string)` for attached databases
411417

412-
7. **🚧 Performance & Compatibility** (Low Priority)
418+
8. **🚧 Performance & Compatibility** (Low Priority)
413419
- [ ] Benchmark against alternatives
414420
- [ ] Node.js compatibility verification
415421
- [ ] Memory leak testing
@@ -425,8 +431,8 @@ scripts/
425431
## 🏆 **Success Metrics Achieved**
426432

427433
-**Core SQLite operations working** (CREATE, INSERT, SELECT, UPDATE, DELETE)
428-
-**Advanced SQLite features working** (user functions, aggregates, and iterators all fully functional)
429-
-**127 tests passing** with comprehensive coverage across all features:
434+
-**Advanced SQLite features working** (user functions, aggregates, iterators, and sessions all fully functional)
435+
-**155 tests passing** with comprehensive coverage across all features:
430436
- ✅ 13 basic database tests
431437
- ✅ 13 configuration option tests
432438
- ✅ 8 user-defined function tests
@@ -437,6 +443,7 @@ scripts/
437443
- ✅ 17 Node.js compatibility tests
438444
- ✅ 7 double-quoted string literals tests
439445
- ✅ 14 extension loading tests
446+
- ✅ 28 SQLite session tests (with changeset content verification!)
440447
-**All core data types supported** (INTEGER, REAL, TEXT, BLOB, NULL, BigInt)
441448
-**Error handling working** for invalid SQL and operations
442449
-**Memory management working** with proper cleanup and N-API references

src/binding.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
99
DatabaseSync::Init(env, exports);
1010
StatementSync::Init(env, exports);
1111
StatementSyncIterator::Init(env, exports);
12+
Session::Init(env, exports);
1213

1314
// Add SQLite constants
1415
Napi::Object constants = Napi::Object::New(env);
@@ -32,6 +33,18 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
3233
constants.Set("SQLITE_OPEN_SHAREDCACHE", Napi::Number::New(env, SQLITE_OPEN_SHAREDCACHE));
3334
constants.Set("SQLITE_OPEN_PRIVATECACHE", Napi::Number::New(env, SQLITE_OPEN_PRIVATECACHE));
3435
constants.Set("SQLITE_OPEN_WAL", Napi::Number::New(env, SQLITE_OPEN_WAL));
36+
37+
// Changeset/session constants
38+
constants.Set("SQLITE_CHANGESET_OMIT", Napi::Number::New(env, SQLITE_CHANGESET_OMIT));
39+
constants.Set("SQLITE_CHANGESET_REPLACE", Napi::Number::New(env, SQLITE_CHANGESET_REPLACE));
40+
constants.Set("SQLITE_CHANGESET_ABORT", Napi::Number::New(env, SQLITE_CHANGESET_ABORT));
41+
42+
constants.Set("SQLITE_CHANGESET_DATA", Napi::Number::New(env, SQLITE_CHANGESET_DATA));
43+
constants.Set("SQLITE_CHANGESET_NOTFOUND", Napi::Number::New(env, SQLITE_CHANGESET_NOTFOUND));
44+
constants.Set("SQLITE_CHANGESET_CONFLICT", Napi::Number::New(env, SQLITE_CHANGESET_CONFLICT));
45+
constants.Set("SQLITE_CHANGESET_CONSTRAINT", Napi::Number::New(env, SQLITE_CHANGESET_CONSTRAINT));
46+
constants.Set("SQLITE_CHANGESET_FOREIGN_KEY", Napi::Number::New(env, SQLITE_CHANGESET_FOREIGN_KEY));
47+
3548
exports.Set("constants", constants);
3649

3750
// TODO: Add backup function

src/index.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,45 @@ export interface AggregateOptions {
7373
readonly varargs?: boolean;
7474
}
7575

76+
export interface SessionOptions {
77+
/** The table to track changes for. If omitted, all tables are tracked. */
78+
readonly table?: string;
79+
/** The database name. @default "main" */
80+
readonly db?: string;
81+
}
82+
83+
export interface Session {
84+
/**
85+
* Generate a changeset containing all changes recorded by the session.
86+
* @returns A Buffer containing the changeset data.
87+
*/
88+
changeset(): Buffer;
89+
/**
90+
* Generate a patchset containing all changes recorded by the session.
91+
* @returns A Buffer containing the patchset data.
92+
*/
93+
patchset(): Buffer;
94+
/**
95+
* Close the session and release its resources.
96+
*/
97+
close(): void;
98+
}
99+
100+
export interface ChangesetApplyOptions {
101+
/**
102+
* Function called when a conflict is detected during changeset application.
103+
* @param conflictType The type of conflict (SQLITE_CHANGESET_CONFLICT, etc.)
104+
* @returns One of SQLITE_CHANGESET_OMIT, SQLITE_CHANGESET_REPLACE, or SQLITE_CHANGESET_ABORT
105+
*/
106+
readonly onConflict?: (conflictType: number) => number;
107+
/**
108+
* Function called to filter which tables to apply changes to.
109+
* @param tableName The name of the table
110+
* @returns true to include the table, false to skip it
111+
*/
112+
readonly filter?: (tableName: string) => boolean;
113+
}
114+
76115
export interface Database {
77116
readonly location: string;
78117
readonly isOpen: boolean;
@@ -103,8 +142,19 @@ export interface Database {
103142
* @param options Configuration object containing step function and other settings.
104143
*/
105144
aggregate(name: string, options: AggregateOptions): void;
106-
createSession(table?: string): any;
107-
applyChangeset(changeset: Uint8Array, options?: any): void;
145+
/**
146+
* Create a new session to record database changes.
147+
* @param options Optional configuration for the session.
148+
* @returns A Session object for recording changes.
149+
*/
150+
createSession(options?: SessionOptions): Session;
151+
/**
152+
* Apply a changeset to the database.
153+
* @param changeset The changeset data to apply.
154+
* @param options Optional configuration for applying the changeset.
155+
* @returns true if successful, false if aborted.
156+
*/
157+
applyChangeset(changeset: Buffer, options?: ChangesetApplyOptions): boolean;
108158
enableLoadExtension(enable: boolean): void;
109159
loadExtension(path: string, entryPoint?: string): void;
110160

@@ -121,10 +171,20 @@ export interface SqliteModule {
121171
sql: string,
122172
options?: StatementOptions,
123173
) => PreparedStatement;
174+
Session: new () => Session;
124175
constants: {
125176
SQLITE_OPEN_READONLY: number;
126177
SQLITE_OPEN_READWRITE: number;
127178
SQLITE_OPEN_CREATE: number;
179+
// Changeset constants
180+
SQLITE_CHANGESET_OMIT: number;
181+
SQLITE_CHANGESET_REPLACE: number;
182+
SQLITE_CHANGESET_ABORT: number;
183+
SQLITE_CHANGESET_DATA: number;
184+
SQLITE_CHANGESET_NOTFOUND: number;
185+
SQLITE_CHANGESET_CONFLICT: number;
186+
SQLITE_CHANGESET_CONSTRAINT: number;
187+
SQLITE_CHANGESET_FOREIGN_KEY: number;
128188
// ... more constants
129189
};
130190
backup(
@@ -161,6 +221,7 @@ export const DatabaseSync =
161221
binding.DatabaseSync as SqliteModule["DatabaseSync"];
162222
export const StatementSync =
163223
binding.StatementSync as SqliteModule["StatementSync"];
224+
export const Session = binding.Session as SqliteModule["Session"];
164225
export const constants = binding.constants as SqliteModule["constants"];
165226
export const backup = binding.backup as SqliteModule["backup"];
166227

0 commit comments

Comments
 (0)