keystone
Version:
Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose
107 lines (92 loc) • 3.96 kB
JavaScript
var debug = require('debug')('keystone:core:list:ensureTextIndex');
// A basic string hashing function
function hashString (string) {
var char;
var hash = 0;
if (string.length === 0) return hash;
for (var i = 0; i < string.length; i++) {
char = string.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
/**
* Does what it can to ensure the collection has an appropriate text index.
*
* Works around unreliable behaviour with the Mongo drivers ensureIndex()
* Specifically, when the following are true..
* - Relying on collection.ensureIndexes() to create text indexes
* - A text index already exists on the collection
* - The existing index has a different definition but the same name
* The index is not created/updated and no error is returned, either by the
* ensureIndexes() call or the connection error listener.
* Or at least that's what was happening for me (mongoose v3.8.40, mongodb v1.4.38)..
*/
function ensureTextIndex (callback) {
var list = this;
var collection = list.model.collection;
var textIndex = list.buildSearchTextIndex();
var fieldsHash = Math.abs(hashString(Object.keys(textIndex).sort().join(';')));
var indexNamePrefix = 'keystone_searchFields_textIndex_';
var newIndexName = indexNamePrefix + fieldsHash;
// We use this later to create a new index if needed
var createNewIndex = function () {
collection.createIndex(textIndex, { name: newIndexName }, function (result) {
debug('collection.createIndex() result for \'' + list.key + '\:', result);
return callback();
});
};
collection.getIndexes(function (err, indexes) {
if (err) {
if (err.code === 26) {
// if the database doesn't exist, we'll get error code 26 "no database" here
// that's fine, we just default the indexes object so the new text index
// gets created when the database is connected.
indexes = {};
} else {
// otherwise, we've had an unexpected error, so we throw it
throw err;
}
}
var indexNames = Object.keys(indexes);
// Search though the
for (var i = 0; i < indexNames.length; i++) {
var existingIndexName = indexNames[i];
var isText = false;
// Check we're dealing with a text index
for (var h = 0; h < indexes[existingIndexName].length; h++) {
var column = indexes[existingIndexName][h];
if (column[1] === 'text') isText = isText || true;
}
// Skip non-text indexes
if (!isText) continue;
// Already exists with correct def
if (existingIndexName === newIndexName) {
debug('Existing text index \'' + existingIndexName + '\' already matches the searchFields for \'' + list.key + '\'');
return;
}
// Exists but hash (def) doesn't match
// Check for 'searchFields_text_index' for backwards compatibility
if (existingIndexName.slice(0, indexNamePrefix.length) === indexNamePrefix || existingIndexName === 'searchFields_text_index') {
debug('Existing text index \'' + existingIndexName + '\' doesn\'t match the searchFields for \'' + list.key + '\' and will be recreated as \'' + newIndexName + '\'');
collection.dropIndex(existingIndexName, function (result) {
debug('collection.dropIndex() result for \'' + list.key + '\:', result);
createNewIndex();
});
return;
}
// It's a text index but not one of ours; nothing we can do
console.error(''
+ 'list.ensureTextIndex() failed to update the existing text index \'' + existingIndexName + '\' for the \'' + list.key + '\' list.\n'
+ 'The existing index wasn\'t automatically created by ensureTextIndex() so will not be replaced.\n'
+ 'This may lead to unexpected behaviour when performing text searches on the this list.'
);
return;
}
// No text indexes found at all; create ours now
debug('No existing text index found in \'' + list.key + '\'; Creating ours now');
createNewIndex();
});
}
module.exports = ensureTextIndex;