Comparing version 5.1.2 to 5.1.3
@@ -32,4 +32,2 @@ 'use strict'; | ||
Hoek.assert(changes && typeof changes === 'object', 'Invalid changes object'); | ||
const without = []; | ||
@@ -113,3 +111,5 @@ const wrapped = internals.wrap(changes, without, []); | ||
if (!Object.keys(ref).length) { | ||
if (!Object.keys(ref).length && | ||
!Array.isArray(ref)) { | ||
return undefined; | ||
@@ -116,0 +116,0 @@ } |
@@ -120,6 +120,7 @@ 'use strict'; | ||
Hoek.assert(changes && typeof changes === 'object', 'Invalid changes object'); | ||
const diag = { id, changes }; | ||
const wrapped = Modifier.wrap(changes); | ||
Unique.reserve(this, wrapped, id, (err, postUnique) => { | ||
Unique.reserve(this, changes, id, (err, postUnique) => { | ||
@@ -130,2 +131,3 @@ if (err) { | ||
const wrapped = Modifier.wrap(changes); | ||
const opts = { returnChanges: !!postUnique }; | ||
@@ -132,0 +134,0 @@ this._run(this._table.get(id)[typeof wrapped === 'object' ? 'update' : 'replace'](wrapped, opts), 'update', diag, callback, (ignore, result) => { |
@@ -5,2 +5,3 @@ 'use strict'; | ||
const Boom = require('boom'); | ||
const Hoek = require('hoek'); | ||
@@ -52,21 +53,64 @@ const Items = require('items'); | ||
const fields = []; | ||
const byHolder = (updateId ? [] : null); | ||
[].concat(items).forEach((item) => { | ||
// Find unique changes | ||
table._unique.rules.forEach((rule) => { | ||
const reserve = []; | ||
const release = []; | ||
items = [].concat(items); | ||
for (let i = 0; i < items.length; ++i) { | ||
const item = items[i]; | ||
for (let j = 0; j < table._unique.rules.length; ++j) { | ||
const rule = table._unique.rules[j]; | ||
const values = internals.reach(item, rule.path); | ||
if (values !== undefined) { | ||
fields.push({ rule, values, id: updateId !== null ? updateId : item.id }); | ||
if (values.isBoom) { | ||
return callback(values); | ||
} | ||
if (updateId) { | ||
byHolder.push(rule); | ||
if (values.length) { | ||
reserve.push({ rule, values, id: updateId !== null ? updateId : item.id }); | ||
} | ||
if (updateId && | ||
!values._bypass) { | ||
release.push(rule); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
// Prepare cleanup operation | ||
const cleanup = (!release.length ? null : (changes, finalize) => { | ||
const change = changes[0]; // Always includes one change | ||
const eachRule = (rule, next) => { | ||
let released = internals.reach(change.old_val, rule.path); | ||
if (!released) { | ||
return next(); | ||
} | ||
const taken = internals.reach(change.new_val, rule.path); | ||
if (taken) { | ||
released = released.filter((value) => taken.indexOf(value) === -1); | ||
} | ||
if (!released.length) { | ||
return next(); | ||
} | ||
return rule.table.remove(released, next); | ||
}; | ||
return Items.serial(release, eachRule, finalize); | ||
}); | ||
if (!fields.length) { | ||
return Hoek.nextTick(callback)(); | ||
// Reserve new values | ||
if (!reserve.length) { | ||
return Hoek.nextTick(callback)(null, cleanup); | ||
} | ||
@@ -82,33 +126,39 @@ | ||
const now = Date.now(); | ||
const reservations = []; | ||
field.values.forEach((value) => { | ||
// Try to get existing reservations | ||
const rsv = { id: value, created: now }; | ||
rsv[field.rule.key] = field.id; | ||
reservations.push(rsv); | ||
}); | ||
let values = field.values; | ||
field.rule.table.get(values, (err, existing) => { | ||
field.rule.table.insert(reservations, next); | ||
}; | ||
if (err) { | ||
return next(err); | ||
} | ||
return Items.serial(fields, each, (err) => { | ||
if (existing) { | ||
const existingIds = []; | ||
for (let i = 0; i < existing.length; ++i) { | ||
const item = existing[i]; | ||
if (item[field.rule.key] !== field.id) { | ||
return next(Boom.internal(`Action will violate unique restriction on ${item.id} in table ${field.rule.table.name}`)); | ||
} | ||
if (err) { | ||
return callback(err); | ||
} | ||
existingIds.push(item.id); | ||
} | ||
return callback(null, (changes, finalize) => { | ||
values = values.filter((value) => existingIds.indexOf(value) === -1); | ||
} | ||
const change = changes[0]; // Always includes one change | ||
const item = change.old_val; | ||
const eachRule = (rule, next) => { | ||
const reservations = []; | ||
const now = Date.now(); | ||
values.forEach((value) => { | ||
const oldValue = internals.reach(item, rule.path); | ||
return rule.table.remove(oldValue, next); | ||
}; | ||
const rsv = { id: value, created: now }; | ||
rsv[field.rule.key] = field.id; | ||
reservations.push(rsv); | ||
}); | ||
return Items.serial(byHolder, eachRule, finalize); | ||
field.rule.table.insert(reservations, next); | ||
}); | ||
}); | ||
}; | ||
Items.serial(reserve, each, (err) => callback(err, cleanup)); | ||
}); | ||
@@ -156,5 +206,41 @@ }; | ||
if (typeof ref === 'object') { | ||
return Object.keys(ref); | ||
const keys = Object.keys(ref); | ||
let unset = false; | ||
const taken = keys.filter((key) => { | ||
if (typeof ref[key] !== 'function') { | ||
return true; | ||
} | ||
if (ref[key].type !== 'unset') { | ||
return true; | ||
} | ||
unset = true; | ||
return false; | ||
}); | ||
return taken.length ? taken : (unset ? [] : undefined); | ||
} | ||
if (typeof ref === 'function') { | ||
if (ref.type === 'unset') { | ||
return []; | ||
} | ||
if (ref.type === 'append') { | ||
if (Array.isArray(ref.value) && | ||
ref.flags.single) { | ||
return Boom.internal('Cannot add an array as single value to unique index value'); | ||
} | ||
const result = Array.isArray(ref.value) ? ref.value : [ref.value]; | ||
result._bypass = true; | ||
return result; | ||
} | ||
return Boom.internal('Cannot increment unique index value'); // type: increment | ||
} | ||
return [ref]; | ||
@@ -161,0 +247,0 @@ }; |
{ | ||
"name": "penseur", | ||
"description": "Lightweight RethinkDB wrapper", | ||
"version": "5.1.2", | ||
"version": "5.1.3", | ||
"author": "Eran Hammer <eran@hammer.io> (http://hueniverse.com)", | ||
@@ -27,6 +27,6 @@ "repository": "git://github.com/hueniverse/penseur", | ||
"scripts": { | ||
"test": "lab -a code -t 100 -L -m 6000", | ||
"test-cov-html": "lab -a code -r html -o coverage.html -m 6000" | ||
"test": "lab -a code -t 100 -L -m 10000", | ||
"test-cov-html": "lab -a code -r html -o coverage.html -m 10000" | ||
}, | ||
"license": "BSD-3-Clause" | ||
} |
@@ -74,2 +74,22 @@ 'use strict'; | ||
it('returns the requested objects (array of one)', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
db.establish(['test'], (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert([{ id: 1, a: 1 }, { id: 2, a: 2 }, { id: 3, a: 1 }], (err, keys) => { | ||
expect(err).to.not.exist(); | ||
db.test.get([1], (err, result) => { | ||
expect(err).to.not.exist(); | ||
expect(result).to.deep.equal([{ id: 1, a: 1 }]); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('returns the requested objects found (partial)', (done) => { | ||
@@ -653,2 +673,20 @@ | ||
}); | ||
it('errors on key conflict', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
db.establish(['test'], (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: 1, a: 1 }, (err, key1) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: 1, a: 1 }, (err, key2) => { | ||
expect(err).to.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -655,0 +693,0 @@ |
@@ -70,3 +70,3 @@ 'use strict'; | ||
expect(err).to.not.exist(); | ||
db.test.insert([{ id: 1, a: { b: 1 } }, { id: 2, a: { c: { d: 2 } } }], (err, keys) => { | ||
db.test.insert([{ id: 1, a: { b: 1 } }, { id: 2, a: { c: { d: [2] } } }], (err, keys) => { | ||
@@ -79,3 +79,8 @@ expect(err).to.not.exist(); | ||
expect(err).to.exist(); | ||
done(); | ||
db.test.update(2, { a: { c: { d: db.append([1, 2]) } } }, (err) => { | ||
expect(err).to.not.exist(); | ||
done(); | ||
}); | ||
}); | ||
@@ -118,2 +123,118 @@ }); | ||
it('allows appending a unique value', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: '1', a: [1] }, (err, key) => { | ||
expect(err).to.not.exist(); | ||
db.test.update('1', { a: db.append(2) }, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.update('1', { a: db.append(1) }, (err) => { | ||
expect(err).to.not.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('releases value on unset', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: '1', a: 1 }, (err, key) => { | ||
expect(err).to.not.exist(); | ||
db.test.update('1', { a: db.unset() }, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: 2, a: 1 }, (err) => { | ||
expect(err).to.not.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('ignores empty object', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: '1', a: {} }, (err, key) => { | ||
expect(err).to.not.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('allows adding a unique value via update', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: '1' }, (err, key) => { | ||
expect(err).to.not.exist(); | ||
db.test.update('1', { a: 2 }, (err) => { | ||
expect(err).to.not.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('forbids violating a unique value', (done) => { | ||
@@ -159,14 +280,82 @@ | ||
expect(err).to.not.exist(); | ||
db.test.insert({ a: { b: 1 } }, (err, key1) => { | ||
db.test.insert({ a: { b: [1] } }, (err, key1) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ a: { c: 2, d: 4 } }, (err, key2) => { | ||
db.test.update(key1, { a: { b: db.append(2) } }, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ a: { b: 3 } }, (err, key3) => { | ||
db.test.insert({ a: { c: 2, d: 4 } }, (err, key2) => { | ||
expect(err).to.exist(); | ||
db.test.insert({ a: { d: 5 } }, (err, key4) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ a: { b: 3 } }, (err, key3) => { | ||
expect(err).to.exist(); | ||
db.test.insert({ a: { d: 5 } }, (err, key4) => { | ||
expect(err).to.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('allows same owner changes', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: 1, a: { c: 1 } }, (err, key1) => { | ||
expect(err).to.not.exist(); | ||
db.test.update(1, { a: { c: 5 } }, (err) => { | ||
expect(err).to.not.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('releases reservations on update (keys)', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: 1, a: { b: 1 } }, (err, key1) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ id: 2, a: { c: 2, d: 4 } }, (err, key2) => { | ||
expect(err).to.not.exist(); | ||
db.test.update(2, { a: { c: db.unset() } }, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.update(1, { a: { c: 5 } }, (err) => { | ||
expect(err).to.not.exist(); | ||
done(); | ||
@@ -208,3 +397,11 @@ }); | ||
expect(err).to.exist(); | ||
done(); | ||
db.test.update(key2, { a: [] }, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ a: ['a'] }, (err, key5) => { | ||
expect(err).to.not.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -217,3 +414,3 @@ }); | ||
it('cusomizes unique table name', (done) => { | ||
it('customizes unique table name', (done) => { | ||
@@ -322,2 +519,83 @@ const db = new Penseur.Db('penseurtest'); | ||
}); | ||
it('errors on incrementing unique index', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ a: 1 }, (err, key) => { | ||
expect(err).to.not.exist(); | ||
db.test.update(key, { a: db.increment(1) }, (err) => { | ||
expect(err).to.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('errors on appending a single array to a unique index', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test.insert({ a: [1] }, (err, key) => { | ||
expect(err).to.not.exist(); | ||
db.test.update(key, { a: db.append([2], { single: true }) }, (err) => { | ||
expect(err).to.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('errors on database unique table get error', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const settings = { | ||
penseur_unique_test_a: true, // Test cleanup | ||
test: { | ||
id: 'uuid', | ||
unique: { | ||
path: 'a' | ||
} | ||
} | ||
}; | ||
db.establish(settings, (err) => { | ||
expect(err).to.not.exist(); | ||
db.test._unique.rules[0].table.get = (id, callback) => callback(new Error('boom')); | ||
db.test.insert([{ a: 1 }, { a: 2 }], (err, keys) => { | ||
expect(err).to.exist(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -324,0 +602,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
169231
4056