diff --git a/src/91indexeddb.js b/src/91indexeddb.js index b779669366..4032b3bea4 100755 --- a/src/91indexeddb.js +++ b/src/91indexeddb.js @@ -184,9 +184,26 @@ IDB.createTable = async function (databaseid, tableid, ifnotexists, cb) { throw err; } + // Get primary key information from the table definition + const table = alasql.databases[databaseid].tables[tableid]; + const pkColumns = table && table.pk && table.pk.columns; + + // Build object store options + let storeOptions; + if (pkColumns && pkColumns.length === 1) { + // Single-column primary key: use keyPath + storeOptions = {keyPath: pkColumns[0]}; + } else if (pkColumns && pkColumns.length > 1) { + // Composite primary key: use array keyPath + storeOptions = {keyPath: pkColumns}; + } else { + // No primary key: use auto-increment + storeOptions = {autoIncrement: true}; + } + const request = indexedDB.open(ixdbid, found.version + 1); request.onupgradeneeded = function (event) { - request.result.createObjectStore(tableid, {autoIncrement: true}); + request.result.createObjectStore(tableid, storeOptions); }; request.onsuccess = function (event) { request.result.close(); @@ -299,8 +316,23 @@ IDB.intoTable = function (databaseid, tableid, value, columns, cb) { var ixdb = request.result; var tx = ixdb.transaction([tableid], 'readwrite'); var tb = tx.objectStore(tableid); + var errorHandled = false; for (var i = 0, ilen = value.length; i < ilen; i++) { - tb.add(value[i]); + var addRequest = tb.add(value[i]); + addRequest.onerror = function (evt) { + // Handle duplicate key errors + if (!errorHandled && evt.target.error && evt.target.error.name === 'ConstraintError') { + errorHandled = true; + evt.preventDefault(); + evt.stopPropagation(); + tx.abort(); + ixdb.close(); + var err = new Error( + 'Cannot insert record, because it already exists in primary key index' + ); + if (cb) cb(null, err); + } + }; } tx.oncomplete = function () { ixdb.close(); diff --git a/test/test1292.js b/test/test1292.js new file mode 100644 index 0000000000..3c017e5f8c --- /dev/null +++ b/test/test1292.js @@ -0,0 +1,64 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +describe('Test 1292 - Primary key enforcement prevents duplicate values', function () { + const test = '1292'; + + before(function () { + alasql('create database test' + test); + alasql('use test' + test); + }); + + after(function () { + alasql('drop database test' + test); + }); + + it('A) Primary key column constraint prevents duplicates', function () { + alasql('CREATE TABLE settings (setting varchar(50) PRIMARY KEY, val varchar(300))'); + alasql("INSERT INTO settings (setting, val) values ('domain', 'http')"); + + // Inserting a duplicate primary key should throw an error + assert.throws(function () { + alasql("INSERT INTO settings (setting, val) values ('domain', 'https')"); + }, /already exists in primary key/); + + // Verify only one record exists + var res = alasql('SELECT * FROM settings'); + assert.deepEqual(res, [{setting: 'domain', val: 'http'}]); + + alasql('DROP TABLE settings'); + }); + + it('B) Primary key table constraint prevents duplicates', function () { + alasql('CREATE TABLE settings2 (setting varchar(50), val varchar(300), PRIMARY KEY (setting))'); + alasql("INSERT INTO settings2 (setting, val) values ('domain', 'http')"); + + // Inserting a duplicate primary key should throw an error + assert.throws(function () { + alasql("INSERT INTO settings2 (setting, val) values ('domain', 'https')"); + }, /already exists in primary key/); + + // Verify only one record exists + var res = alasql('SELECT * FROM settings2'); + assert.deepEqual(res, [{setting: 'domain', val: 'http'}]); + + alasql('DROP TABLE settings2'); + }); + + it('C) Primary key allows different key values', function () { + alasql('CREATE TABLE settings3 (setting varchar(50) PRIMARY KEY, val varchar(300))'); + alasql("INSERT INTO settings3 (setting, val) values ('domain', 'http')"); + alasql("INSERT INTO settings3 (setting, val) values ('port', '8080')"); + + // Verify both records exist + var res = alasql('SELECT * FROM settings3 ORDER BY setting'); + assert.deepEqual(res, [ + {setting: 'domain', val: 'http'}, + {setting: 'port', val: '8080'}, + ]); + + alasql('DROP TABLE settings3'); + }); +});