Comparing version 2.2.0 to 2.3.0
277
lib/db.js
@@ -18,98 +18,150 @@ 'use strict'; | ||
exports = module.exports = internals.Db = function (name, options) { | ||
exports = module.exports = class { | ||
options = options || {}; | ||
Hoek.assert(!options.db, 'Cannot set db option'); | ||
constructor(name, options) { | ||
this._settings = Hoek.clone(options); | ||
this._name = name; | ||
this._connection = null; | ||
options = options || {}; | ||
Hoek.assert(!options.db, 'Cannot set db option'); | ||
if (this._settings.test) { | ||
this.disable = internals.disable; | ||
this.enable = internals.enable; | ||
this._settings = Hoek.clone(options); | ||
this._name = name; | ||
this._connection = null; | ||
if (this._settings.test) { | ||
this.disable = internals.disable; | ||
this.enable = internals.enable; | ||
} | ||
delete this._settings.test; // Always delete in case value is falsy | ||
this.tables = {}; | ||
} | ||
delete this._settings.test; // Always delete in case value is falsy | ||
connect(callback) { | ||
this.tables = {}; | ||
}; | ||
RethinkDB.connect(this._settings, (err, connection) => { | ||
if (err) { | ||
return callback(err); | ||
} | ||
internals.Db.prototype.connect = function (callback) { | ||
this._connection = connection; | ||
return callback(null); | ||
}); | ||
} | ||
RethinkDB.connect(this._settings, (err, connection) => { | ||
close(next) { | ||
if (err) { | ||
return callback(err); | ||
next = next || Hoek.ignore; | ||
if (!this._connection) { | ||
return next(); | ||
} | ||
this._connection = connection; | ||
return callback(null); | ||
}); | ||
}; | ||
// Close change stream cursors | ||
const tables = Object.keys(this.tables); | ||
for (let i = 0; i < tables.length; ++i) { | ||
const table = this.tables[tables[i]]; | ||
for (let j = 0; j < table._cursors.length; ++j) { | ||
table._cursors[j].close(); | ||
} | ||
internals.Db.prototype.close = function (next) { | ||
table._cursors = []; | ||
} | ||
next = next || Hoek.ignore; | ||
// Close connection | ||
if (!this._connection) { | ||
return next(); | ||
this._connection.close((err) => { // Explicit callback to avoid generating a promise | ||
return next(err); | ||
}); | ||
} | ||
// Close change stream cursors | ||
table(tables, options) { | ||
const tables = Object.keys(this.tables); | ||
for (let i = 0; i < tables.length; ++i) { | ||
const table = this.tables[tables[i]]; | ||
for (let j = 0; j < table._cursors.length; ++j) { | ||
table._cursors[j].close(); | ||
} | ||
options = options || {}; | ||
table._cursors = []; | ||
} | ||
const Proto = options.extended || this._settings.extended || Table; | ||
// Close connection | ||
tables = [].concat(tables); | ||
this._connection.close((err) => { // Explicit callback to avoid generating a promise | ||
for (let i = 0; i < tables.length; ++i) { | ||
const table = tables[i]; | ||
if (this.tables[table]) { | ||
return; | ||
} | ||
return next(err); | ||
}); | ||
}; | ||
const record = new Proto(table, this); | ||
// Decorate object with tables | ||
internals.Db.prototype.table = function (tables) { | ||
this.tables[table] = record; | ||
if (!this[table] && | ||
table[0] !== '_') { // Do not override prototype or private members | ||
const Proto = this._settings.extended || Table; | ||
this[table] = record; | ||
} | ||
} | ||
} | ||
tables = [].concat(tables); | ||
establish(tables, callback) { | ||
for (let i = 0; i < tables.length; ++i) { | ||
const table = tables[i]; | ||
if (this.tables[table]) { | ||
// Connect if not connected already | ||
if (!this._connection) { | ||
this.connect((err) => { | ||
if (err) { | ||
return callback(err); | ||
} | ||
return this.establish(tables, callback); | ||
}); | ||
return; | ||
} | ||
const record = new Proto(table, this); | ||
RethinkDB.dbList().run(this._connection, (err, names) => { | ||
// Decorate object with tables | ||
if (err) { | ||
return callback(err); | ||
} | ||
this.tables[table] = record; | ||
if (!this[table] && | ||
table[0] !== '_') { // Do not override prototype or private members | ||
if (names.indexOf(this._name) === -1) { | ||
this[table] = record; | ||
} | ||
// Create new database | ||
RethinkDB.dbCreate(this._name).run(this._connection, (err, created) => { | ||
if (err) { | ||
return callback(err); | ||
} | ||
return this._createTable(tables, callback); | ||
}); | ||
} | ||
else { | ||
// Reuse existing | ||
return this._createTable(tables, callback); | ||
} | ||
}); | ||
} | ||
}; | ||
_createTable(tables, callback) { | ||
internals.Db.prototype.establish = function (tables, callback) { | ||
let names = tables; | ||
const tablesOptions = {}; | ||
if (!Array.isArray(tables)) { | ||
names = Object.keys(tables); | ||
for (let i = 0; i < names.length; ++i) { | ||
const name = names[i]; | ||
tablesOptions[name] = (!tables[name] ? false : (tables[name] === true ? {} : tables[name])); | ||
} | ||
} | ||
// Connect if not connected already | ||
RethinkDB.db(this._name).tableList().run(this._connection, (err, existing) => { | ||
if (!this._connection) { | ||
this.connect((err) => { | ||
if (err) { | ||
@@ -119,71 +171,92 @@ return callback(err); | ||
return this.establish(tables, callback); | ||
}); | ||
const each = (name, next) => { | ||
return; | ||
} | ||
if (tablesOptions[name] === false) { | ||
return next(); | ||
} | ||
RethinkDB.dbList().run(this._connection, (err, names) => { | ||
const options = tablesOptions[name] || {}; | ||
this.table(name, options); | ||
if (err) { | ||
return callback(err); | ||
} | ||
const finalize = (err, indexes) => { | ||
if (names.indexOf(this._name) === -1) { | ||
if (err) { | ||
return next(err); | ||
} | ||
// Create new database | ||
if (indexes) { | ||
return this.tables[name].index(indexes, next); | ||
} | ||
RethinkDB.dbCreate(this._name).run(this._connection, (err, created) => { | ||
return next(); | ||
}; | ||
if (err) { | ||
return callback(err); | ||
// Create new table | ||
if (existing.indexOf(name) === -1) { | ||
return RethinkDB.db(this._name).tableCreate(name).run(this._connection, (err) => finalize(err, options.index)); | ||
} | ||
return this._createTable(tables, callback); | ||
}); | ||
} | ||
else { | ||
// Reuse existing table | ||
// Reuse existing | ||
const recreate = () => { | ||
return this._createTable(tables, callback); | ||
} | ||
}); | ||
}; | ||
RethinkDB.db(this._name).table(name).indexList().run(this._connection, (err, currentIndexes) => { | ||
if (err) { | ||
return next(err); | ||
} | ||
internals.Db.prototype._createTable = function (tables, callback) { | ||
const requestedIndexes = [].concat(options.index || []); | ||
const intersection = Hoek.intersect(currentIndexes, requestedIndexes); | ||
const creatIndexes = internals.difference(requestedIndexes, intersection); | ||
const removeIndesex = internals.difference(currentIndexes, intersection); | ||
RethinkDB.db(this._name).tableList().run(this._connection, (err, names) => { | ||
const eachIndex = (index, nextIndex) => { | ||
if (err) { | ||
return callback(err); | ||
} | ||
RethinkDB.db(this._name).table(name).indexDrop(index).run(this._connection, nextIndex); | ||
}; | ||
const each = (table, next) => { | ||
Items.serial(removeIndesex, eachIndex, (err) => finalize(err, creatIndexes)); | ||
}); | ||
}; | ||
if (names.indexOf(table) === -1) { | ||
if (options.purge === false) { // Defaults to true | ||
return recreate(); | ||
} | ||
// Create new table | ||
this.tables[name].empty((err) => { | ||
RethinkDB.db(this._name).tableCreate(table).run(this._connection, (err, result) => { | ||
if (err) { | ||
return next(err); | ||
} | ||
this.table(table); | ||
return next(err); | ||
return recreate(); | ||
}); | ||
} | ||
else { | ||
}; | ||
// Empty existing table | ||
Items.serial(names, each, callback); | ||
}); | ||
} | ||
this.table(table); | ||
return this.tables[table].empty(next); | ||
} | ||
}; | ||
fields(criteria) { | ||
Items.serial(tables, each, callback); | ||
}); | ||
return new Criteria(criteria, 'fields'); | ||
} | ||
}; | ||
internals.difference = function (superset, subset) { | ||
const result = []; | ||
for (let i = 0; i < superset.length; ++i) { | ||
if (subset.indexOf(superset[i]) === -1) { | ||
result.push(superset[i]); | ||
} | ||
} | ||
return result; | ||
}; | ||
internals.disable = function (table, method) { | ||
@@ -212,7 +285,1 @@ | ||
}; | ||
internals.Db.prototype.fields = function (criteria) { | ||
return new Criteria(criteria, 'fields'); | ||
}; |
@@ -6,2 +6,3 @@ 'use strict'; | ||
const Boom = require('boom'); | ||
const Items = require('items'); | ||
const RethinkDB = require('rethinkdb'); | ||
@@ -57,3 +58,3 @@ const Cursor = require('./cursor'); | ||
}); | ||
}; | ||
} | ||
@@ -63,3 +64,3 @@ count(criteria, callback) { | ||
this._run(Criteria.wrap(criteria).select(this._table).count(), 'count', { criteria: criteria }, callback); | ||
}; | ||
} | ||
@@ -98,3 +99,3 @@ insert(items, callback) { | ||
}); | ||
}; | ||
} | ||
@@ -113,3 +114,3 @@ update(id, changes, callback) { | ||
}); | ||
}; | ||
} | ||
@@ -129,3 +130,3 @@ increment(id, field, value, callback) { | ||
}); | ||
}; | ||
} | ||
@@ -144,3 +145,3 @@ append(id, field, value, callback) { | ||
}); | ||
}; | ||
} | ||
@@ -161,3 +162,3 @@ unset(id, fields, callback) { | ||
}); | ||
}; | ||
} | ||
@@ -181,9 +182,12 @@ remove(criteria, callback) { | ||
}); | ||
}; | ||
} | ||
empty(callback) { | ||
this._run(this._table.delete(), 'empty', null, callback); | ||
}; | ||
this._run(this._table.delete(), 'empty', null, (err, result) => { | ||
return callback(err, result ? result.deleted : 0); | ||
}); | ||
} | ||
sync(callback) { | ||
@@ -199,4 +203,18 @@ | ||
}); | ||
}; | ||
} | ||
index(names, callback) { | ||
names = [].concat(names); | ||
const each = (name, next) => { | ||
this._run(this._table.indexCreate(name), 'index', null, (err, result) => { | ||
return next(err); | ||
}); | ||
}; | ||
Items.serial(names, each, callback); | ||
} | ||
changes(criteria, each, callback) { | ||
@@ -246,3 +264,3 @@ | ||
}); | ||
}; | ||
} | ||
@@ -280,3 +298,3 @@ _run(request, action, inputs, callback, next) { | ||
}); | ||
}; | ||
} | ||
@@ -286,3 +304,3 @@ _error(action, err, inputs, callback) { | ||
return callback(Boom.internal('Database error', { error: err, table: this._name, action: action, inputs: inputs })); | ||
}; | ||
} | ||
}; |
{ | ||
"name": "penseur", | ||
"description": "Lightweight RethinkDB wrapper", | ||
"version": "2.2.0", | ||
"version": "2.3.0", | ||
"author": "Eran Hammer <eran@hammer.io> (http://hueniverse.com)", | ||
@@ -6,0 +6,0 @@ "repository": "git://github.com/hueniverse/penseur", |
250
test/db.js
@@ -162,5 +162,46 @@ 'use strict'; | ||
db.establish(['test'], (err) => { | ||
db.establish({ test: { index: 'other' } }, (err) => { | ||
expect(err).to.not.exist(); | ||
RethinkDB.db(db._name).table('test').indexList().run(db._connection, (err, result) => { | ||
expect(result).to.deep.equal(['other']); | ||
db.close(done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('customize table options', (done) => { | ||
const Override = class extends Penseur.Table { | ||
insert(items, callback) { | ||
items = [].concat(items); | ||
for (let i = 0; i < items.length; ++i) { | ||
items[i].flag = true; | ||
} | ||
return super.insert(items, callback); | ||
} | ||
}; | ||
const db = new Penseur.Db('penseurtest', { host: 'localhost', port: 28015 }); | ||
db.establish({ test: { extended: Override }, user: false, other: true }, (err) => { | ||
expect(err).to.not.exist(); | ||
expect(db.user).to.not.exist(); | ||
expect(db.other).to.exist(); | ||
db.test.insert({ id: 1, value: 'x' }, (err, result) => { | ||
expect(err).to.not.exist(); | ||
db.test.get(1, (err, item) => { | ||
expect(err).to.not.exist(); | ||
expect(item.value).to.equal('x'); | ||
expect(item.flag).to.equal(true); | ||
db.close(done); | ||
@@ -172,2 +213,111 @@ }); | ||
it('creates database with different table indexes', (done) => { | ||
const db1 = new Penseur.Db('penseurtest'); | ||
db1.connect((err) => { | ||
expect(err).to.not.exist(); | ||
db1.establish({ test: { index: 'other' } }, (err) => { | ||
expect(err).to.not.exist(); | ||
RethinkDB.db(db1._name).table('test').indexList().run(db1._connection, (err, result1) => { | ||
expect(result1).to.deep.equal(['other']); | ||
db1.close(() => { | ||
const db2 = new Penseur.Db('penseurtest'); | ||
db2.connect((err) => { | ||
expect(err).to.not.exist(); | ||
db2.establish(['test'], (err) => { | ||
expect(err).to.not.exist(); | ||
RethinkDB.db(db2._name).table('test').indexList().run(db2._connection, (err, result2) => { | ||
expect(result2).to.deep.equal([]); | ||
db2.close(done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('creates database with different table indexes (partial overlap)', (done) => { | ||
const db1 = new Penseur.Db('penseurtest'); | ||
db1.connect((err) => { | ||
expect(err).to.not.exist(); | ||
db1.establish({ test: { index: ['a', 'b'] } }, (err) => { | ||
expect(err).to.not.exist(); | ||
RethinkDB.db(db1._name).table('test').indexList().run(db1._connection, (err, result1) => { | ||
expect(result1).to.deep.equal(['a', 'b']); | ||
db1.close(() => { | ||
const db2 = new Penseur.Db('penseurtest'); | ||
db2.connect((err) => { | ||
expect(err).to.not.exist(); | ||
db2.establish({ test: { index: ['b', 'c'] } }, (err) => { | ||
expect(err).to.not.exist(); | ||
RethinkDB.db(db2._name).table('test').indexList().run(db2._connection, (err, result2) => { | ||
expect(result2).to.deep.equal(['b', 'c']); | ||
db2.close(done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('retains records in existing table', (done) => { | ||
const db1 = new Penseur.Db('penseurtest'); | ||
db1.connect((err) => { | ||
expect(err).to.not.exist(); | ||
db1.establish(['test'], (err) => { | ||
expect(err).to.not.exist(); | ||
db1.test.insert({ id: 1 }, (err, id) => { | ||
expect(err).to.not.exist(); | ||
db1.close(() => { | ||
const db2 = new Penseur.Db('penseurtest'); | ||
db2.connect((err) => { | ||
expect(err).to.not.exist(); | ||
db2.establish({ test: { purge: false } }, (err) => { | ||
expect(err).to.not.exist(); | ||
db2.test.get(1, (err, item) => { | ||
expect(err).to.not.exist(); | ||
expect(item.id).to.equal(1); | ||
db2.close(done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('fails creating a database', (done) => { | ||
@@ -195,2 +345,25 @@ | ||
it('errors on database indexList() error', { parallel: false }, (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const orig = RethinkDB.dbList; | ||
RethinkDB.dbList = function () { | ||
RethinkDB.dbList = orig; | ||
return { | ||
run: function (connection, next) { | ||
return next(new Error('Bad database')); | ||
} | ||
}; | ||
}; | ||
db.establish(['test'], (err) => { | ||
expect(err).to.exist(); | ||
db.close(done); | ||
}); | ||
}); | ||
it('errors on database dbList() error', { parallel: false }, (done) => { | ||
@@ -249,2 +422,77 @@ | ||
}); | ||
it('errors on database indexList() error', { parallel: false }, (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
const orig = RethinkDB.db; | ||
let count = 0; | ||
RethinkDB.db = function () { | ||
return { | ||
tableList: function () { | ||
return { | ||
run: function (connection, next) { | ||
return next(null, ['test']); | ||
} | ||
}; | ||
}, | ||
table: function () { | ||
if (++count === 1) { | ||
return orig('penseurtest').table('test'); | ||
} | ||
RethinkDB.db = orig; | ||
return { | ||
indexList: function () { | ||
return { | ||
run: function (connection, next) { | ||
return next(new Error('Bad database')); | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
}; | ||
}; | ||
db.establish(['test'], (err) => { | ||
expect(err).to.exist(); | ||
db.close(done); | ||
}); | ||
}); | ||
it('errors creating new table', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
db.establish(['bad name'], (err) => { | ||
expect(err).to.exist(); | ||
db.close(done); | ||
}); | ||
}); | ||
it('errors emptying existing table', (done) => { | ||
const Override = class extends Penseur.Table { | ||
empty(callback) { | ||
return callback(new Error('failed')); | ||
} | ||
}; | ||
const db = new Penseur.Db('penseurtest', { host: 'localhost', port: 28015 }); | ||
db.establish({ test: { extended: Override } }, (err) => { | ||
expect(err).to.exist(); | ||
db.close(done); | ||
}); | ||
}); | ||
}); | ||
@@ -251,0 +499,0 @@ |
@@ -630,2 +630,46 @@ 'use strict'; | ||
describe('empty()', () => { | ||
it('removes all records', (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: 1 }], (err, keys) => { | ||
expect(err).to.not.exist(); | ||
db.test.empty((err, count1) => { | ||
expect(err).to.not.exist(); | ||
expect(count1).to.equal(2); | ||
db.test.count({ a: 1 }, (err, count2) => { | ||
expect(err).to.not.exist(); | ||
expect(count2).to.equal(0); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('errors on unknown table', (done) => { | ||
const db = new Penseur.Db('penseurtest'); | ||
db.connect((err) => { | ||
db.table('no_such_table_test'); | ||
db.no_such_table_test.empty((err, count) => { | ||
expect(err).to.exist(); | ||
expect(count).to.equal(0); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('_run()', () => { | ||
@@ -632,0 +676,0 @@ |
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
65216
1501