Skip to content

Commit 1ae1356

Browse files
Copilotmathiasrw
andcommitted
Implement proper transaction state management for IndexedDB
- Added storeTable and restoreTable helper functions - Modified intoTable and fromTable to work with in-memory data when autocommit is OFF - Implemented begin, commit, and rollback with proper state management - Updated attachDatabase to load data into memory when autocommit is OFF - Added comprehensive tests for transaction behavior with COMMIT and ROLLBACK Co-authored-by: mathiasrw <[email protected]>
1 parent 099bab5 commit 1ae1356

File tree

2 files changed

+353
-24
lines changed

2 files changed

+353
-24
lines changed

src/91indexeddb.js

Lines changed: 293 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,14 @@ IDB.attachDatabase = async function (ixdbid, dbid, args, params, cb) {
187187
db.tables[stores[i]] = {};
188188
}
189189

190-
/*/*
191-
if (!alasql.options.autocommit) {
192-
if (db.tables) {
193-
for(var tbid in db.tables) {
194-
db.tables[tbid].data = LS.get(db.lsdbid+'.'+tbid);
195-
}
190+
// IF AUTOCOMMIT IS OFF then copy data to memory
191+
if (!alasql.options.autocommit) {
192+
if (db.tables) {
193+
for (var tbid in db.tables) {
194+
IDB.restoreTable(dbid || ixdbid, tbid);
196195
}
197196
}
198-
*/
197+
}
199198

200199
if (cb) cb(1);
201200
};
@@ -333,11 +332,59 @@ IDB.dropTable = async function (databaseid, tableid, ifexists, cb) {
333332
*/
334333

335334
IDB.intoTable = function (databaseid, tableid, value, columns, cb) {
336-
const ixdbid = alasql.databases[databaseid].ixdbid;
337-
const request = indexedDB.open(ixdbid);
338335
var db = alasql.databases[databaseid];
339336
var table = db.tables[tableid];
340337

338+
// In autocommit mode, work with in-memory data
339+
if (!alasql.options.autocommit) {
340+
// Ensure table data is loaded
341+
if (!table.data) {
342+
IDB.restoreTable(databaseid, tableid, () => {
343+
// After loading, add values
344+
if (!table.data) table.data = [];
345+
table.data = table.data.concat(value);
346+
347+
// Execute triggers
348+
for (var tr in table.afterinsert) {
349+
if (table.afterinsert[tr]) {
350+
var trigger = table.afterinsert[tr];
351+
if (trigger.funcid) {
352+
alasql.fn[trigger.funcid](value);
353+
} else if (trigger.statement) {
354+
trigger.statement.execute(databaseid);
355+
}
356+
}
357+
}
358+
359+
if (cb) cb(value.length);
360+
});
361+
return;
362+
}
363+
364+
// Table already loaded, just add to memory
365+
if (!table.data) table.data = [];
366+
table.data = table.data.concat(value);
367+
368+
// Execute triggers
369+
for (var tr in table.afterinsert) {
370+
if (table.afterinsert[tr]) {
371+
var trigger = table.afterinsert[tr];
372+
if (trigger.funcid) {
373+
alasql.fn[trigger.funcid](value);
374+
} else if (trigger.statement) {
375+
trigger.statement.execute(databaseid);
376+
}
377+
}
378+
}
379+
380+
if (cb) cb(value.length);
381+
return;
382+
}
383+
384+
// In autocommit mode, write directly to IndexedDB
385+
const ixdbid = db.ixdbid;
386+
const request = indexedDB.open(ixdbid);
387+
341388
request.onupgradeneeded = evt => {
342389
evt.target.transaction.abort();
343390
const err = new Error(
@@ -371,7 +418,27 @@ IDB.intoTable = function (databaseid, tableid, value, columns, cb) {
371418
};
372419

373420
IDB.fromTable = function (databaseid, tableid, cb, idx, query) {
374-
const ixdbid = alasql.databases[databaseid].ixdbid;
421+
var db = alasql.databases[databaseid];
422+
var table = db.tables[tableid];
423+
424+
// In transaction mode (autocommit OFF), read from memory
425+
if (!alasql.options.autocommit) {
426+
// Ensure table data is loaded
427+
if (!table.data) {
428+
IDB.restoreTable(databaseid, tableid, () => {
429+
const res = table.data || [];
430+
if (cb) cb(res, idx, query);
431+
});
432+
return;
433+
}
434+
435+
const res = table.data || [];
436+
if (cb) cb(res, idx, query);
437+
return;
438+
}
439+
440+
// In autocommit mode, read directly from IndexedDB
441+
const ixdbid = db.ixdbid;
375442
const request = indexedDB.open(ixdbid);
376443

377444
request.onupgradeneeded = evt => {
@@ -454,34 +521,236 @@ IDB.updateTable = function (databaseid, tableid, assignfn, wherefn, params, cb)
454521
};
455522
};
456523

524+
/**
525+
* Store table structure and data into IndexedDB
526+
* @param databaseid {string} AlaSQL database id
527+
* @param tableid {string} Table name
528+
* @param cb {function} Optional callback
529+
*/
530+
IDB.storeTable = function (databaseid, tableid, cb) {
531+
const db = alasql.databases[databaseid];
532+
const table = db.tables[tableid];
533+
const ixdbid = db.ixdbid;
534+
535+
if (!table || !table.data) {
536+
if (cb) cb(0);
537+
return;
538+
}
539+
540+
const request = indexedDB.open(ixdbid);
541+
542+
request.onerror = () => {
543+
if (cb) cb(null, new Error('Failed to open IndexedDB'));
544+
};
545+
546+
request.onsuccess = () => {
547+
const ixdb = request.result;
548+
549+
// Clear existing data first
550+
const clearTx = ixdb.transaction([tableid], 'readwrite');
551+
const clearStore = clearTx.objectStore(tableid);
552+
const clearReq = clearStore.clear();
553+
554+
clearReq.onsuccess = () => {
555+
// Now add all data
556+
const tx = ixdb.transaction([tableid], 'readwrite');
557+
const tb = tx.objectStore(tableid);
558+
559+
for (let i = 0; i < table.data.length; i++) {
560+
tb.add(table.data[i]);
561+
}
562+
563+
tx.oncomplete = () => {
564+
ixdb.close();
565+
if (cb) cb(table.data.length);
566+
};
567+
568+
tx.onerror = () => {
569+
ixdb.close();
570+
if (cb) cb(null, new Error('Failed to store table data'));
571+
};
572+
};
573+
574+
clearReq.onerror = () => {
575+
ixdb.close();
576+
if (cb) cb(null, new Error('Failed to clear table'));
577+
};
578+
};
579+
};
580+
581+
/**
582+
* Restore table structure and data from IndexedDB to memory
583+
* @param databaseid {string} AlaSQL database id
584+
* @param tableid {string} Table name
585+
* @param cb {function} Optional callback
586+
*/
587+
IDB.restoreTable = function (databaseid, tableid, cb) {
588+
const db = alasql.databases[databaseid];
589+
const ixdbid = db.ixdbid;
590+
591+
// Initialize table if it doesn't exist
592+
if (!db.tables[tableid]) {
593+
db.tables[tableid] = new alasql.Table();
594+
}
595+
596+
const table = db.tables[tableid];
597+
598+
const request = indexedDB.open(ixdbid);
599+
600+
request.onerror = () => {
601+
if (cb) cb(null, new Error('Failed to open IndexedDB'));
602+
};
603+
604+
request.onsuccess = () => {
605+
const ixdb = request.result;
606+
const tx = ixdb.transaction([tableid]);
607+
const store = tx.objectStore(tableid);
608+
const getAllReq = store.getAll();
609+
610+
getAllReq.onsuccess = () => {
611+
table.data = getAllReq.result || [];
612+
ixdb.close();
613+
if (cb) cb(table.data.length);
614+
};
615+
616+
getAllReq.onerror = () => {
617+
ixdb.close();
618+
if (cb) cb(null, new Error('Failed to restore table data'));
619+
};
620+
};
621+
622+
return table;
623+
};
624+
457625
/**
458626
* Begin transaction for IndexedDB
459-
* Note: IndexedDB handles transactions internally, so this is a no-op
460-
* that just acknowledges the transaction start
627+
* Implements transaction state management similar to LOCALSTORAGE
461628
*/
462629
IDB.begin = function (databaseid, cb) {
463-
// IndexedDB manages transactions internally at the operation level
464-
// This method is here for compatibility with the transaction API
465-
return cb ? cb(1) : 1;
630+
const db = alasql.databases[databaseid];
631+
632+
// Store snapshot of current state for rollback
633+
if (!db.engineid || db.engineid !== 'INDEXEDDB') {
634+
return cb ? cb(1) : 1;
635+
}
636+
637+
// In autocommit mode, begin just commits current state
638+
if (alasql.options.autocommit) {
639+
return IDB.commit(databaseid, cb);
640+
}
641+
642+
// In transaction mode, ensure data is loaded in memory
643+
const tablesToLoad = [];
644+
for (const tbid in db.tables) {
645+
if (db.tables[tbid] && !db.tables[tbid].data) {
646+
tablesToLoad.push(tbid);
647+
}
648+
}
649+
650+
if (tablesToLoad.length === 0) {
651+
return cb ? cb(1) : 1;
652+
}
653+
654+
// Load all tables that aren't already in memory
655+
let loaded = 0;
656+
const checkComplete = () => {
657+
loaded++;
658+
if (loaded === tablesToLoad.length) {
659+
if (cb) cb(1);
660+
}
661+
};
662+
663+
tablesToLoad.forEach(tbid => {
664+
IDB.restoreTable(databaseid, tbid, checkComplete);
665+
});
666+
667+
if (tablesToLoad.length === 0) {
668+
return cb ? cb(1) : 1;
669+
}
466670
};
467671

468672
/**
469673
* Commit transaction for IndexedDB
470-
* Note: IndexedDB handles commits internally, so this is a no-op
674+
* Persists in-memory data to IndexedDB
471675
*/
472676
IDB.commit = function (databaseid, cb) {
473-
// IndexedDB automatically commits transactions when operations complete
474-
// This method is here for compatibility with the transaction API
475-
return cb ? cb(1) : 1;
677+
const db = alasql.databases[databaseid];
678+
679+
if (!db.engineid || db.engineid !== 'INDEXEDDB') {
680+
return cb ? cb(1) : 1;
681+
}
682+
683+
const tablesToStore = [];
684+
for (const tbid in db.tables) {
685+
if (db.tables[tbid] && db.tables[tbid].data) {
686+
tablesToStore.push(tbid);
687+
}
688+
}
689+
690+
if (tablesToStore.length === 0) {
691+
return cb ? cb(1) : 1;
692+
}
693+
694+
let stored = 0;
695+
let hasError = false;
696+
697+
const checkComplete = (res, err) => {
698+
if (err && !hasError) {
699+
hasError = true;
700+
if (cb) cb(null, err);
701+
return;
702+
}
703+
704+
stored++;
705+
if (stored === tablesToStore.length && !hasError) {
706+
if (cb) cb(1);
707+
}
708+
};
709+
710+
tablesToStore.forEach(tbid => {
711+
IDB.storeTable(databaseid, tbid, checkComplete);
712+
});
476713
};
477714

478715
/**
479716
* Rollback transaction for IndexedDB
480-
* Note: IndexedDB handles rollbacks internally, so this is a no-op
717+
* Restores data from IndexedDB, discarding in-memory changes
481718
*/
482719
IDB.rollback = function (databaseid, cb) {
483-
// IndexedDB automatically rolls back transactions on errors
484-
// Manual rollback is not supported in the same way as other engines
485-
// This method is here for compatibility with the transaction API
486-
return cb ? cb(1) : 1;
720+
const db = alasql.databases[databaseid];
721+
722+
if (!db.engineid || db.engineid !== 'INDEXEDDB') {
723+
return cb ? cb(1) : 1;
724+
}
725+
726+
const tablesToRestore = [];
727+
for (const tbid in db.tables) {
728+
if (db.tables[tbid]) {
729+
tablesToRestore.push(tbid);
730+
}
731+
}
732+
733+
if (tablesToRestore.length === 0) {
734+
return cb ? cb(1) : 1;
735+
}
736+
737+
let restored = 0;
738+
let hasError = false;
739+
740+
const checkComplete = (res, err) => {
741+
if (err && !hasError) {
742+
hasError = true;
743+
if (cb) cb(null, err);
744+
return;
745+
}
746+
747+
restored++;
748+
if (restored === tablesToRestore.length && !hasError) {
749+
if (cb) cb(1);
750+
}
751+
};
752+
753+
tablesToRestore.forEach(tbid => {
754+
IDB.restoreTable(databaseid, tbid, checkComplete);
755+
});
487756
};

0 commit comments

Comments
 (0)