2012-01-30 19:37:14 +00:00
|
|
|
exports.safeRequire = safeRequire;
|
2013-07-17 00:53:52 +00:00
|
|
|
exports.fieldsToArray = fieldsToArray;
|
|
|
|
exports.selectFields = selectFields;
|
2013-10-11 18:50:00 +00:00
|
|
|
exports.removeUndefined = removeUndefined;
|
2013-10-25 23:18:02 +00:00
|
|
|
exports.parseSettings = parseSettings;
|
2013-12-06 23:52:39 +00:00
|
|
|
exports.mergeSettings = mergeSettings;
|
2014-01-28 17:57:23 +00:00
|
|
|
exports.isPlainObject = isPlainObject;
|
|
|
|
exports.defineCachedRelations = defineCachedRelations;
|
2014-08-15 17:39:18 +00:00
|
|
|
exports.sortObjectsByIds = sortObjectsByIds;
|
2014-09-06 09:13:47 +00:00
|
|
|
exports.setScopeValuesFromWhere = setScopeValuesFromWhere;
|
|
|
|
exports.mergeQuery = mergeQuery;
|
2013-10-11 18:50:00 +00:00
|
|
|
|
|
|
|
var traverse = require('traverse');
|
2012-01-30 19:37:14 +00:00
|
|
|
|
|
|
|
function safeRequire(module) {
|
2014-01-24 17:09:53 +00:00
|
|
|
try {
|
|
|
|
return require(module);
|
|
|
|
} catch (e) {
|
|
|
|
console.log('Run "npm install loopback-datasource-juggler ' + module
|
|
|
|
+ '" command to use loopback-datasource-juggler using ' + module
|
|
|
|
+ ' database engine');
|
|
|
|
process.exit(1);
|
|
|
|
}
|
2012-01-30 19:37:14 +00:00
|
|
|
}
|
|
|
|
|
2014-09-06 09:13:47 +00:00
|
|
|
/*
|
|
|
|
* Extracting fixed property values for the scope from the where clause into
|
|
|
|
* the data object
|
|
|
|
*
|
|
|
|
* @param {Object} The data object
|
|
|
|
* @param {Object} The where clause
|
|
|
|
*/
|
|
|
|
function setScopeValuesFromWhere(data, where, targetModel) {
|
|
|
|
for (var i in where) {
|
|
|
|
if (i === 'and') {
|
|
|
|
// Find fixed property values from each subclauses
|
|
|
|
for (var w = 0, n = where[i].length; w < n; w++) {
|
|
|
|
setScopeValuesFromWhere(data, where[i][w], targetModel);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var prop = targetModel.definition.properties[i];
|
|
|
|
if (prop) {
|
|
|
|
var val = where[i];
|
|
|
|
if (typeof val !== 'object' || val instanceof prop.type
|
|
|
|
|| prop.type.name === 'ObjectID') // MongoDB key
|
|
|
|
{
|
|
|
|
// Only pick the {propertyName: propertyValue}
|
|
|
|
data[i] = where[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Merge query parameters
|
|
|
|
* @param {Object} base The base object to contain the merged results
|
|
|
|
* @param {Object} update The object containing updates to be merged
|
2014-09-06 12:38:57 +00:00
|
|
|
* @param {Object} spec Optionally specifies parameters to exclude (set to false)
|
2014-09-06 09:13:47 +00:00
|
|
|
* @returns {*|Object} The base object
|
|
|
|
* @private
|
|
|
|
*/
|
2014-09-06 12:38:57 +00:00
|
|
|
function mergeQuery(base, update, spec) {
|
2014-09-06 09:13:47 +00:00
|
|
|
if (!update) {
|
|
|
|
return;
|
|
|
|
}
|
2014-09-06 12:38:57 +00:00
|
|
|
spec = spec || {};
|
2014-09-06 09:13:47 +00:00
|
|
|
base = base || {};
|
2014-09-06 12:38:57 +00:00
|
|
|
|
2014-09-06 09:13:47 +00:00
|
|
|
if (update.where && Object.keys(update.where).length > 0) {
|
|
|
|
if (base.where && Object.keys(base.where).length > 0) {
|
|
|
|
base.where = {and: [base.where, update.where]};
|
|
|
|
} else {
|
|
|
|
base.where = update.where;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Merge inclusion
|
2014-09-06 12:38:57 +00:00
|
|
|
if (spec.include !== false && update.include) {
|
2014-09-06 09:13:47 +00:00
|
|
|
if (!base.include) {
|
|
|
|
base.include = update.include;
|
|
|
|
} else {
|
|
|
|
var saved = base.include;
|
|
|
|
base.include = {};
|
|
|
|
base.include[update.include] = saved;
|
|
|
|
}
|
|
|
|
}
|
2014-09-06 12:38:57 +00:00
|
|
|
|
|
|
|
if (spec.collect !== false && update.collect) {
|
2014-09-06 09:13:47 +00:00
|
|
|
base.collect = update.collect;
|
|
|
|
}
|
2014-09-06 12:38:57 +00:00
|
|
|
|
|
|
|
// Overwrite fields
|
|
|
|
if (spec.fields !== false && update.fields !== undefined) {
|
|
|
|
base.fields = update.fields;
|
|
|
|
}
|
|
|
|
|
2014-09-06 09:13:47 +00:00
|
|
|
// set order
|
2014-09-06 12:38:57 +00:00
|
|
|
if ((!base.order || spec.order === false) && update.order) {
|
2014-09-06 09:13:47 +00:00
|
|
|
base.order = update.order;
|
|
|
|
}
|
|
|
|
|
|
|
|
// overwrite pagination
|
2014-09-06 12:38:57 +00:00
|
|
|
if (spec.limit !== false && update.limit !== undefined) {
|
2014-09-06 09:13:47 +00:00
|
|
|
base.limit = update.limit;
|
|
|
|
}
|
2014-09-06 12:38:57 +00:00
|
|
|
|
|
|
|
var skip = spec.skip !== false && spec.offset !== false;
|
|
|
|
|
|
|
|
if (skip && update.skip !== undefined) {
|
2014-09-06 09:13:47 +00:00
|
|
|
base.skip = update.skip;
|
|
|
|
}
|
2014-09-06 12:38:57 +00:00
|
|
|
|
|
|
|
if (skip && update.offset !== undefined) {
|
2014-09-06 09:13:47 +00:00
|
|
|
base.offset = update.offset;
|
|
|
|
}
|
2014-09-06 12:38:57 +00:00
|
|
|
|
2014-09-06 09:13:47 +00:00
|
|
|
return base;
|
|
|
|
}
|
|
|
|
|
2013-07-17 00:53:52 +00:00
|
|
|
function fieldsToArray(fields, properties) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (!fields) return;
|
|
|
|
|
|
|
|
// include all properties by default
|
|
|
|
var result = properties;
|
|
|
|
|
|
|
|
if (typeof fields === 'string') {
|
|
|
|
return [fields];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(fields) && fields.length > 0) {
|
|
|
|
// No empty array, including all the fields
|
|
|
|
return fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ('object' === typeof fields) {
|
|
|
|
// { field1: boolean, field2: boolean ... }
|
|
|
|
var included = [];
|
|
|
|
var excluded = [];
|
|
|
|
var keys = Object.keys(fields);
|
|
|
|
if (!keys.length) return;
|
|
|
|
|
|
|
|
keys.forEach(function (k) {
|
|
|
|
if (fields[k]) {
|
|
|
|
included.push(k);
|
|
|
|
} else if ((k in fields) && !fields[k]) {
|
|
|
|
excluded.push(k);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (included.length > 0) {
|
|
|
|
result = included;
|
|
|
|
} else if (excluded.length > 0) {
|
|
|
|
excluded.forEach(function (e) {
|
|
|
|
var index = result.indexOf(e);
|
|
|
|
result.splice(index, 1);
|
|
|
|
});
|
2013-07-17 00:53:52 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2013-07-17 00:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function selectFields(fields) {
|
|
|
|
// map function
|
|
|
|
return function (obj) {
|
|
|
|
var result = {};
|
|
|
|
var key;
|
2014-01-24 17:09:53 +00:00
|
|
|
|
2013-07-17 00:53:52 +00:00
|
|
|
for (var i = 0; i < fields.length; i++) {
|
|
|
|
key = fields[i];
|
2014-01-24 17:09:53 +00:00
|
|
|
|
2013-07-17 00:53:52 +00:00
|
|
|
result[key] = obj[key];
|
|
|
|
}
|
|
|
|
return result;
|
2013-10-30 05:16:43 +00:00
|
|
|
};
|
2013-10-11 18:50:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove undefined values from the queury object
|
|
|
|
* @param query
|
|
|
|
* @returns {exports.map|*}
|
|
|
|
*/
|
|
|
|
function removeUndefined(query) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (typeof query !== 'object' || query === null) {
|
|
|
|
return query;
|
|
|
|
}
|
|
|
|
// WARNING: [rfeng] Use map() will cause mongodb to produce invalid BSON
|
|
|
|
// as traverse doesn't transform the ObjectId correctly
|
|
|
|
return traverse(query).forEach(function (x) {
|
|
|
|
if (x === undefined) {
|
|
|
|
this.remove();
|
2013-10-11 18:50:00 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
|
|
if (!Array.isArray(x) && (typeof x === 'object' && x !== null
|
|
|
|
&& x.constructor !== Object)) {
|
|
|
|
// This object is not a plain object
|
|
|
|
this.update(x, true); // Stop navigating into this object
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
return x;
|
|
|
|
});
|
2013-10-11 18:50:00 +00:00
|
|
|
}
|
2013-10-25 23:18:02 +00:00
|
|
|
|
|
|
|
var url = require('url');
|
|
|
|
var qs = require('qs');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a URL into a settings object
|
|
|
|
* @param {String} urlStr The URL for connector settings
|
|
|
|
* @returns {Object} The settings object
|
|
|
|
*/
|
|
|
|
function parseSettings(urlStr) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (!urlStr) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
var uri = url.parse(urlStr, false);
|
|
|
|
var settings = {};
|
|
|
|
settings.connector = uri.protocol && uri.protocol.split(':')[0]; // Remove the trailing :
|
|
|
|
settings.host = settings.hostname = uri.hostname;
|
|
|
|
settings.port = uri.port && Number(uri.port); // port is a string
|
|
|
|
settings.user = settings.username = uri.auth && uri.auth.split(':')[0]; // <username>:<password>
|
|
|
|
settings.password = uri.auth && uri.auth.split(':')[1];
|
|
|
|
settings.database = uri.pathname && uri.pathname.split('/')[1]; // remove the leading /
|
|
|
|
settings.url = urlStr;
|
|
|
|
if (uri.query) {
|
|
|
|
var params = qs.parse(uri.query);
|
|
|
|
for (var p in params) {
|
|
|
|
settings[p] = params[p];
|
2013-10-25 23:18:02 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
|
|
|
return settings;
|
2013-10-25 23:18:02 +00:00
|
|
|
}
|
2013-12-06 23:52:39 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Merge model settings
|
|
|
|
*
|
|
|
|
* Folked from https://github.com/nrf110/deepmerge/blob/master/index.js
|
|
|
|
*
|
|
|
|
* The original function tries to merge array items if they are objects
|
|
|
|
*
|
|
|
|
* @param {Object} target The target settings object
|
|
|
|
* @param {Object} src The source settings object
|
|
|
|
* @returns {Object} The merged settings object
|
|
|
|
*/
|
|
|
|
function mergeSettings(target, src) {
|
|
|
|
var array = Array.isArray(src);
|
|
|
|
var dst = array && [] || {};
|
|
|
|
|
|
|
|
if (array) {
|
|
|
|
target = target || [];
|
|
|
|
dst = dst.concat(target);
|
2013-12-17 01:14:56 +00:00
|
|
|
src.forEach(function (e) {
|
|
|
|
if (dst.indexOf(e) === -1) {
|
|
|
|
dst.push(e);
|
2013-12-06 23:52:39 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if (target && typeof target === 'object') {
|
|
|
|
Object.keys(target).forEach(function (key) {
|
|
|
|
dst[key] = target[key];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
Object.keys(src).forEach(function (key) {
|
|
|
|
if (typeof src[key] !== 'object' || !src[key]) {
|
|
|
|
dst[key] = src[key];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!target[key]) {
|
|
|
|
dst[key] = src[key]
|
|
|
|
} else {
|
2013-12-17 01:14:56 +00:00
|
|
|
dst[key] = mergeSettings(target[key], src[key]);
|
2013-12-06 23:52:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return dst;
|
2014-01-28 17:57:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Define an non-enumerable __cachedRelations property
|
|
|
|
* @param {Object} obj The obj to receive the __cachedRelations
|
|
|
|
*/
|
|
|
|
function defineCachedRelations(obj) {
|
|
|
|
if (!obj.__cachedRelations) {
|
|
|
|
Object.defineProperty(obj, '__cachedRelations', {
|
|
|
|
writable: true,
|
|
|
|
enumerable: false,
|
|
|
|
configurable: true,
|
|
|
|
value: {}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the argument is plain object
|
|
|
|
* @param {*) obj The obj value
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
function isPlainObject(obj) {
|
|
|
|
return (typeof obj === 'object') && (obj !== null)
|
|
|
|
&& (obj.constructor === Object);
|
2014-08-15 17:39:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-08-15 17:47:12 +00:00
|
|
|
function sortObjectsByIds(idName, ids, objects, strict) {
|
2014-08-15 17:39:18 +00:00
|
|
|
ids = ids.map(function(id) {
|
2014-08-20 12:03:38 +00:00
|
|
|
return (typeof id === 'object') ? String(id) : id;
|
2014-08-15 17:39:18 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
var indexOf = function(x) {
|
|
|
|
var isObj = (typeof x[idName] === 'object'); // ObjectID
|
2014-08-20 12:03:38 +00:00
|
|
|
var id = isObj ? String(x[idName]) : x[idName];
|
2014-08-15 17:39:18 +00:00
|
|
|
return ids.indexOf(id);
|
|
|
|
};
|
|
|
|
|
|
|
|
var heading = [];
|
|
|
|
var tailing = [];
|
|
|
|
|
|
|
|
objects.forEach(function(x) {
|
|
|
|
if (typeof x === 'object') {
|
|
|
|
var idx = indexOf(x);
|
2014-08-15 17:47:12 +00:00
|
|
|
if (strict && idx === -1) return;
|
2014-08-15 17:39:18 +00:00
|
|
|
idx === -1 ? tailing.push(x) : heading.push(x);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
heading.sort(function(x, y) {
|
|
|
|
var a = indexOf(x);
|
|
|
|
var b = indexOf(y);
|
|
|
|
if (a === -1 || b === -1) return 1; // last
|
|
|
|
if (a === b) return 0;
|
|
|
|
if (a > b) return 1;
|
|
|
|
if (a < b) return -1;
|
|
|
|
});
|
|
|
|
|
|
|
|
return heading.concat(tailing);
|
|
|
|
};
|