Skip to content

Commit

Permalink
feat(design docs): split views in several design documents
Browse files Browse the repository at this point in the history
  • Loading branch information
stropitek committed Sep 20, 2016
1 parent 01f2d36 commit c4f822d
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 22 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test/homedir/**
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@
"koa-session": "^3.1.0",
"ldapjs": "^1.0.0",
"lodash": "^4.9.0",
"md5": "^2.2.1",
"minimist": "^1.2.0",
"nano": "^6.1.5",
"nunjucks": "^2.3.0",
"object-hash": "^1.1.4",
"passport-facebook": "^2.0.0",
"passport-github": "^1.0.0",
"passport-google": "^0.3.0",
Expand Down
13 changes: 13 additions & 0 deletions src/config/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,25 @@ if (homeDir) {
let databaseConfig = {};
try {
databaseConfig = require(path.join(databasePath, 'config'));
var designDocNames = {};
databaseConfig.designDocNames = [];
if (databaseConfig.customDesign && databaseConfig.customDesign.views) {
var views = databaseConfig.customDesign.views;
for (var key in views) {
if (views[key].designDoc) {
designDocNames[key] = views[key].designDoc;
}
}
}
databaseConfig.designDocNames = designDocNames;
} catch (e) {
// database config is not mandatory
}
if (!databaseConfig.import) {
databaseConfig.import = {};
}

databaseConfig.designDocNames = databaseConfig.designDocNames || {};
readImportConfig(databasePath, databaseConfig);
dbConfig[database] = databaseConfig;
}
Expand Down
36 changes: 22 additions & 14 deletions src/design/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,29 @@ const mapTpl = function (doc) {
customMap(doc);
}.toString();

module.exports = function getDesignDoc(custom) {
module.exports = function (custom) {
custom = custom || {};

processViews(custom);

if (custom.designDoc === 'app') {
return {
_id: constants.DESIGN_DOC_ID,
language: 'javascript',
version: constants.DESIGN_DOC_VERSION,
customVersion: custom.version,
filters: Object.assign({}, custom.filters, filters),
updates: Object.assign({}, custom.updates, updates),
views: Object.assign({}, custom.views, views),
validate_doc_update: validateDocUpdate,
lists: Object.assign({}, custom.lists)
};
} else {
return custom;
}
};

function processViews(custom) {
if (custom.views) {
for (const viewName in custom.views) {
const view = custom.views[viewName];
Expand All @@ -45,16 +65,4 @@ module.exports = function getDesignDoc(custom) {
}
}
}

return {
_id: constants.DESIGN_DOC_ID,
language: 'javascript',
version: constants.DESIGN_DOC_VERSION,
customVersion: custom.version,
filters: Object.assign({}, custom.filters, filters),
updates: Object.assign({}, custom.updates, updates),
views: Object.assign({}, custom.views, views),
validate_doc_update: validateDocUpdate,
lists: Object.assign({}, custom.lists)
};
};
}
91 changes: 85 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const debug = require('./util/debug')('main');
const _ = require('lodash');
const extend = require('extend');
const nano = require('nano');
const objHash = require('object-hash');

const CouchError = require('./util/CouchError');
const constants = require('./constants');
Expand Down Expand Up @@ -904,24 +905,102 @@ async function checkSecurity(db, admin) {
}

async function checkDesignDoc(db, custom) {
debug.trace('check design doc');
var toUpdate = new Set();
debug.trace('check _design/app design doc');
const doc = await nanoPromise.getDocument(db, constants.DESIGN_DOC_ID);
if (doc === null) {
toUpdate.add(constants.DESIGN_DOC_NAME);
debug.trace('design doc missing');
return await createDesignDoc(db, null, custom);
}
if (
} else if (
(!doc.version || doc.version < constants.DESIGN_DOC_VERSION) ||
(custom && typeof custom.version === 'number' && (!doc.customVersion || doc.customVersion < custom.version))
) {
debug.trace('design doc needs update');
return await createDesignDoc(db, doc._rev, custom);
toUpdate.add(constants.DESIGN_DOC_NAME);
}

debug.trace('check other custom design docs');
var viewNames = Object.keys(custom.views);
if (viewNames.indexOf(constants.DESIGN_DOC_NAME) > -1) {
let idx = viewNames.indexOf(constants.DESIGN_DOC_NAME);
viewNames.splice(idx, 1);
}
var designNames = viewNames.map(vn => custom.views[vn].designDoc);
var uniqDesignNames = _.uniq(designNames);
uniqDesignNames = uniqDesignNames.filter(d => d && d !== constants.DESIGN_DOC_NAME);
var designDocs = await Promise.all(uniqDesignNames.map(name => nanoPromise.getDocument(db, `_design/${name}`)));
uniqDesignNames.push(constants.DESIGN_DOC_NAME);
designDocs.push(doc);

for (var i = 0; i < viewNames.length; i++) {
let view = custom.views[viewNames[i]];
var hash = objHash(view);
var dbView = getDBView(viewNames[i]);
if (!dbView) {
if (view.designDoc) {
debug.trace(`design doc ${view.designDoc} not found, will create it`);
toUpdate.add(view.designDoc);
}
} else {

if (dbView.hash !== hash) {
if (view.designDoc) {
debug.trace(`design doc ${view.designDoc} changed, will update it`);
toUpdate.add(view.designDoc);
}
}
}
view.hash = hash;
}

debug.trace(`Update ${toUpdate.size} design documents`);
for (var designName of toUpdate.keys()) {
var idx = uniqDesignNames.indexOf(designName);
if (idx > -1 || designName === constants.DESIGN_DOC_NAME) {
await createDesignDoc(db, designDocs[idx] && designDocs[idx]._rev || null, getNewDesignDoc(designName));
} else {
debug.trace('Expected to be unreachable');
}
}

function getDBView(viewName) {
for (var i = 0; i < designDocs.length; i++) {
if (designDocs[i] && designDocs[i].views && designDocs[i].views[viewName]) {
return designDocs[i].views[viewName];
}
}
return null;
}

function getNewDesignDoc(designName) {
if (designName === constants.DESIGN_DOC_NAME) {
var designDoc = Object.assign({}, custom);
} else {
designDoc = {};
}
designDoc.views = {};
designDoc.designDoc = designName;
for (var i = 0; i < viewNames.length; i++) {
var viewName = viewNames[i];
if (custom.views[viewName].designDoc === designName) {
designDoc.views[viewName] = custom.views[viewName];
} else if (!custom.views[viewName].designDoc && designName === constants.DESIGN_DOC_NAME) {
designDoc.views[viewName] = custom.views[viewName];
designDoc.version = custom.version;
designDoc.updat;
}
}
designDoc._id = '_design/' + designName;
return designDoc;
}

// For each design doc, check if any of the associated view has a different hash

}

async function createDesignDoc(db, revID, custom) {
debug.trace('create design doc');
const designDoc = getDesignDoc(custom);
var designDoc = getDesignDoc(custom);
if (revID) {
designDoc._rev = revID;
}
Expand Down
5 changes: 4 additions & 1 deletion src/util/nanoPromise.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const constants = require('../constants');
const debug = require('./debug')('nano');
const hasOwnProperty = Object.prototype.hasOwnProperty;
const getConfig = require('../config/config').getConfig;

exports.authenticate = function (nano, user, password) {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -90,7 +91,9 @@ exports.queryView = function (db, view, params = {}, options = {}) {
return new Promise((resolve, reject) => {
debug.trace(`queryView ${view}`);
cleanOptions(params);
db.view(constants.DESIGN_DOC_NAME, view, params, function (err, body) {
var config = getConfig(db.config.db);
var designDoc = config.designDocNames && config.designDocNames[view] || constants.DESIGN_DOC_NAME;
db.view(designDoc, view, params, function (err, body) {
if (err) return reject(err);
if (options.onlyValue) {
resolve(body.rows.map(row => row.value));
Expand Down
30 changes: 30 additions & 0 deletions test/basic.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict';

const Couch = require('..');
const nanoPromise = require('../src/util/nanoPromise');
const assert = require('assert');

process.on('unhandledRejection', function (err) {
throw err;
Expand All @@ -21,3 +23,31 @@ describe('basic initialization tests', function () {
}).should.be.rejectedWith('database option is mandatory');
});
});

describe('basic initialization with custom design docs', function () {
let couch;
it('should load the design doc files at initialization', function () {
couch = new Couch({database: 'test3'});
return couch._initPromise.then(function () {
var app = nanoPromise.getDocument(couch._db, '_design/app')
.then(app => {
assert.notEqual(app, null);
assert.ok(app.views.test);
assert.ok(app.filters.abc);
});
var custom = nanoPromise.getDocument(couch._db, '_design/custom')
.then(custom => {
assert.notEqual(custom, null);
assert.ok(custom.views.testCustom);
});

return Promise.all([app, custom]);
});
});

it('should query a custom design document', function () {
return couch.queryEntriesByUser('a@a.com', 'testCustom').then(data => {
data.should.have.length(0);
});
});
});
1 change: 0 additions & 1 deletion test/homedir/test-import-db/config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

module.exports = {
database: 'jdx',
defaultEntry: {
molecule: function () {
return {
Expand Down
26 changes: 26 additions & 0 deletions test/homedir/test3/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

module.exports = {
customDesign: {
version: 6,
views: {
test: {
map: function (doc) {
emit(doc._id);
}
},
testCustom: {
map: function (doc) {
emit(doc._id);
},
designDoc: 'custom'
}
},
updates: {},
filters: {
abc: function (doc) {
return doc.$type === 'log';
}
}
}
};

0 comments on commit c4f822d

Please sign in to comment.