diff --git a/index.js b/index.js index 8017ab5..73d70a7 100644 --- a/index.js +++ b/index.js @@ -20,23 +20,43 @@ async function createDoubleDb (dataDirectory) { const db = new Level(dataDirectory); - async function addToIndexes (id, object, prefix = '') { - const promises = Object.keys(object).map(key => { - if (isObject(object[key])) { - addToIndexes(id, object[key], prefix + '.' + key); - return null; - } + async function addToIndexes(id, object, prefix = '') { + const promises = []; - if (Array.isArray(object[key])) { - for (const index in object[key]) { - db.put('indexes' + prefix + '.' + key + '=' + object[key][index] + '|' + id, id); - } - return null; + const addIndex = (key, value) => { + return db.put('indexes' + prefix + '.' + key + '=' + value + '|' + id, id); + }; + + for (const [key, value] of Object.entries(object)) { + if (isObject(value)) { + promises.push(addToIndexes(id, value, prefix + '.' + key)); + } else if (Array.isArray(value)) { + value.forEach(item => promises.push(addIndex(key, item))); + } else { + promises.push(addIndex(key, value)); } + } + + return Promise.all(promises); + } + + async function removeIndexesForDocument(id, document, prefix = '') { + const parsedDocument = typeof document === 'string' ? JSON.parse(document) : document; + const promises = []; - db.put('indexes' + prefix + '.' + key + '=' + object[key] + '|' + id, id); - return null; - }); + const removeIndex = (key, value) => { + return db.del('indexes' + prefix + '.' + key + '=' + value + '|' + id).catch(() => {}); + }; + + for (const [key, value] of Object.entries(parsedDocument)) { + if (isObject(value)) { + promises.push(removeIndexesForDocument(id, value, prefix + '.' + key)); + } else if (Array.isArray(value)) { + value.forEach(item => promises.push(removeIndex(key, item))); + } else { + promises.push(removeIndex(key, value)); + } + } return Promise.all(promises); } @@ -87,12 +107,16 @@ async function createDoubleDb (dataDirectory) { throw new Error(`doubledb.replace: document with id ${id} does not exist`); } + await removeIndexesForDocument(id, existingDocument); + const puttableRecord = { ...newDocument, id }; await db.put(id, JSON.stringify(puttableRecord)); + await addToIndexes(id, puttableRecord); + return puttableRecord; } @@ -116,6 +140,8 @@ async function createDoubleDb (dataDirectory) { throw new Error(`doubledb.patch: document with id ${id} does not exist`); } + await removeIndexesForDocument(id, existingDocument); + const puttableRecord = { ...JSON.parse(existingDocument), ...newDocument, @@ -123,6 +149,8 @@ async function createDoubleDb (dataDirectory) { }; await db.put(id, JSON.stringify(puttableRecord)); + await addToIndexes(id, puttableRecord); + return puttableRecord; } @@ -266,6 +294,8 @@ async function createDoubleDb (dataDirectory) { throw new Error(`doubledb.remove: document with id ${id} does not exist`); } + await removeIndexesForDocument(id, existingDocument); + return db.del(id); } diff --git a/test/index.js b/test/index.js index f1f74d1..1a8f3d5 100644 --- a/test/index.js +++ b/test/index.js @@ -493,4 +493,118 @@ test('query - found', async t => { return () => db.close(); }); +// Index resets + +test('replace - updates indexes correctly', async t => { + t.plan(4); + + await fs.rm('./testData', { recursive: true }).catch(() => {}); + const db = await createDoubleDb('./testData'); + + // Insert initial document + const insertedRecord = await db.insert({ + firstName: 'John', + lastName: 'Doe', + age: 30 + }); + + // Replace the document + await db.replace(insertedRecord.id, { + firstName: 'Jane', + lastName: 'Smith', + occupation: 'Engineer' + }); + + // Check if old indexes are removed + const oldIndexes = await db.find('age', 30); + t.equal(oldIndexes, undefined, 'Old index should be removed'); + + // Check if new indexes are added + const newFirstNameIndex = await db.find('firstName', 'Jane'); + t.deepEqual(newFirstNameIndex, { id: insertedRecord.id, firstName: 'Jane', lastName: 'Smith', occupation: 'Engineer' }, 'New firstName index should exist'); + + const newLastNameIndex = await db.find('lastName', 'Smith'); + t.deepEqual(newLastNameIndex, { id: insertedRecord.id, firstName: 'Jane', lastName: 'Smith', occupation: 'Engineer' }, 'New lastName index should exist'); + + const newOccupationIndex = await db.find('occupation', 'Engineer'); + t.deepEqual(newOccupationIndex, { id: insertedRecord.id, firstName: 'Jane', lastName: 'Smith', occupation: 'Engineer' }, 'New occupation index should exist'); + + await db.close(); +}); + +test('patch - updates indexes correctly', async t => { + t.plan(5); + + await fs.rm('./testData', { recursive: true }).catch(() => {}); + const db = await createDoubleDb('./testData'); + + // Insert initial document + const insertedRecord = await db.insert({ + firstName: 'John', + lastName: 'Doe', + age: 30 + }); + + // Patch the document + await db.patch(insertedRecord.id, { + firstName: 'Jane', + age: 31, + occupation: 'Engineer' + }); + + // Check if updated indexes are correct + const updatedFirstNameIndex = await db.find('firstName', 'Jane'); + t.deepEqual(updatedFirstNameIndex, { id: insertedRecord.id, firstName: 'Jane', lastName: 'Doe', age: 31, occupation: 'Engineer' }, 'Updated firstName index should exist'); + + const updatedAgeIndex = await db.find('age', 31); + t.deepEqual(updatedAgeIndex, { id: insertedRecord.id, firstName: 'Jane', lastName: 'Doe', age: 31, occupation: 'Engineer' }, 'Updated age index should exist'); + + // Check if new index is added + const newOccupationIndex = await db.find('occupation', 'Engineer'); + t.deepEqual(newOccupationIndex, { id: insertedRecord.id, firstName: 'Jane', lastName: 'Doe', age: 31, occupation: 'Engineer' }, 'New occupation index should exist'); + + // Check if unchanged index still exists + const unchangedLastNameIndex = await db.find('lastName', 'Doe'); + t.deepEqual(unchangedLastNameIndex, { id: insertedRecord.id, firstName: 'Jane', lastName: 'Doe', age: 31, occupation: 'Engineer' }, 'Unchanged lastName index should still exist'); + + // Check if old index is removed + const oldAgeIndex = await db.find('age', 30); + t.equal(oldAgeIndex, undefined, 'Old age index should be removed'); + + await db.close(); +}); + +test('remove - cleans up indexes correctly', async t => { + t.plan(4); + + await fs.rm('./testData', { recursive: true }).catch(() => {}); + const db = await createDoubleDb('./testData'); + + // Insert initial document + const insertedRecord = await db.insert({ + firstName: 'John', + lastName: 'Doe', + age: 30 + }); + + // Remove the document + await db.remove(insertedRecord.id); + + // Check if all indexes are removed + const firstNameIndex = await db.find('firstName', 'John'); + t.equal(firstNameIndex, undefined, 'firstName index should be removed'); + + const lastNameIndex = await db.find('lastName', 'Doe'); + t.equal(lastNameIndex, undefined, 'lastName index should be removed'); + + const ageIndex = await db.find('age', 30); + t.equal(ageIndex, undefined, 'age index should be removed'); + + // Check if the document is actually removed + const removedDocument = await db.read(insertedRecord.id); + t.equal(removedDocument, undefined, 'Document should be removed'); + + await db.close(); +}); + test.trigger();