Comparing version 2.1.0 to 2.1.1
@@ -0,1 +1,33 @@ | ||
### v2.2.0 - (to do, in future) | ||
- Fixes error code spelling: `PARAM_MISSMATCH` -> `PARAM_MISMATCH` | ||
### v2.1.1 - 13 Sep 2013 | ||
- Add TypeScript interface | ||
- Allow custom join tables (#276) | ||
- Fixes stack overflow when saving auto-fetched model with relations (#279) | ||
- Unique validator can be scoped and case insensitive (#288) | ||
- Allow async express middleware (#291) | ||
- Allow finding by associations (#293) | ||
- Fix sqlite find with boolean (#292) | ||
- Fix `afterLoad` hook error handling (#301) | ||
- Allow auto-escaping for custom queries (#304) | ||
- Add support for custom property types (#305) | ||
- Allow ordering by raw sql - .orderRaw() when chaining (#308, #311) | ||
- Fix saving Instance.extra fields (#312) | ||
- Fix `NaN` handling (#310) | ||
- Fix incorrect SQL query (#313) | ||
- Deprecated `PARAM_MISSMATCH` ErrorCode in favour of correctly spelt `PARAM_MISMATCH` (#315) | ||
- Add promises to query chain (#316) | ||
- Adds a test for hasMany.delAccessor with arguments switched (#320) | ||
- Allow passing timezone in database connection string, local timezone is now default (#325, #303) | ||
- Adds ability to call db.load() with multiple files (closes #329) | ||
- For mysql driver, when using pool, use con.release() instead of con.end() (if defined) (closes #335) | ||
- Passes error from afterLoad hook to ready event | ||
- Most errors now have a model property | ||
- Adds connection.pool and connection.debug settings | ||
- Fixes throw when calling ChainFind.first() or .last() and it has an error | ||
- Removes upper limit on VARCHAR column size | ||
- Allows multi-key models to support hasMany | ||
### v2.1.0 - 3 Aug 2013 | ||
@@ -2,0 +34,0 @@ |
@@ -7,3 +7,3 @@ var ErrorCodes = require("./ErrorCodes"); | ||
function AggregateFunctions(opts) { | ||
if (typeof opts.driver.getQuery != "function") { | ||
if (typeof opts.driver.getQuery !== "function") { | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "This driver does not support aggregate functions"); | ||
@@ -30,3 +30,3 @@ } | ||
if (fun == "distinct") { | ||
if (fun === "distinct") { | ||
used_distinct = true; | ||
@@ -44,3 +44,3 @@ } | ||
limit: function (offset, limit) { | ||
if (typeof limit == "number") { | ||
if (typeof limit === "number") { | ||
opts.limit = [ offset, limit ]; | ||
@@ -58,3 +58,3 @@ } else { | ||
if (arguments.length === 0) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "When using append you must at least define one property"); | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "When using append you must at least define one property"); | ||
} | ||
@@ -67,4 +67,4 @@ opts.properties = opts.properties.concat(Array.isArray(arguments[0]) ? | ||
as: function (alias) { | ||
if (aggregates.length === 0 || (aggregates.length == 1 && aggregates[0].length === 0)) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "No aggregate functions defined yet"); | ||
if (aggregates.length === 0 || (aggregates.length === 1 && aggregates[0].length === 0)) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "No aggregate functions defined yet"); | ||
} | ||
@@ -86,3 +86,3 @@ | ||
if (fun.toLowerCase() == "distinct") { | ||
if (fun.toLowerCase() === "distinct") { | ||
used_distinct = true; | ||
@@ -94,3 +94,3 @@ } | ||
get: function (cb) { | ||
if (typeof cb != "function") { | ||
if (typeof cb !== "function") { | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "You must pass a callback to Model.aggregate().get()"); | ||
@@ -102,3 +102,3 @@ } | ||
if (aggregates.length === 0) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "Missing aggregate functions"); | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "Missing aggregate functions"); | ||
} | ||
@@ -143,3 +143,3 @@ | ||
if (used_distinct && aggregates.length == 1) { | ||
if (used_distinct && aggregates.length === 1) { | ||
for (i = 0; i < data.length; i++) { | ||
@@ -146,0 +146,0 @@ items.push(data[i][Object.keys(data[i]).pop()]); |
@@ -76,3 +76,3 @@ var _ = require('lodash'); | ||
if (!Instance[Model.id]) { | ||
cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension")); | ||
cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension", { model: Model.table })); | ||
} else { | ||
@@ -90,3 +90,3 @@ association.model.get(util.values(Instance, Model.id), function (err, extension) { | ||
if (!Instance[Model.id]) { | ||
cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension")); | ||
cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension", { model: Model.table })); | ||
} else { | ||
@@ -131,3 +131,3 @@ association.model.get(util.values(Instance, Model.id), cb); | ||
if (!Instance[Model.id]) { | ||
cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension")); | ||
cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension", { model: Model.table })); | ||
} else { | ||
@@ -134,0 +134,0 @@ var conditions = {}; |
@@ -9,9 +9,2 @@ var _ = require("lodash"); | ||
exports.prepare = function (Model, associations) { | ||
if (Model.id.length > 1) { | ||
Model.hasMany = function () { | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Model.hasMany() does not support multiple keys models"); | ||
}; | ||
return; | ||
} | ||
Model.hasMany = function () { | ||
@@ -44,3 +37,3 @@ var name; | ||
for (var k in props) { | ||
props[k] = Property.normalize(props[k], Model.settings); | ||
props[k] = Property.normalize(props[k], {}, Model.settings); | ||
} | ||
@@ -50,2 +43,3 @@ } | ||
var assocName = opts.name || ucfirst(name); | ||
var assocTemplateName = opts.accessor || assocName; | ||
var association = { | ||
@@ -62,7 +56,7 @@ name : name, | ||
mergeAssocId : util.wrapFieldObject(opts.mergeAssocId, Model, name, Model.properties) || util.formatField(Model, name, true, opts.reversed), | ||
getAccessor : opts.getAccessor || ("get" + assocName), | ||
setAccessor : opts.setAccessor || ("set" + assocName), | ||
hasAccessor : opts.hasAccessor || ("has" + assocName), | ||
delAccessor : opts.delAccessor || ("remove" + assocName), | ||
addAccessor : opts.addAccessor || ("add" + assocName) | ||
getAccessor : opts.getAccessor || ("get" + assocTemplateName), | ||
setAccessor : opts.setAccessor || ("set" + assocTemplateName), | ||
hasAccessor : opts.hasAccessor || ("has" + assocTemplateName), | ||
delAccessor : opts.delAccessor || ("remove" + assocTemplateName), | ||
addAccessor : opts.addAccessor || ("add" + assocTemplateName) | ||
}; | ||
@@ -75,2 +69,3 @@ | ||
reversed : true, | ||
association : opts.reverseAssociation, | ||
mergeTable : association.mergeTable, | ||
@@ -247,3 +242,3 @@ mergeId : association.mergeAssocId, | ||
if (Instances.length === 0) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "No associations defined"); | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "No associations defined", { model: Model.name }); | ||
} | ||
@@ -271,4 +266,18 @@ | ||
value: function () { | ||
var Associations = Array.prototype.slice.apply(arguments); | ||
var cb = (typeof Associations[Associations.length - 1] == "function" ? Associations.pop() : noOperation); | ||
var Associations = []; | ||
var cb = noOperation; | ||
for (var i = 0; i < arguments.length; i++) { | ||
switch (typeof arguments[i]) { | ||
case "function": | ||
cb = arguments[i]; | ||
break; | ||
case "object": | ||
if (Array.isArray(arguments[i])) { | ||
Associations = Associations.concat(arguments[i]); | ||
} else if (arguments[i].isInstance) { | ||
Associations.push(arguments[i]); | ||
} | ||
break; | ||
} | ||
} | ||
var conditions = {}; | ||
@@ -314,5 +323,7 @@ var run = function () { | ||
var run = function () { | ||
var savedAssociations = []; | ||
var saveNextAssociation = function () { | ||
if (Associations.length === 0) { | ||
return cb(); | ||
return cb(null, savedAssociations); | ||
} | ||
@@ -339,2 +350,4 @@ | ||
savedAssociations.push(Association); | ||
return saveNextAssociation(); | ||
@@ -352,2 +365,4 @@ }); | ||
savedAssociations.push(Association); | ||
return saveNextAssociation(); | ||
@@ -379,3 +394,3 @@ }); | ||
if (Associations.length === 0) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "No associations defined"); | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "No associations defined", { model: Model.name }); | ||
} | ||
@@ -382,0 +397,0 @@ |
@@ -9,2 +9,3 @@ var _ = require("lodash"); | ||
var assocName; | ||
var assocTemplateName; | ||
var association = { | ||
@@ -37,2 +38,3 @@ name : Model.table, | ||
assocName = ucfirst(association.name); | ||
assocTemplateName = association.accessor || assocName; | ||
@@ -46,3 +48,3 @@ if (!association.hasOwnProperty("field")) { | ||
if (!association.hasOwnProperty(k + "Accessor")) { | ||
association[k + "Accessor"] = Accessors[k] + assocName; | ||
association[k + "Accessor"] = Accessors[k] + assocTemplateName; | ||
} | ||
@@ -55,2 +57,4 @@ } | ||
if (!association.reversed) { | ||
Model.allProperties[k] = _.omit(association.field[k], 'klass'); | ||
Model.allProperties[k].klass = 'hasOne'; | ||
model_fields.push(k); | ||
@@ -63,2 +67,4 @@ } | ||
reversed : true, | ||
accessor : association.reverseAccessor, | ||
reverseAccessor: undefined, | ||
field : association.field, | ||
@@ -70,3 +76,3 @@ autoFetch : association.autoFetch, | ||
Model["findBy" + assocName] = function () { | ||
Model["findBy" + assocTemplateName] = function () { | ||
var cb = null, conditions = null, options = {}; | ||
@@ -90,3 +96,3 @@ | ||
if (conditions === null) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, ".findBy(" + assocName + ") is missing a conditions object"); | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, ".findBy(" + assocName + ") is missing a conditions object"); | ||
} | ||
@@ -93,0 +99,0 @@ |
var _ = require("lodash"); | ||
var Singleton = require("./Singleton"); | ||
var ChainInstance = require("./ChainInstance"); | ||
var Promise = require("./Promise").Promise; | ||
@@ -8,2 +9,3 @@ module.exports = ChainFind; | ||
function ChainFind(Model, opts) { | ||
var promise = null; | ||
var chain = { | ||
@@ -13,14 +15,14 @@ find: function () { | ||
arguments = Array.prototype.slice.call(arguments); | ||
var args = Array.prototype.slice.call(arguments); | ||
opts.conditions = opts.conditions || {}; | ||
if (typeof _.last(arguments) == 'function') { | ||
cb = arguments.pop(); | ||
if (typeof _.last(args) === 'function') { | ||
cb = args.pop(); | ||
} | ||
if (typeof arguments[0] == 'object') { | ||
_.extend(opts.conditions, arguments[0]); | ||
} else if (typeof arguments[0] == 'string') { | ||
opts.conditions.__sql = opts.conditions.__sql || [] | ||
opts.conditions.__sql.push(arguments); | ||
if (typeof args[0] === 'object') { | ||
_.extend(opts.conditions, args[0]); | ||
} else if (typeof args[0] === 'string') { | ||
opts.conditions.__sql = opts.conditions.__sql || []; | ||
opts.conditions.__sql.push(args); | ||
} | ||
@@ -56,9 +58,16 @@ | ||
} | ||
if (property[0] == "-") { | ||
if (property[0] === "-") { | ||
opts.order.push([ property.substr(1), "Z" ]); | ||
} else { | ||
opts.order.push([ property, (order && order.toUpperCase() == "Z" ? "Z" : "A") ]); | ||
opts.order.push([ property, (order && order.toUpperCase() === "Z" ? "Z" : "A") ]); | ||
} | ||
return this; | ||
}, | ||
orderRaw: function (str, args) { | ||
if (!Array.isArray(opts.order)) { | ||
opts.order = []; | ||
} | ||
opts.order.push([ str, args || [] ]); | ||
return this; | ||
}, | ||
count: function (cb) { | ||
@@ -102,3 +111,3 @@ opts.driver.count(opts.table, opts.conditions, {}, function (err, data) { | ||
return this.run(function (err, items) { | ||
return cb(err, items.length > 0 ? items[0] : null); | ||
return cb(err, items && items.length > 0 ? items[0] : null); | ||
}); | ||
@@ -108,3 +117,3 @@ }, | ||
return this.run(function (err, items) { | ||
return cb(err, items.length > 0 ? items[items.length - 1] : null); | ||
return cb(err, items && items.length > 0 ? items[items.length - 1] : null); | ||
}); | ||
@@ -118,2 +127,16 @@ }, | ||
}, | ||
success: function (cb) { | ||
if (!promise) { | ||
promise = new Promise(); | ||
promise.handle(this.all); | ||
} | ||
return promise.success(cb); | ||
}, | ||
fail: function (cb) { | ||
if (!promise) { | ||
promise = new Promise(); | ||
promise.handle(this.all); | ||
} | ||
return promise.fail(cb); | ||
}, | ||
all: function (cb) { | ||
@@ -135,12 +158,14 @@ opts.driver.find(opts.only, opts.table, opts.conditions, { | ||
var createInstance = function (idx) { | ||
opts.newInstance(data[idx], function (err, instance) { | ||
data[idx] = instance; | ||
if (--pending === 0) { | ||
return cb(null, data); | ||
} | ||
}); | ||
}; | ||
for (var i = 0; i < data.length; i++) { | ||
(function (idx) { | ||
opts.newInstance(data[idx], function (err, instance) { | ||
data[idx] = instance; | ||
if (--pending === 0) { | ||
return cb(null, data); | ||
} | ||
}); | ||
})(i); | ||
createInstance(i); | ||
} | ||
@@ -162,3 +187,3 @@ }); | ||
"exists", "settings", "aggregate" ].indexOf(k) >= 0) continue; | ||
if (typeof Model[k] != "function") continue; | ||
if (typeof Model[k] !== "function") continue; | ||
@@ -184,3 +209,3 @@ chain[k] = Model[k]; | ||
for (var i = 0; i < assocIds.length; i++) { | ||
if (typeof conditions[assocIds[i]] == 'undefined') | ||
if (typeof conditions[assocIds[i]] === 'undefined') | ||
conditions[assocIds[i]] = source[ids[i]]; | ||
@@ -187,0 +212,0 @@ else if(Array.isArray(conditions[assocIds[i]])) |
@@ -63,3 +63,3 @@ module.exports = ChainInstance; | ||
if (i >= instances.length) { | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
cb(); | ||
@@ -72,3 +72,3 @@ } | ||
if (err) { | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
cb(err); | ||
@@ -87,3 +87,3 @@ } | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
return calls.forEach(cb); | ||
@@ -90,0 +90,0 @@ } |
@@ -26,35 +26,25 @@ var ErrorCodes = require("../../ErrorCodes"); | ||
var definitions = []; | ||
var k, i, pending; | ||
var k, i, pending, prop; | ||
var primary_keys = opts.id.map(function (k) { return driver.query.escapeId(k); }); | ||
var keys = []; | ||
for (i = 0; i < opts.id.length; i++) { | ||
if (opts.properties.hasOwnProperty(opts.id[i])) continue; | ||
keys.push(driver.query.escapeId(opts.id[i])); | ||
for (k in opts.allProperties) { | ||
prop = opts.allProperties[k]; | ||
definitions.push(buildColumnDefinition(driver, k, prop)); | ||
} | ||
for (i = 0; i < keys.length; i++) { | ||
definitions.push(keys[i] + " INT(10) UNSIGNED NOT NULL"); | ||
} | ||
if (opts.id.length == 1 && !opts.extension) { | ||
definitions[definitions.length - 1] += " AUTO_INCREMENT"; | ||
} | ||
for (k in opts.properties) { | ||
definitions.push(buildColumnDefinition(driver, k, opts.properties[k])); | ||
} | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
if (opts.one_associations[i].extension) continue; | ||
if (opts.one_associations[i].reversed) continue; | ||
for (k in opts.one_associations[i].field) { | ||
definitions.push(buildColumnDefinition(driver, k, opts.one_associations[i].field[k])); | ||
for (k in opts.allProperties) { | ||
prop = opts.allProperties[k]; | ||
if (prop.unique === true) { | ||
definitions.push("UNIQUE (" + driver.query.escapeId(k) + ")"); | ||
} else if (prop.index) { | ||
definitions.push("INDEX (" + driver.query.escapeId(k) + ")"); | ||
} | ||
} | ||
for (k in opts.properties) { | ||
if (opts.properties[k].unique === true) { | ||
for (k in opts.allProperties) { | ||
prop = opts.allProperties[k]; | ||
if (prop.unique === true) { | ||
definitions.push("UNIQUE KEY " + driver.query.escapeId(k) + " (" + driver.query.escapeId(k) + ")"); | ||
} else if (opts.properties[k].index) { | ||
} else if (prop.index) { | ||
definitions.push("INDEX (" + driver.query.escapeId(k) + ")"); | ||
@@ -136,3 +126,4 @@ } | ||
function buildColumnDefinition(driver, name, prop) { | ||
var def; | ||
var def = driver.query.escapeId(name); | ||
var customType; | ||
@@ -142,12 +133,15 @@ switch (prop.type) { | ||
if (prop.big === true) { | ||
def = driver.query.escapeId(name) + " LONGTEXT"; | ||
def += " LONGTEXT"; | ||
} else { | ||
def = driver.query.escapeId(name) + " VARCHAR(" + Math.min(Math.max(parseInt(prop.size, 10) || 255, 1), 65535) + ")"; | ||
def += " VARCHAR(" + Math.min(Math.max(parseInt(prop.size, 10) || 255, 1), 65535) + ")"; | ||
} | ||
break; | ||
case "serial": | ||
def += " INT(10) UNSIGNED NOT NULL AUTO_INCREMENT"; | ||
break; | ||
case "number": | ||
if (prop.rational === false) { | ||
def = driver.query.escapeId(name) + " " + colTypes.integer[prop.size || 4]; | ||
def += " " + colTypes.integer[prop.size || 4]; | ||
} else { | ||
def = driver.query.escapeId(name) + " " + colTypes.floating[prop.size || 4]; | ||
def += " " + colTypes.floating[prop.size || 4]; | ||
} | ||
@@ -159,9 +153,9 @@ if (prop.unsigned === true) { | ||
case "boolean": | ||
def = driver.query.escapeId(name) + " BOOLEAN"; | ||
def += " BOOLEAN"; | ||
break; | ||
case "date": | ||
if (prop.time === false) { | ||
def = driver.query.escapeId(name) + " DATE"; | ||
def += " DATE"; | ||
} else { | ||
def = driver.query.escapeId(name) + " DATETIME"; | ||
def += " DATETIME"; | ||
} | ||
@@ -172,9 +166,9 @@ break; | ||
if (prop.big === true) { | ||
def = driver.query.escapeId(name) + " LONGBLOB"; | ||
def += " LONGBLOB"; | ||
} else { | ||
def = driver.query.escapeId(name) + " BLOB"; | ||
def += " BLOB"; | ||
} | ||
break; | ||
case "enum": | ||
def = driver.query.escapeId(name) + " ENUM (" + | ||
def += " ENUM (" + | ||
prop.values.map(driver.query.escapeVal.bind(driver.query)) + | ||
@@ -184,8 +178,13 @@ ")"; | ||
case "point": | ||
def = driver.query.escapeId(name) + " POINT"; | ||
def += " POINT"; | ||
break; | ||
default: | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { | ||
property : prop | ||
}); | ||
customType = driver.customTypes[prop.type]; | ||
if (customType) { | ||
def += " " + customType.datastoreType(prop); | ||
} else { | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { | ||
property : prop | ||
}); | ||
} | ||
} | ||
@@ -192,0 +191,0 @@ if (prop.required === true) { |
@@ -27,26 +27,14 @@ var ErrorCodes = require("../../ErrorCodes"); | ||
var definitions = []; | ||
var k, i, pending; | ||
var k, i, pending, prop; | ||
var primary_keys = opts.id.map(function (k) { return driver.query.escapeId(k); }); | ||
var keys = []; | ||
for (i = 0; i < opts.id.length; i++) { | ||
if (opts.properties.hasOwnProperty(opts.id[i])) continue; | ||
keys.push(driver.query.escapeId(opts.id[i])); | ||
} | ||
for (k in opts.allProperties) { | ||
prop = opts.allProperties[k]; | ||
definitions.push(buildColumnDefinition(driver, opts.table, k, prop)); | ||
for (i = 0; i < keys.length; i++) { | ||
definitions.push(keys[i] + " INTEGER NOT NULL"); | ||
} | ||
if (opts.id.length == 1 && !opts.extension) { | ||
definitions[definitions.length - 1] = keys[0] + " SERIAL"; | ||
} | ||
for (k in opts.properties) { | ||
definitions.push(buildColumnDefinition(driver, opts.table, k, opts.properties[k])); | ||
if (opts.properties[k].type == "enum") { | ||
if (prop.type == "enum") { | ||
typequeries.push( | ||
"CREATE TYPE " + driver.query.escapeId("enum_" + opts.table + "_" + k) + " AS ENUM (" + | ||
opts.properties[k].values.map(driver.query.escapeVal.bind(driver)) + ")" | ||
prop.values.map(driver.query.escapeVal.bind(driver)) + ")" | ||
); | ||
@@ -56,14 +44,7 @@ } | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
if (opts.one_associations[i].extension) continue; | ||
if (opts.one_associations[i].reversed) continue; | ||
for (k in opts.one_associations[i].field) { | ||
definitions.push(buildColumnDefinition(driver, opts.table, k, opts.one_associations[i].field[k])); | ||
} | ||
} | ||
for (k in opts.properties) { | ||
if (opts.properties[k].unique === true) { | ||
for (k in opts.allProperties) { | ||
prop = opts.allProperties[k]; | ||
if (prop.unique === true) { | ||
definitions.push("UNIQUE (" + driver.query.escapeId(k) + ")"); | ||
} else if (opts.properties[k].index) { | ||
} else if (prop.index) { | ||
definitions.push("INDEX (" + driver.query.escapeId(k) + ")"); | ||
@@ -82,3 +63,3 @@ } | ||
}); | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
@@ -107,3 +88,3 @@ if (opts.one_associations[i].extension) continue; | ||
typequeries = []; | ||
for (k in opts.many_associations[i].mergeId) { | ||
@@ -127,3 +108,3 @@ definitions.push(buildColumnDefinition(driver, opts.many_associations[i].mergeTable, k, opts.many_associations[i].mergeId[k])); | ||
} | ||
var index = null; | ||
@@ -205,3 +186,4 @@ for (k in opts.many_associations[i].mergeId) { | ||
function buildColumnDefinition(driver, table, name, prop) { | ||
var def; | ||
var def = driver.query.escapeId(name); | ||
var customType; | ||
@@ -211,22 +193,25 @@ switch (prop.type) { | ||
if (prop.big === true) { | ||
def = driver.query.escapeId(name) + " TEXT"; | ||
def += " TEXT"; | ||
} else { | ||
def = driver.query.escapeId(name) + " VARCHAR(" + Math.min(Math.max(parseInt(prop.size, 10) || 255, 1), 65535) + ")"; | ||
def += " VARCHAR(" + Math.max(parseInt(prop.size, 10) || 255, 1) + ")"; | ||
} | ||
break; | ||
case "serial": | ||
def += " SERIAL"; | ||
break; | ||
case "number": | ||
if (prop.rational === false) { | ||
def = driver.query.escapeId(name) + " " + colTypes.integer[prop.size || 4]; | ||
def += " " + colTypes.integer[prop.size || 4]; | ||
} else { | ||
def = driver.query.escapeId(name) + " " + colTypes.floating[prop.size || 4]; | ||
def += " " + colTypes.floating[prop.size || 4]; | ||
} | ||
break; | ||
case "boolean": | ||
def = driver.query.escapeId(name) + " BOOLEAN"; | ||
def += " BOOLEAN"; | ||
break; | ||
case "date": | ||
if (prop.time === false) { | ||
def = driver.query.escapeId(name) + " DATE"; | ||
def += " DATE"; | ||
} else { | ||
def = driver.query.escapeId(name) + " TIMESTAMP WITHOUT TIME ZONE"; | ||
def += " TIMESTAMP WITHOUT TIME ZONE"; | ||
} | ||
@@ -236,14 +221,19 @@ break; | ||
case "object": | ||
def = driver.query.escapeId(name) + " BYTEA"; | ||
def += " BYTEA"; | ||
break; | ||
case "enum": | ||
def = driver.query.escapeId(name) + " " + driver.query.escapeId("enum_" + table + "_" + name); | ||
def += " " + driver.query.escapeId("enum_" + table + "_" + name); | ||
break; | ||
case "point": | ||
def = driver.query.escapeId(name) + " POINT"; | ||
def += " POINT"; | ||
break; | ||
default: | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { | ||
property : prop | ||
}); | ||
customType = driver.customTypes[prop.type]; | ||
if (customType) { | ||
def += " " + customType.datastoreType(prop); | ||
} else { | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { | ||
property : prop | ||
}); | ||
} | ||
} | ||
@@ -250,0 +240,0 @@ if (prop.required === true) { |
@@ -25,32 +25,11 @@ var ErrorCodes = require("../../ErrorCodes"); | ||
var definitions = []; | ||
var k, i, pending; | ||
var k, i, pending, prop; | ||
var primary_keys = opts.id.map(function (k) { return driver.query.escapeId(k); }); | ||
var keys = []; | ||
for (i = 0; i < opts.id.length; i++) { | ||
if (opts.properties.hasOwnProperty(opts.id[i])) continue; | ||
keys.push(driver.query.escapeId(opts.id[i])); | ||
for (k in opts.allProperties) { | ||
prop = opts.allProperties[k]; | ||
definitions.push(buildColumnDefinition(driver, k, prop)); | ||
} | ||
for (i = 0; i < keys.length; i++) { | ||
definitions.push(keys[i] + " INTEGER UNSIGNED NOT NULL"); | ||
} | ||
if (opts.id.length == 1 && !opts.extension) { | ||
definitions[definitions.length - 1] = keys[0] + " INTEGER PRIMARY KEY AUTOINCREMENT"; | ||
} | ||
for (k in opts.properties) { | ||
definitions.push(buildColumnDefinition(driver, k, opts.properties[k])); | ||
} | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
if (opts.one_associations[i].extension) continue; | ||
if (opts.one_associations[i].reversed) continue; | ||
for (k in opts.one_associations[i].field) { | ||
definitions.push(buildColumnDefinition(driver, k, opts.one_associations[i].field[k])); | ||
} | ||
} | ||
if (keys.length > 1) { | ||
@@ -79,3 +58,3 @@ definitions.push("PRIMARY KEY (" + primary_keys.join(", ") + ")"); | ||
} | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
@@ -117,3 +96,3 @@ if (opts.one_associations[i].extension) continue; | ||
} | ||
var index = null; | ||
@@ -153,32 +132,41 @@ for (k in opts.many_associations[i].mergeId) { | ||
function buildColumnDefinition(driver, name, prop) { | ||
var def; | ||
var def = driver.query.escapeId(name); | ||
var customType; | ||
switch (prop.type) { | ||
case "text": | ||
def = driver.query.escapeId(name) + " TEXT"; | ||
def += " TEXT"; | ||
break; | ||
case "serial": | ||
def += " INTEGER PRIMARY KEY AUTOINCREMENT"; | ||
break; | ||
case "number": | ||
if (prop.rational === false) { | ||
def = driver.query.escapeId(name) + " INTEGER"; | ||
def += " INTEGER"; | ||
} else { | ||
def = driver.query.escapeId(name) + " REAL"; | ||
def += " REAL"; | ||
} | ||
break; | ||
case "boolean": | ||
def = driver.query.escapeId(name) + " INTEGER UNSIGNED"; | ||
def += " INTEGER UNSIGNED"; | ||
break; | ||
case "date": | ||
def = driver.query.escapeId(name) + " DATETIME"; | ||
def += " DATETIME"; | ||
break; | ||
case "binary": | ||
case "object": | ||
def = driver.query.escapeId(name) + " BLOB"; | ||
def += " BLOB"; | ||
break; | ||
case "enum": | ||
def = driver.query.escapeId(name) + " INTEGER"; | ||
def += " INTEGER"; | ||
break; | ||
default: | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { | ||
property : prop | ||
}); | ||
customType = driver.customTypes[prop.type]; | ||
if (customType) { | ||
def += " " + customType.datastoreType(prop); | ||
} else { | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { | ||
property : prop | ||
}); | ||
} | ||
} | ||
@@ -185,0 +173,0 @@ if (prop.required === true) { |
@@ -1,4 +0,5 @@ | ||
var mongodb = require("mongodb"); | ||
var util = require("../../Utilities"); | ||
var _ = require('lodash'); | ||
var Utilities = require("../../Utilities"); | ||
var mongodb = require("mongodb"); | ||
var util = require("util"); | ||
var _ = require('lodash'); | ||
@@ -13,2 +14,6 @@ exports.Driver = Driver; | ||
if (!this.config.timezone) { | ||
this.config.timezone = "local"; | ||
} | ||
this.opts.settings.set("properties.primary_key", "_id"); | ||
@@ -104,3 +109,3 @@ this.opts.settings.set("properties.association_key", function (name, field) { | ||
convertToDB(conditions); | ||
convertToDB(conditions, this.config.timezone); | ||
@@ -133,3 +138,3 @@ var cursor = (fields ? collection.find(conditions, fields) : collection.find(conditions)); | ||
for (var i = 0; i < docs.length; i++) { | ||
convertFromDB(docs[i]); | ||
convertFromDB(docs[i], this.config.timezone); | ||
if (opts.extra && opts.extra[docs[i]._id]) { | ||
@@ -154,3 +159,3 @@ docs[i] = _.merge(docs[i], _.omit(opts.extra[docs[i]._id], '_id')); | ||
} | ||
}); | ||
}.bind(this)); | ||
}; | ||
@@ -161,3 +166,3 @@ | ||
convertToDB(conditions); | ||
convertToDB(conditions, this.config.timezone); | ||
@@ -189,3 +194,3 @@ var cursor = collection.find(conditions); | ||
Driver.prototype.insert = function (table, data, id_prop, cb) { | ||
convertToDB(data); | ||
convertToDB(data, this.config.timezone); | ||
@@ -208,7 +213,7 @@ return this.db.collection(table).insert( | ||
} | ||
convertFromDB(ids); | ||
convertFromDB(ids, this.config.timezone); | ||
} | ||
return cb(null, ids); | ||
} | ||
}.bind(this) | ||
); | ||
@@ -269,3 +274,3 @@ }; | ||
options.order[0] = options.order[0][1]; | ||
options.order = util.standardizeOrder(options.order); | ||
options.order = Utilities.standardizeOrder(options.order); | ||
} | ||
@@ -331,4 +336,4 @@ | ||
Driver.prototype.update = function (table, changes, conditions, cb) { | ||
convertToDB(changes); | ||
convertToDB(conditions); | ||
convertToDB(changes, this.config.timezone); | ||
convertToDB(conditions, this.config.timezone); | ||
@@ -349,3 +354,3 @@ return this.db.collection(table).update( | ||
Driver.prototype.remove = function (table, conditions, cb) { | ||
convertToDB(conditions); | ||
convertToDB(conditions, this.config.timezone); | ||
@@ -359,7 +364,7 @@ return this.db.collection(table).remove(conditions, cb); | ||
function convertToDB(obj) { | ||
function convertToDB(obj, timeZone) { | ||
for (var k in obj) { | ||
if (Array.isArray(obj[k])) { | ||
for (var i = 0; i < obj[k].length; i++) { | ||
obj[k][i] = convertToDBVal(k, obj[k][i]); | ||
obj[k][i] = convertToDBVal(k, obj[k][i], timeZone); | ||
} | ||
@@ -371,12 +376,15 @@ | ||
obj[k] = convertToDBVal(k, obj[k]); | ||
obj[k] = convertToDBVal(k, obj[k], timeZone); | ||
} | ||
} | ||
function convertFromDB(obj) { | ||
function convertFromDB(obj, timezone) { | ||
for (var k in obj) { | ||
if (obj[k] instanceof mongodb.ObjectID) { | ||
obj[k] = obj[k].toString(); | ||
} else if (obj[k] instanceof mongodb.Binary) { | ||
continue; | ||
} | ||
if (obj[k] instanceof mongodb.Binary) { | ||
obj[k] = new Buffer(obj[k].value(), "binary"); | ||
continue; | ||
} | ||
@@ -386,3 +394,3 @@ } | ||
function convertToDBVal(key, value) { | ||
function convertToDBVal(key, value, timezone) { | ||
if (value && typeof value.sql_comparator == "function") { | ||
@@ -426,1 +434,5 @@ var val = (key != "_id" ? value.val : new mongodb.ObjectID(value.val)); | ||
} | ||
Object.defineProperty(Driver.prototype, "isSql", { | ||
value: false | ||
}); |
@@ -1,3 +0,5 @@ | ||
var mysql = require("mysql"); | ||
var Query = require("sql-query").Query; | ||
var _ = require("lodash"); | ||
var mysql = require("mysql"); | ||
var Query = require("sql-query").Query; | ||
var helpers = require("../helpers"); | ||
@@ -9,8 +11,8 @@ exports.Driver = Driver; | ||
this.opts = opts || {}; | ||
this.query = new Query("mysql"); | ||
this.customTypes = {}; | ||
if (!this.config.timezone) { | ||
// force UTC if not defined, UTC is always better.. | ||
this.config.timezone = "Z"; | ||
this.config.timezone = "local"; | ||
} | ||
this.query = new Query({ dialect: "mysql", timezone: config.timezone }); | ||
@@ -25,5 +27,7 @@ this.reconnect(null, connection); | ||
"SUM", "COUNT", | ||
"DISTINCT" ]; | ||
"DISTINCT"]; | ||
} | ||
_.extend(Driver.prototype, helpers.sql); | ||
Driver.prototype.sync = function (opts, cb) { | ||
@@ -54,3 +58,7 @@ return require("../DDL/mysql").sync(this, opts, cb); | ||
if (!err) { | ||
con.end(); | ||
if (con.release) { | ||
con.release(); | ||
} else { | ||
con.end(); | ||
} | ||
} | ||
@@ -85,3 +93,3 @@ return cb(err); | ||
Driver.prototype.execQuery = function (query, cb) { | ||
Driver.prototype.execSimpleQuery = function (query, cb) { | ||
if (this.opts.debug) { | ||
@@ -135,3 +143,3 @@ require("../../Debug").sql('mysql', query); | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -163,3 +171,3 @@ | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -173,3 +181,3 @@ | ||
this.execQuery(q, function (err, info) { | ||
this.execSimpleQuery(q, function (err, info) { | ||
if (err) return cb(err); | ||
@@ -199,3 +207,3 @@ | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -209,3 +217,3 @@ | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -216,3 +224,3 @@ | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -227,3 +235,7 @@ | ||
con.query(query, function (err, data) { | ||
con.end(); | ||
if (con.release) { | ||
con.release(); | ||
} else { | ||
con.end(); | ||
} | ||
@@ -236,31 +248,51 @@ return cb(err, data); | ||
Driver.prototype.valueToProperty = function (value, property) { | ||
var customType; | ||
switch (property.type) { | ||
case "boolean": | ||
return !!value; | ||
value = !!value; | ||
break; | ||
case "object": | ||
if (typeof value == "object" && !Buffer.isBuffer(value)) { | ||
return value; | ||
break; | ||
} | ||
try { | ||
return JSON.parse(value); | ||
value = JSON.parse(value); | ||
} catch (e) { | ||
return null; | ||
value = null; | ||
} | ||
break; | ||
default: | ||
return value; | ||
customType = this.customTypes[property.type]; | ||
if(customType && 'valueToProperty' in customType) { | ||
value = customType.valueToProperty(value); | ||
} | ||
} | ||
return value; | ||
}; | ||
Driver.prototype.propertyToValue = function (value, property) { | ||
var customType; | ||
switch (property.type) { | ||
case "boolean": | ||
return (value) ? 1 : 0; | ||
value = (value) ? 1 : 0; | ||
break; | ||
case "object": | ||
return JSON.stringify(value); | ||
value = JSON.stringify(value); | ||
break; | ||
case "point": | ||
return function() { return 'POINT(' + value.x + ', ' + value.y + ')'; }; | ||
break; | ||
default: | ||
return value; | ||
customType = this.customTypes[property.type]; | ||
if(customType && 'propertyToValue' in customType) { | ||
value = customType.propertyToValue(value); | ||
} | ||
} | ||
return value; | ||
}; | ||
Object.defineProperty(Driver.prototype, "isSql", { | ||
value: true | ||
}); |
@@ -1,3 +0,5 @@ | ||
var pg = require("pg"); | ||
var Query = require("sql-query").Query; | ||
var _ = require("lodash"); | ||
var pg = require("pg"); | ||
var Query = require("sql-query").Query; | ||
var helpers = require("../helpers"); | ||
@@ -10,12 +12,19 @@ exports.Driver = Driver; | ||
this.opts = opts || {}; | ||
this.query = new Query("postgresql"); | ||
if (!this.config.timezone) { | ||
this.config.timezone = "local"; | ||
} | ||
this.query = new Query({ dialect: "postgresql", timezone: this.config.timezone }); | ||
this.customTypes = {}; | ||
if (connection) { | ||
this.db = connection; | ||
} else { | ||
if (config.query && config.query.ssl) { | ||
if (this.config.query && this.config.query.ssl) { | ||
config.ssl = true; | ||
this.config = config; | ||
} else { | ||
this.config = config.href || config; | ||
this.config = _.extend(this.config, config); | ||
// } else { | ||
// this.config = _.extend(this.config, config); | ||
// this.config = config.href || config; | ||
} | ||
@@ -31,5 +40,3 @@ | ||
for (var name in functions) { | ||
this.constructor.prototype[name] = functions[name]; | ||
} | ||
_.extend(this.constructor.prototype, functions); | ||
@@ -53,3 +60,3 @@ this.aggregate_functions = [ "ABS", "CEIL", "FLOOR", "ROUND", | ||
}, | ||
execQuery: function (query, cb) { | ||
execSimpleQuery: function (query, cb) { | ||
if (this.opts.debug) { | ||
@@ -83,3 +90,3 @@ require("../../Debug").sql('postgres', query); | ||
}, | ||
execQuery: function (query, cb) { | ||
execSimpleQuery: function (query, cb) { | ||
if (this.opts.debug) { | ||
@@ -106,2 +113,4 @@ require("../../Debug").sql('postgres', query); | ||
_.extend(Driver.prototype, helpers.sql); | ||
Driver.prototype.sync = function (opts, cb) { | ||
@@ -116,3 +125,3 @@ return require("../DDL/postgres").sync(this, opts, cb); | ||
Driver.prototype.ping = function (cb) { | ||
this.execQuery("SELECT * FROM pg_stat_activity LIMIT 1", function () { | ||
this.execSimpleQuery("SELECT * FROM pg_stat_activity LIMIT 1", function () { | ||
return cb(); | ||
@@ -124,3 +133,7 @@ }); | ||
Driver.prototype.close = function (cb) { | ||
this.db.end(cb); | ||
this.db.end(); | ||
if (typeof cb == "function") cb(); | ||
return; | ||
}; | ||
@@ -167,3 +180,3 @@ | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -195,3 +208,3 @@ | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -205,3 +218,3 @@ | ||
this.execQuery(q + " RETURNING *", function (err, results) { | ||
this.execSimpleQuery(q + " RETURNING *", function (err, results) { | ||
if (err) { | ||
@@ -230,3 +243,3 @@ return cb(err); | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -240,3 +253,3 @@ | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
@@ -247,15 +260,17 @@ | ||
this.execQuery(q, cb); | ||
this.execSimpleQuery(q, cb); | ||
}; | ||
Driver.prototype.valueToProperty = function (value, property) { | ||
var customType, v; | ||
switch (property.type) { | ||
case "object": | ||
if (typeof value == "object" && !Buffer.isBuffer(value)) { | ||
return value; | ||
break; | ||
} | ||
try { | ||
return JSON.parse(value); | ||
value = JSON.parse(value); | ||
} catch (e) { | ||
return null; | ||
value = null; | ||
} | ||
@@ -267,19 +282,52 @@ break; | ||
if (m) { | ||
return { x : parseFloat(m[1], 10) , y : parseFloat(m[2], 10) }; | ||
value = { x : parseFloat(m[1], 10) , y : parseFloat(m[2], 10) }; | ||
} | ||
} | ||
return value; | ||
break; | ||
case "date": | ||
if (this.config.timezone && this.config.timezone != 'local') { | ||
var tz = convertTimezone(this.config.timezone); | ||
// shift local to UTC | ||
value.setTime(value.getTime() - (value.getTimezoneOffset() * 60000)); | ||
if (tz !== false) { | ||
// shift UTC to timezone | ||
value.setTime(value.getTime() - (tz * 60000)); | ||
} | ||
} | ||
break; | ||
case "number": | ||
if (value !== null) { | ||
return Number(value); | ||
if (typeof value != 'number' && value !== null) { | ||
v = Number(value); | ||
if (!isNaN(v)) value = v; | ||
} | ||
break; | ||
default: | ||
return value; | ||
customType = this.customTypes[property.type]; | ||
if(customType && 'valueToProperty' in customType) { | ||
value = customType.valueToProperty(value); | ||
} | ||
} | ||
return value; | ||
}; | ||
Driver.prototype.propertyToValue = function (value, property) { | ||
var customType; | ||
switch (property.type) { | ||
case "object": | ||
return JSON.stringify(value); | ||
value = JSON.stringify(value); | ||
break; | ||
case "date": | ||
if (this.config.timezone && this.config.timezone != 'local') { | ||
var tz = convertTimezone(this.config.timezone); | ||
// shift local to UTC | ||
value.setTime(value.getTime() + (value.getTimezoneOffset() * 60000)); | ||
if (tz !== false) { | ||
// shift UTC to timezone | ||
value.setTime(value.getTime() + (tz * 60000)); | ||
} | ||
} | ||
break; | ||
case "point": | ||
@@ -289,5 +337,24 @@ return function () { | ||
}; | ||
break; | ||
default: | ||
return value; | ||
customType = this.customTypes[property.type]; | ||
if(customType && 'propertyToValue' in customType) { | ||
value = customType.propertyToValue(value); | ||
} | ||
} | ||
return value; | ||
}; | ||
Object.defineProperty(Driver.prototype, "isSql", { | ||
value: true | ||
}); | ||
function convertTimezone(tz) { | ||
if (tz == "Z") return 0; | ||
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/); | ||
if (m) { | ||
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60; | ||
} | ||
return false; | ||
} |
@@ -0,3 +1,6 @@ | ||
var _ = require("lodash"); | ||
var util = require("util"); | ||
var sqlite3 = require("sqlite3"); | ||
var Query = require("sql-query").Query; | ||
var helpers = require("../helpers"); | ||
@@ -9,4 +12,10 @@ exports.Driver = Driver; | ||
this.opts = opts || {}; | ||
this.query = new Query("sqlite"); | ||
if (!this.config.timezone) { | ||
this.config.timezone = "local"; | ||
} | ||
this.query = new Query({ dialect: "sqlite", timezone: this.config.timezone }); | ||
this.customTypes = {}; | ||
if (connection) { | ||
@@ -32,2 +41,4 @@ this.db = connection; | ||
_.extend(Driver.prototype, helpers.sql); | ||
Driver.prototype.sync = function (opts, cb) { | ||
@@ -66,3 +77,3 @@ return require("../DDL/sqlite").sync(this, opts, cb); | ||
Driver.prototype.execQuery = function (query, cb) { | ||
Driver.prototype.execSimpleQuery = function (query, cb) { | ||
if (this.opts.debug) { | ||
@@ -207,33 +218,65 @@ require("../../Debug").sql('mysql', query); | ||
Driver.prototype.valueToProperty = function (value, property) { | ||
var v, customType; | ||
switch (property.type) { | ||
case "boolean": | ||
return !!value; | ||
value = !!value; | ||
break; | ||
case "object": | ||
if (typeof value == "object" && !Buffer.isBuffer(value)) { | ||
return value; | ||
break; | ||
} | ||
try { | ||
return JSON.parse(value); | ||
value = JSON.parse(value); | ||
} catch (e) { | ||
return null; | ||
value = null; | ||
} | ||
break; | ||
case "number": | ||
if (typeof value != 'number' && value !== null) { | ||
v = Number(value); | ||
if (!isNaN(v)) { | ||
value = v; | ||
} | ||
} | ||
break; | ||
case "date": | ||
if (typeof value === 'string') { | ||
if (typeof value == 'string') { | ||
if (value.indexOf('Z', value.length - 1) === -1) { | ||
return new Date(value + 'Z'); | ||
value = new Date(value + 'Z'); | ||
} else { | ||
value = new Date(value); | ||
} | ||
if (this.config.timezone && this.config.timezone != 'local') { | ||
var tz = convertTimezone(this.config.timezone); | ||
// shift local to UTC | ||
value.setTime(value.getTime() - (value.getTimezoneOffset() * 60000)); | ||
if (tz !== false) { | ||
// shift UTC to timezone | ||
value.setTime(value.getTime() - (tz * 60000)); | ||
} | ||
} | ||
} | ||
return new Date(value); | ||
break; | ||
default: | ||
return value; | ||
customType = this.customTypes[property.type]; | ||
if(customType && 'valueToProperty' in customType) { | ||
value = customType.valueToProperty(value); | ||
} | ||
} | ||
return value; | ||
}; | ||
Driver.prototype.propertyToValue = function (value, property) { | ||
var customType; | ||
switch (property.type) { | ||
case "boolean": | ||
return (value) ? 1 : 0; | ||
value = (value) ? 1 : 0; | ||
break; | ||
case "object": | ||
return JSON.stringify(value); | ||
value = JSON.stringify(value); | ||
break; | ||
case "date": | ||
@@ -253,3 +296,4 @@ if (this.config.query && this.config.query.strdates) { | ||
if (property.time === false) { | ||
return strdate; | ||
value = strdate; | ||
break; | ||
} | ||
@@ -277,10 +321,27 @@ | ||
strdate += ' ' + hours + ':' + minutes + ':' + seconds + '.' + millis + '000'; | ||
return strdate; | ||
value = strdate; | ||
} | ||
} | ||
return value; | ||
break; | ||
default: | ||
return value; | ||
customType = this.customTypes[property.type]; | ||
if(customType && 'propertyToValue' in customType) { | ||
value = customType.propertyToValue(value); | ||
} | ||
} | ||
return value; | ||
}; | ||
Object.defineProperty(Driver.prototype, "isSql", { | ||
value: true | ||
}); | ||
function convertTimezone(tz) { | ||
if (tz == "Z") return 0; | ||
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/); | ||
if (m) { | ||
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60; | ||
} | ||
return false; | ||
} |
@@ -6,6 +6,14 @@ exports.QUERY_ERROR = 1; | ||
exports.MISSING_CALLBACK = 5; | ||
exports.PARAM_MISSMATCH = 6; | ||
exports.PARAM_MISMATCH = 6; | ||
exports.CONNECTION_LOST = 10; | ||
// Deprecated, remove on next major release. | ||
Object.defineProperty(exports, "PARAM_MISSMATCH", { | ||
enumerable: true, get: function () { | ||
console.log("PARAM_MISSMATCH spelling is deprecated. Use PARAM_MISMATCH instead"); | ||
return exports.PARAM_MISMATCH; | ||
} | ||
}); | ||
Object.defineProperty(exports, "generateError", { | ||
@@ -12,0 +20,0 @@ value: function (code, message, extra) { |
@@ -13,6 +13,4 @@ var orm = require("./ORM"); | ||
orm.connect(uri, function (err, db) { | ||
_pending -= 1; | ||
if (err) { | ||
if (typeof opts.error == "function") { | ||
if (typeof opts.error === "function") { | ||
opts.error(err); | ||
@@ -33,3 +31,10 @@ } else { | ||
} | ||
if (typeof opts.define == "function") { | ||
if (typeof opts.define === "function") { | ||
if (opts.define.length > 2) { | ||
return opts.define(db, _models, function () { | ||
return checkRequestQueue(); | ||
}); | ||
} | ||
opts.define(db, _models); | ||
@@ -41,3 +46,3 @@ } | ||
return function ORM(req, res, next) { | ||
return function ORM_ExpressMiddleware(req, res, next) { | ||
if (!req.hasOwnProperty("models")) { | ||
@@ -58,2 +63,4 @@ req.models = _models; | ||
function checkRequestQueue() { | ||
_pending -= 1; | ||
if (_pending > 0) return; | ||
@@ -60,0 +67,0 @@ if (_queue.length === 0) return; |
@@ -6,3 +6,3 @@ exports.trigger = function () { | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
cb.apply(self, args); | ||
@@ -20,3 +20,3 @@ } | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
cb.apply(self, args); | ||
@@ -23,0 +23,0 @@ |
@@ -31,2 +31,3 @@ var Property = require("./Property"); | ||
Hook.wait(instance, opts.hooks.beforeValidation, function (err) { | ||
var k, i; | ||
if (err) { | ||
@@ -36,12 +37,6 @@ return saveError(cb, err); | ||
for (var k in Model.properties) { | ||
if (!Model.properties.hasOwnProperty(k)) continue; | ||
if (opts.data[k] == null && Model.properties[k].hasOwnProperty("defaultValue")) { | ||
opts.data[k] = Model.properties[k].defaultValue; | ||
} | ||
} | ||
for (var i = 0; i < opts.one_associations.length; i++) { | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
for (k in opts.one_associations[i].field) { | ||
if (opts.one_associations[i].required && opts.data[k] == null) { | ||
var err = new Error("Property required"); | ||
if (opts.one_associations[i].required && opts.data[k] === null) { | ||
var err = new Error("Property required"); | ||
@@ -52,2 +47,3 @@ err.field = k; | ||
err.type = "validation"; | ||
err.model = Model.table; | ||
@@ -67,8 +63,10 @@ if (!Model.settings.get("instance.returnAllErrors")) { | ||
for (var k in opts.validations) { | ||
for (k in opts.validations) { | ||
required = false; | ||
if (Model.properties[k]) { | ||
required = Model.properties[k].required; | ||
} else { | ||
for (var i = 0; i < opts.one_associations.length; i++) { | ||
if (opts.one_associations[i].field == k) { | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
if (opts.one_associations[i].field === k) { | ||
required = opts.one_associations[i].required; | ||
@@ -82,3 +80,3 @@ break; | ||
} | ||
for (var i = 0; i < opts.validations[k].length; i++) { | ||
for (i = 0; i < opts.validations[k].length; i++) { | ||
checks.add(k, opts.validations[k][i]); | ||
@@ -88,3 +86,5 @@ } | ||
checks.context("instance", instance); | ||
checks.context("model", Model); | ||
checks.context("driver", opts.driver); | ||
@@ -97,3 +97,3 @@ return checks.check(instance, cb); | ||
Hook.trigger(instance, opts.hooks.afterSave, false); | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
cb(err, instance); | ||
@@ -123,5 +123,8 @@ } | ||
if (opts.changes.length === 0) { | ||
return saveAssociations(function (err) { | ||
return afterSave(cb, false, err); | ||
}); | ||
if (saveOptions.saveAssociations === false) { | ||
return saveInstanceExtra(cb); | ||
} | ||
return saveAssociations(function (err) { | ||
return afterSave(cb, false, err); | ||
}); | ||
} | ||
@@ -147,10 +150,13 @@ | ||
var getInstanceData = function () { | ||
var data = {}; | ||
var data = {}, prop; | ||
for (var k in opts.data) { | ||
if (!opts.data.hasOwnProperty(k)) continue; | ||
prop = Model.allProperties[k]; | ||
if (Model.properties[k]) { | ||
data[k] = Property.validate(opts.data[k], Model.properties[k]); | ||
if (prop) { | ||
if (prop.type === 'serial' && opts.data[k] == null) continue; | ||
data[k] = Property.validate(opts.data[k], prop); | ||
if (opts.driver.propertyToValue) { | ||
data[k] = opts.driver.propertyToValue(data[k], Model.properties[k]); | ||
data[k] = opts.driver.propertyToValue(data[k], prop); | ||
} | ||
@@ -161,2 +167,3 @@ } else { | ||
} | ||
return data; | ||
@@ -227,3 +234,3 @@ }; | ||
var saveAssociations = function (cb) { | ||
var pending = 0, errored = false, i, j; | ||
var pending = 1, errored = false, i, j; | ||
var saveAssociation = function (accessor, instances) { | ||
@@ -246,44 +253,49 @@ pending += 1; | ||
var _saveOneAssociation = function (assoc) { | ||
if (!instance[assoc.name] || typeof instance[assoc.name] !== "object") return; | ||
if (assoc.reversed) { | ||
// reversed hasOne associations should behave like hasMany | ||
if (!Array.isArray(instance[assoc.name])) { | ||
instance[assoc.name] = [ instance[assoc.name] ]; | ||
} | ||
for (var i = 0; i < instance[assoc.name].length; i++) { | ||
if (!instance[assoc.name][i].isInstance) { | ||
instance[assoc.name][i] = new assoc.model(instance[assoc.name][i]); | ||
} | ||
saveAssociation(assoc.setAccessor, instance[assoc.name][i]); | ||
} | ||
return; | ||
} | ||
if (!instance[assoc.name].isInstance) { | ||
instance[assoc.name] = new assoc.model(instance[assoc.name]); | ||
} | ||
saveAssociation(assoc.setAccessor, instance[assoc.name]); | ||
}; | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
(function (assoc) { | ||
if (!instance[assoc.name] || typeof instance[assoc.name] != "object") return; | ||
if (assoc.reversed) { | ||
// reversed hasOne associations should behave like hasMany | ||
if (!Array.isArray(instance[assoc.name])) { | ||
instance[assoc.name] = [ instance[assoc.name] ]; | ||
} | ||
for (var i = 0; i < instance[assoc.name].length; i++) { | ||
if (!instance[assoc.name][i].isInstance) { | ||
instance[assoc.name][i] = new assoc.model(instance[assoc.name][i]); | ||
} | ||
saveAssociation(assoc.setAccessor, instance[assoc.name][i]); | ||
} | ||
return; | ||
} | ||
if (!instance[assoc.name].isInstance) { | ||
instance[assoc.name] = new assoc.model(instance[assoc.name]); | ||
} | ||
saveAssociation(assoc.setAccessor, instance[assoc.name]); | ||
})(opts.one_associations[i]); | ||
_saveOneAssociation(opts.one_associations[i]); | ||
} | ||
for (i = 0; i < opts.many_associations.length; i++) { | ||
(function (assoc) { | ||
if (!instance.hasOwnProperty(assoc.name)) return; | ||
if (!Array.isArray(instance[assoc.name])) { | ||
instance[assoc.name] = [ instance[assoc.name] ]; | ||
} | ||
for (j = 0; j < instance[assoc.name].length; j++) { | ||
if (!instance[assoc.name][j].isInstance) { | ||
instance[assoc.name][j] = new assoc.model(instance[assoc.name][j]); | ||
} | ||
} | ||
var _saveManyAssociation = function (assoc) { | ||
if (!instance.hasOwnProperty(assoc.name)) return; | ||
if (!Array.isArray(instance[assoc.name])) { | ||
instance[assoc.name] = [ instance[assoc.name] ]; | ||
} | ||
return saveAssociation(assoc.setAccessor, instance[assoc.name]); | ||
})(opts.many_associations[i]); | ||
for (j = 0; j < instance[assoc.name].length; j++) { | ||
if (!instance[assoc.name][j].isInstance) { | ||
instance[assoc.name][j] = new assoc.model(instance[assoc.name][j]); | ||
} | ||
} | ||
return saveAssociation(assoc.setAccessor, instance[assoc.name]); | ||
}; | ||
for (i = 0; i < opts.many_associations.length; i++) { | ||
_saveManyAssociation(opts.many_associations[i]); | ||
} | ||
if (pending === 0) { | ||
if (--pending === 0) { | ||
return cb(); | ||
@@ -314,3 +326,3 @@ } | ||
for (i = 0; i < opts.extra_info.id; i++) { | ||
for (i = 0; i < opts.extra_info.id.length; i++) { | ||
conditions[opts.extra_info.id_prop[i]] = opts.extra_info.id[i]; | ||
@@ -337,3 +349,3 @@ conditions[opts.extra_info.assoc_prop[i]] = opts.data[opts.id[i]]; | ||
emitEvent("remove", err, instance); | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
cb(err, instance); | ||
@@ -351,3 +363,3 @@ } | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
cb(err, instance); | ||
@@ -391,5 +403,34 @@ } | ||
}; | ||
var setInstanceProperty = function (key, value) { | ||
var prop = Model.allProperties[key] || opts.extra[key]; | ||
if (prop) { | ||
if ('valueToProperty' in opts.driver) { | ||
value = opts.driver.valueToProperty(value, prop); | ||
} | ||
if (opts.data[key] !== value) { | ||
opts.data[key] = value; | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
var addInstanceProperty = function (key) { | ||
// if (instance.hasOwnProperty(key)) return; | ||
var defaultValue = null; | ||
var prop = Model.allProperties[key]; | ||
// This code was first added, and then commented out in a later commit. | ||
// Its presence doesn't affect tests, so I'm just gonna log if it ever gets called. | ||
// If someone complains about noise, we know it does something, and figure it out then. | ||
if (instance.hasOwnProperty(key)) console.log("Overwriting instance property"); | ||
if (key in opts.data) { | ||
defaultValue = opts.data[key]; | ||
} else if (prop && 'defaultValue' in prop) { | ||
defaultValue = prop.defaultValue; | ||
} | ||
setInstanceProperty(key, defaultValue); | ||
Object.defineProperty(instance, key, { | ||
@@ -400,12 +441,13 @@ get: function () { | ||
set: function (val) { | ||
if (opts.id.indexOf(key) >= 0 && opts.data.hasOwnProperty(key)) { | ||
return; | ||
if (Model.allProperties[key].key === true && opts.data[key] != null) { | ||
return; | ||
} | ||
if (opts.data[key] === val) return; | ||
opts.data[key] = val; | ||
if (!setInstanceProperty(key, val)) { | ||
return; | ||
} | ||
if (opts.autoSave) { | ||
saveInstanceProperty(key, val); | ||
} else if (opts.changes.indexOf(key) == -1) { | ||
} else if (opts.changes.indexOf(key) === -1) { | ||
opts.changes.push(key); | ||
@@ -426,7 +468,7 @@ } | ||
set: function (val) { | ||
opts.data[key] = val; | ||
setInstanceProperty(key, val); | ||
/*if (opts.autoSave) { | ||
saveInstanceProperty(key, val); | ||
}*/if (opts.extrachanges.indexOf(key) == -1) { | ||
}*/if (opts.extrachanges.indexOf(key) === -1) { | ||
opts.extrachanges.push(key); | ||
@@ -439,30 +481,8 @@ } | ||
for (var i = 0; i < opts.id.length; i++) { | ||
if (!opts.data.hasOwnProperty(opts.id[i])) { | ||
addInstanceProperty(opts.id[i]); | ||
} | ||
} | ||
var i, k; | ||
for (var k in Model.properties) { | ||
if (Model.properties.hasOwnProperty(k) && !opts.data.hasOwnProperty(k) && opts.id.indexOf(k) == -1) { | ||
opts.data[k] = null; | ||
} | ||
for (k in Model.allProperties) { | ||
addInstanceProperty(k); | ||
} | ||
for (k in opts.data) { | ||
if (!opts.data.hasOwnProperty(k)) continue; | ||
if (!Model.properties.hasOwnProperty(k) && opts.id.indexOf(k) == -1 && opts.association_properties.indexOf(k) == -1) { | ||
if (!opts.extra.hasOwnProperty(k)) continue; | ||
if (opts.driver.valueToProperty) { | ||
opts.data[k] = opts.driver.valueToProperty(opts.data[k], opts.extra[k]); | ||
} | ||
addInstanceExtraProperty(k); | ||
continue; | ||
} | ||
if (Model.properties[k] && opts.driver.valueToProperty) { | ||
opts.data[k] = opts.driver.valueToProperty(opts.data[k], Model.properties[k]); | ||
} | ||
for (k in opts.extra) { | ||
addInstanceProperty(k); | ||
@@ -478,2 +498,6 @@ } | ||
for (k in opts.extra) { | ||
addInstanceExtraProperty(k); | ||
} | ||
Object.defineProperty(instance, "on", { | ||
@@ -513,3 +537,5 @@ value: function (event, cb) { | ||
default: | ||
throw new Error("Unknown parameter type '" + (typeof arg) + "' in Instance.save()"); | ||
var err = new Error("Unknown parameter type '" + (typeof arg) + "' in Instance.save()"); | ||
err.model = Model.table; | ||
throw err; | ||
} | ||
@@ -574,4 +600,10 @@ } | ||
}); | ||
Object.defineProperty(instance, "model", { | ||
value: function (cb) { | ||
return Model; | ||
}, | ||
enumerable: false | ||
}); | ||
for (var i = 0; i < opts.id.length; i++) { | ||
for (i = 0; i < opts.id.length; i++) { | ||
if (!opts.data.hasOwnProperty(opts.id[i])) { | ||
@@ -583,3 +615,3 @@ opts.changes = Object.keys(opts.data); | ||
for (i = 0; i < opts.one_associations.length; i++) { | ||
var asc = opts.one_associations[i] | ||
var asc = opts.one_associations[i]; | ||
@@ -623,5 +655,5 @@ if (!asc.reversed && !asc.extension) { | ||
Hook.wait(instance, opts.hooks.afterLoad, function () { | ||
Hook.wait(instance, opts.hooks.afterLoad, function (err) { | ||
process.nextTick(function () { | ||
emitEvent("ready"); | ||
emitEvent("ready", err); | ||
}); | ||
@@ -628,0 +660,0 @@ }); |
240
lib/Model.js
@@ -28,3 +28,3 @@ var ChainFind = require("./ChainFind"); | ||
if (!Array.isArray(opts.id)) { | ||
opts.id = [opts.id]; | ||
opts.id = [ opts.id ]; | ||
} | ||
@@ -37,10 +37,7 @@ | ||
var model_fields = []; | ||
var allProperties = {}; | ||
for (var i = 0; i < opts.id.length; i++) { | ||
model_fields.push(opts.id[i]); | ||
} | ||
var createHookHelper = function (hook) { | ||
return function (cb) { | ||
if (typeof cb != "function") { | ||
if (typeof cb !== "function") { | ||
delete opts.hooks[hook]; | ||
@@ -61,3 +58,3 @@ } else { | ||
for (k in data) { | ||
if (k == "extra_field") continue; | ||
if (k === "extra_field") continue; | ||
if (opts.properties.hasOwnProperty(k)) continue; | ||
@@ -69,3 +66,3 @@ if (inst_opts.extra && inst_opts.extra.hasOwnProperty(k)) continue; | ||
for (i = 0; i < one_associations.length; i++) { | ||
if (one_associations[i].name == k) { | ||
if (one_associations[i].name === k) { | ||
found_assoc = true; | ||
@@ -77,3 +74,3 @@ break; | ||
for (i = 0; i < many_associations.length; i++) { | ||
if (many_associations[i].name == k) { | ||
if (many_associations[i].name === k) { | ||
found_assoc = true; | ||
@@ -114,6 +111,6 @@ break; | ||
}); | ||
instance.on("ready", function () { | ||
instance.on("ready", function (err) { | ||
if (--pending > 0) return; | ||
if (typeof cb == "function") { | ||
return cb(instance); | ||
if (typeof cb === "function") { | ||
return cb(err, instance); | ||
} | ||
@@ -133,4 +130,4 @@ }); | ||
if (--pending > 0) return; | ||
if (typeof cb == "function") { | ||
return cb(instance); | ||
if (typeof cb === "function") { | ||
return cb(err, instance); | ||
} | ||
@@ -144,59 +141,55 @@ }); | ||
var model = function (data) { | ||
var instance; | ||
var model = function () { | ||
var instance, i; | ||
//TODO: Should be smarter about how this is handled. | ||
// ideally we should check the type of ID used | ||
// by the model, and accept that type of data. | ||
if (typeof data == "number" || typeof data == "string") { | ||
var data2 = {}; | ||
data2[opts.id] = data; | ||
var data = arguments.length > 1 ? arguments : arguments[0]; | ||
return createInstance(data2, { isShell: true }); | ||
} else if (typeof data == "undefined") { | ||
data = {}; | ||
} | ||
if (Array.isArray(opts.id) && Array.isArray(data)) { | ||
if (data.length == opts.id.length) { | ||
var data2 = {}; | ||
for (i = 0; i < opts.id.length; i++) { | ||
data2[opts.id[i]] = data[i++]; | ||
} | ||
var isNew = false; | ||
return createInstance(data2, { isShell: true }); | ||
} | ||
else { | ||
var err = new Error('Model requires ' + opts.id.length + ' keys, only ' + data.length + ' were provided'); | ||
err.model = opts.table; | ||
for (var k in opts.id) { | ||
isNew |= !data.hasOwnProperty(k); | ||
} | ||
throw err; | ||
} | ||
} | ||
else if (typeof data === "number" || typeof data === "string") { | ||
var data2 = {}; | ||
data2[opts.id[0]] = data; | ||
return createInstance(data, { | ||
is_new : isNew, | ||
autoSave : opts.autoSave, | ||
cascadeRemove : opts.cascadeRemove | ||
}); | ||
}; | ||
return createInstance(data2, { isShell: true }); | ||
} else if (typeof data === "undefined") { | ||
data = {}; | ||
} | ||
// Standardize validations | ||
for (var k in opts.validations) { | ||
if (typeof opts.validations[k] == 'function') { | ||
opts.validations[k] = [ opts.validations[k] ]; | ||
} | ||
} | ||
var isNew = false; | ||
for (k in opts.properties) { | ||
opts.properties[k] = Property.normalize(opts.properties[k], opts.settings); | ||
for (i = 0; i < opts.id.length; i++) { | ||
if (!data.hasOwnProperty(opts.id[i])) { | ||
isNew = true; | ||
break; | ||
} | ||
} | ||
if (opts.properties[k].lazyload !== true) { | ||
model_fields.push(k); | ||
} | ||
if (opts.properties[k].required) { | ||
// Prepend `required` validation | ||
if(opts.validations.hasOwnProperty(k)) { | ||
opts.validations[k].splice(0, 0, Validators.required()); | ||
} else { | ||
opts.validations[k] = [Validators.required()]; | ||
} | ||
} | ||
} | ||
if (opts.id.length === 1 && opts.id[0] != 'id') { | ||
isNew = true; //Dubiously assume that it is a new instance if we're using custom keys | ||
} | ||
for (k in AvailableHooks) { | ||
model[AvailableHooks[k]] = createHookHelper(AvailableHooks[k]); | ||
} | ||
return createInstance(data, { | ||
is_new: isNew, | ||
autoSave: opts.autoSave, | ||
cascadeRemove: opts.cascadeRemove | ||
}); | ||
}; | ||
model.properties = opts.properties; | ||
model.settings = opts.settings; | ||
model.allProperties = allProperties; | ||
model.properties = opts.properties; | ||
model.settings = opts.settings; | ||
@@ -207,3 +200,3 @@ model.drop = function (cb) { | ||
} | ||
if (typeof opts.driver.drop == "function") { | ||
if (typeof opts.driver.drop === "function") { | ||
opts.driver.drop({ | ||
@@ -219,3 +212,3 @@ table : opts.table, | ||
return cb(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Driver does not support Model.drop()")); | ||
return cb(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Driver does not support Model.drop()", { model: opts.table })); | ||
}; | ||
@@ -227,3 +220,3 @@ | ||
} | ||
if (typeof opts.driver.sync == "function") { | ||
if (typeof opts.driver.sync === "function") { | ||
try { | ||
@@ -235,3 +228,5 @@ opts.driver.sync({ | ||
properties : opts.properties, | ||
allProperties : allProperties, | ||
indexes : opts.indexes || [], | ||
customTypes : opts.db.customTypes, | ||
one_associations : one_associations, | ||
@@ -248,3 +243,3 @@ many_associations : many_associations, | ||
return cb(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Driver does not support Model.sync()")); | ||
return cb(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Driver does not support Model.sync()", { model: opts.table })); | ||
}; | ||
@@ -258,16 +253,16 @@ | ||
if (typeof cb != "function") { | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.get() callback"); | ||
if (typeof cb !== "function") { | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.get() callback", { model: opts.table }); | ||
} | ||
if (typeof ids[ids.length - 1] == "object" && !Array.isArray(ids[ids.length - 1])) { | ||
if (typeof ids[ids.length - 1] === "object" && !Array.isArray(ids[ids.length - 1])) { | ||
options = ids.pop(); | ||
} | ||
if (ids.length == 1 && Array.isArray(ids[0])) { | ||
if (ids.length === 1 && Array.isArray(ids[0])) { | ||
ids = ids[0]; | ||
} | ||
if (ids.length != opts.id.length) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "Model.get() IDs number missmatch (" + opts.id.length + " needed, " + ids.length + " passed)"); | ||
if (ids.length !== opts.id.length) { | ||
throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "Model.get() IDs number mismatch (" + opts.id.length + " needed, " + ids.length + " passed)", { model: opts.table }); | ||
} | ||
@@ -294,3 +289,3 @@ | ||
if (data.length === 0) { | ||
return cb(ErrorCodes.generateError(ErrorCodes.NOT_FOUND, "Not found")); | ||
return cb(ErrorCodes.generateError(ErrorCodes.NOT_FOUND, "Not found", { model: opts.table })); | ||
} | ||
@@ -311,5 +306,3 @@ | ||
}, cb); | ||
}, function (instance) { | ||
return cb(null, instance); | ||
}); | ||
}, cb); | ||
}); | ||
@@ -362,3 +355,3 @@ | ||
case "string": | ||
if (arguments[i][0] == "-") { | ||
if (arguments[i][0] === "-") { | ||
order = [ arguments[i].substr(1), "Z" ]; | ||
@@ -385,2 +378,5 @@ } else { | ||
} | ||
if (conditions) { | ||
conditions = Utilities.checkConditions(conditions, one_associations); | ||
} | ||
@@ -417,9 +413,7 @@ var chain = new ChainFind(model, { | ||
}, cb); | ||
}, function (instance) { | ||
return cb(null, instance); | ||
}); | ||
}, cb); | ||
} | ||
}); | ||
if (typeof cb != "function") { | ||
if (typeof cb !== "function") { | ||
return chain; | ||
@@ -441,3 +435,3 @@ } | ||
for (var i = 0; i < args.length; i++) { | ||
if (typeof args[i] == "function") { | ||
if (typeof args[i] === "function") { | ||
cb = args.splice(i, 1)[0]; | ||
@@ -449,3 +443,3 @@ break; | ||
if (cb === null) { | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.one() callback"); | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.one() callback", { model: opts.table }); | ||
} | ||
@@ -480,6 +474,10 @@ | ||
if (typeof cb != "function") { | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.count() callback"); | ||
if (typeof cb !== "function") { | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.count() callback", { model: opts.table }); | ||
} | ||
if (conditions) { | ||
conditions = Utilities.checkConditions(conditions, one_associations); | ||
} | ||
opts.driver.count(opts.table, conditions, {}, function (err, data) { | ||
@@ -499,3 +497,3 @@ if (err || data.length === 0) { | ||
for (var i = 0; i < arguments.length; i++) { | ||
if (typeof arguments[i] == "object") { | ||
if (typeof arguments[i] === "object") { | ||
if (Array.isArray(arguments[i])) { | ||
@@ -509,2 +507,6 @@ properties = arguments[i]; | ||
if (conditions) { | ||
conditions = Utilities.checkConditions(conditions, one_associations); | ||
} | ||
return new require("./AggregateFunctions")({ | ||
@@ -523,4 +525,4 @@ table : opts.table, | ||
if (typeof cb != "function") { | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.exists() callback"); | ||
if (typeof cb !== "function") { | ||
throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.exists() callback", { model: opts.table }); | ||
} | ||
@@ -530,3 +532,3 @@ | ||
if (ids.length === 1 && typeof ids[0] == "object") { | ||
if (ids.length === 1 && typeof ids[0] === "object") { | ||
if (Array.isArray(ids[0])) { | ||
@@ -545,2 +547,6 @@ for (i = 0; i < opts.id.length; i++) { | ||
if (conditions) { | ||
conditions = Utilities.checkConditions(conditions, one_associations); | ||
} | ||
opts.driver.count(opts.table, conditions, {}, function (err, data) { | ||
@@ -568,3 +574,8 @@ if (err || data.length === 0) { | ||
autoFetch : false | ||
}, function () { | ||
}, function (err) { | ||
if (err) { | ||
err.index = idx; | ||
err.instance = Instances[idx]; | ||
return cb(err); | ||
} | ||
Instances[idx].save(function (err) { | ||
@@ -589,3 +600,3 @@ if (err) { | ||
Instances = Instances.concat(arguments[i]); | ||
} else if (i == 0) { | ||
} else if (i === 0) { | ||
single = true; | ||
@@ -610,3 +621,3 @@ Instances.push(arguments[i]); | ||
opts.driver.clear(opts.table, function (err) { | ||
if (typeof cb == "function") cb(err); | ||
if (typeof cb === "function") cb(err); | ||
}); | ||
@@ -629,3 +640,50 @@ | ||
}); | ||
Object.defineProperty(model, "uid", { | ||
value: opts.driver.uid + "/" + opts.table + "/" + opts.id.join("/"), | ||
enumerable: false | ||
}); | ||
// Standardize validations | ||
for (var k in opts.validations) { | ||
if (!Array.isArray(opts.validations[k])) { | ||
opts.validations[k] = [ opts.validations[k] ]; | ||
} | ||
} | ||
// standardize properties | ||
for (k in opts.properties) { | ||
opts.properties[k] = Property.normalize(opts.properties[k], opts.db.customTypes, opts.settings); | ||
opts.properties[k].klass = 'primary'; | ||
allProperties[k] = opts.properties[k]; | ||
if (opts.id.indexOf(k) != -1) { | ||
opts.properties[k].key = true; | ||
} | ||
if (opts.properties[k].lazyload !== true && model_fields.indexOf(k) == -1) { | ||
model_fields.push(k); | ||
} | ||
if (opts.properties[k].required) { | ||
// Prepend `required` validation | ||
if(opts.validations.hasOwnProperty(k)) { | ||
opts.validations[k].splice(0, 0, Validators.required()); | ||
} else { | ||
opts.validations[k] = [Validators.required()]; | ||
} | ||
} | ||
} | ||
for (var i = 0; i < opts.id.length; i++) { | ||
k = opts.id[i]; | ||
allProperties[k] = opts.properties[k] || { | ||
type: 'serial', rational: 'false', key: true, klass: 'key' | ||
}; | ||
} | ||
model_fields = opts.id.concat(model_fields); | ||
// setup hooks | ||
for (k in AvailableHooks) { | ||
model[AvailableHooks[k]] = createHookHelper(AvailableHooks[k]); | ||
} | ||
OneAssociation.prepare(model, one_associations, association_properties, model_fields); | ||
@@ -632,0 +690,0 @@ ManyAssociation.prepare(model, many_associations); |
@@ -8,2 +8,3 @@ var util = require("util"); | ||
var enforce = require("enforce"); | ||
var _ = require("lodash"); | ||
@@ -46,3 +47,3 @@ var Model = require("./Model").Model; | ||
} | ||
if (typeof opts == "function") { | ||
if (typeof opts === "function") { | ||
cb = opts; | ||
@@ -56,3 +57,3 @@ opts = {}; | ||
var driver = new Driver(null, connection, { | ||
debug : (opts.query && opts.query.debug == 'true'), | ||
debug : (opts.query && opts.query.debug === 'true'), | ||
settings : settings | ||
@@ -69,9 +70,12 @@ }); | ||
if (arguments.length === 0 || !opts) { | ||
return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "CONNECTION_URL_EMPTY"), cb); | ||
return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "CONNECTION_URL_EMPTY"), cb); | ||
} | ||
if (typeof opts == "string") { | ||
if (typeof opts === "string") { | ||
if (opts.replace(/\s+/, "").length === 0) { | ||
return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "CONNECTION_URL_EMPTY"), cb); | ||
return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "CONNECTION_URL_EMPTY"), cb); | ||
} | ||
opts = url.parse(opts, true); | ||
for(var k in opts.query) { | ||
opts[k] = opts.query[k]; | ||
} | ||
} | ||
@@ -85,3 +89,3 @@ if (!opts.database) { | ||
if (!opts.protocol) { | ||
return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISSMATCH, "CONNECTION_URL_NO_PROTOCOL"), cb); | ||
return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "CONNECTION_URL_NO_PROTOCOL"), cb); | ||
} | ||
@@ -110,8 +114,8 @@ // if (!opts.host) { | ||
var Driver = require("./Drivers/DML/" + proto).Driver; | ||
var debug = Boolean(extractOption(opts, "debug")); | ||
var pool = Boolean(extractOption(opts, "pool")); | ||
var settings = new Settings.Container(exports.settings.get('*')); | ||
var debug = extractOption(opts, "debug"); | ||
var pool = extractOption(opts, "pool"); | ||
var driver = new Driver(opts, null, { | ||
debug : debug, | ||
pool : pool, | ||
debug : (debug !== null ? Boolean(debug) : settings.get("connection.debug")), | ||
pool : (pool !== null ? Boolean(pool) : settings.get("connection.pool")), | ||
settings : settings | ||
@@ -123,3 +127,3 @@ }); | ||
driver.connect(function (err) { | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
if (err) { | ||
@@ -135,3 +139,3 @@ return cb(err); | ||
} catch (ex) { | ||
if (ex.code == "MODULE_NOT_FOUND" || ex.message.indexOf('find module')) { | ||
if (ex.code === "MODULE_NOT_FOUND" || ex.message.indexOf('find module')) { | ||
return ORM_Error(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "CONNECTION_PROTOCOL_NOT_SUPPORTED"), cb); | ||
@@ -155,2 +159,3 @@ } | ||
this.plugins = []; | ||
this.customTypes = {}; | ||
@@ -165,3 +170,3 @@ for (var k in Query.Comparators) { | ||
if (this.settings.get("connection.reconnect")) { | ||
if (typeof this.driver.reconnect == "undefined") { | ||
if (typeof this.driver.reconnect === "undefined") { | ||
return this.emit("error", ErrorCodes.generateError(ErrorCodes.CONNECTION_LOST, "Connection lost - driver does not support reconnection")); | ||
@@ -188,3 +193,3 @@ } | ||
ORM.prototype.use = function (plugin_const, opts) { | ||
if (typeof plugin_const == "string") { | ||
if (typeof plugin_const === "string") { | ||
try { | ||
@@ -199,3 +204,3 @@ plugin_const = require(Utilities.getRealPath(plugin_const)); | ||
if (typeof plugin.define == "function") { | ||
if (typeof plugin.define === "function") { | ||
for (var k in this.models) { | ||
@@ -211,7 +216,9 @@ plugin.define(this.models[k]); | ||
ORM.prototype.define = function (name, properties, opts) { | ||
var i; | ||
properties = properties || {}; | ||
opts = opts || {}; | ||
for (var i = 0; i < this.plugins.length; i++) { | ||
if (typeof this.plugins[i].beforeDefine == "function") { | ||
for (i = 0; i < this.plugins.length; i++) { | ||
if (typeof this.plugins[i].beforeDefine === "function") { | ||
this.plugins[i].beforeDefine(name, properties, opts); | ||
@@ -241,4 +248,4 @@ } | ||
for (var i = 0; i < this.plugins.length; i++) { | ||
if (typeof this.plugins[i].define == "function") { | ||
for (i = 0; i < this.plugins.length; i++) { | ||
if (typeof this.plugins[i].define === "function") { | ||
this.plugins[i].define(this.models[name], this); | ||
@@ -250,2 +257,7 @@ } | ||
}; | ||
ORM.prototype.defineType = function (name, opts) { | ||
this.customTypes[name] = opts; | ||
this.driver.customTypes[name] = opts; | ||
return this; | ||
} | ||
ORM.prototype.ping = function (cb) { | ||
@@ -261,8 +273,29 @@ this.driver.ping(cb); | ||
}; | ||
ORM.prototype.load = function (file, cb) { | ||
try { | ||
return require(Utilities.getRealPath(file))(this, cb); | ||
} catch (ex) { | ||
return cb(ex); | ||
ORM.prototype.load = function () { | ||
var files = _.flatten(Array.prototype.slice.apply(arguments)); | ||
var cb = function () {}; | ||
if (typeof files[files.length - 1] == "function") { | ||
cb = files.pop(); | ||
} | ||
var loadNext = function () { | ||
if (files.length === 0) { | ||
return cb(); | ||
} | ||
var file = files.shift(); | ||
try { | ||
return require(Utilities.getRealPath(file, 4))(this, function (err) { | ||
if (err) return cb(err); | ||
return loadNext(); | ||
}); | ||
} catch (ex) { | ||
return cb(ex); | ||
} | ||
}.bind(this); | ||
return loadNext(); | ||
}; | ||
@@ -360,3 +393,3 @@ ORM.prototype.sync = function (cb) { | ||
if (typeof cb == "function") { | ||
if (typeof cb === "function") { | ||
cb(err); | ||
@@ -363,0 +396,0 @@ } |
var ErrorCodes = require("./ErrorCodes"); | ||
exports.normalize = function (prop, Settings) { | ||
if (typeof prop == "function") { | ||
exports.normalize = function (prop, customTypes, Settings) { | ||
if (typeof prop === "function") { | ||
switch (prop.name) { | ||
@@ -25,3 +25,3 @@ case "String": | ||
} | ||
} else if (typeof prop == "string") { | ||
} else if (typeof prop === "string") { | ||
var tmp = prop; | ||
@@ -34,4 +34,6 @@ prop = {}; | ||
if ([ "text", "number", "boolean", "date", "enum", "object", "binary", "point" ].indexOf(prop.type) == -1) { | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: " + prop.type); | ||
if ([ "text", "number", "boolean", "date", "enum", "object", "binary", "point" ].indexOf(prop.type) === -1) { | ||
if (!(prop.type in customTypes)) { | ||
throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: " + prop.type); | ||
} | ||
} | ||
@@ -38,0 +40,0 @@ |
@@ -18,3 +18,5 @@ var _ = require('lodash'); | ||
connection : { | ||
reconnect : true | ||
reconnect : true, | ||
poll : false, | ||
debug : false | ||
} | ||
@@ -42,3 +44,3 @@ }; | ||
for (var i = 0; i < arguments.length; i++) { | ||
if (typeof arguments[i] == "string") { | ||
if (typeof arguments[i] === "string") { | ||
unset(arguments[i], settings); | ||
@@ -56,3 +58,3 @@ } | ||
if (p == -1) { | ||
if (p === -1) { | ||
return obj[key] = value; | ||
@@ -71,4 +73,4 @@ } | ||
if (p == -1) { | ||
if (key == '*') { | ||
if (p === -1) { | ||
if (key === '*') { | ||
return obj; | ||
@@ -89,4 +91,4 @@ } | ||
if (p == -1) { | ||
if (key == '*') { | ||
if (p === -1) { | ||
if (key === '*') { | ||
return 'reset'; | ||
@@ -103,5 +105,5 @@ } else { | ||
if (unset(key.substr(p + 1), obj[key.substr(0, p)]) == 'reset') { | ||
if (unset(key.substr(p + 1), obj[key.substr(0, p)]) === 'reset') { | ||
obj[key.substr(0, p)] = {}; | ||
} | ||
} |
var map = {}; | ||
exports.clear = function (key) { | ||
if (typeof key == "string") { | ||
if (typeof key === "string") { | ||
delete map[key]; | ||
@@ -17,3 +17,3 @@ } else { | ||
if (map.hasOwnProperty(key)) { | ||
if (opts && opts.save_check && typeof map[key].o.saved == "function" && !map[key].o.saved()) { | ||
if (opts && opts.save_check && typeof map[key].o.saved === "function" && !map[key].o.saved()) { | ||
// if not saved, don't return it, fetch original from db | ||
@@ -24,13 +24,15 @@ return createCb(returnCb); | ||
} else { | ||
return returnCb(map[key].o); | ||
return returnCb(null, map[key].o); | ||
} | ||
} | ||
createCb(function (value) { | ||
createCb(function (err, value) { | ||
if (err) return returnCb(err); | ||
map[key] = { // object , timeout | ||
o : value, | ||
t : (opts && typeof opts.cache == "number" ? Date.now() + (opts.cache * 1000) : null) | ||
t : (opts && typeof opts.cache === "number" ? Date.now() + (opts.cache * 1000) : null) | ||
}; | ||
return returnCb(map[key].o); | ||
return returnCb(null, map[key].o); | ||
}); | ||
}; |
@@ -19,4 +19,4 @@ /** | ||
exports.standardizeOrder = function (order) { | ||
if (typeof order == "string") { | ||
if (order[0] == "-") { | ||
if (typeof order === "string") { | ||
if (order[0] === "-") { | ||
return [ [ order.substr(1), "Z" ] ]; | ||
@@ -30,3 +30,3 @@ } | ||
for (var i = 0; i < order.length; i++) { | ||
minus = (order[i][0] == "-"); | ||
minus = (order[i][0] === "-"); | ||
@@ -50,2 +50,62 @@ if (i < order.length - 1 && [ "A", "Z" ].indexOf(order[i + 1].toUpperCase()) >= 0) { | ||
/** | ||
* Operations | ||
* A) Build an index of associations, with their name as the key | ||
* B) Check for any conditions with a key in the association index | ||
* C) Ensure that our condition supports array values | ||
* D) Remove original condition (not DB compatible) | ||
* E) Convert our association fields into an array, indexes are the same as model.id | ||
* F) Itterate through values for the condition, only accept instances of the same type as the association | ||
*/ | ||
exports.checkConditions = function (conditions, one_associations) { | ||
var k, i, j; | ||
// A) | ||
var associations = {}; | ||
for (i = 0; i < one_associations.length; i++) { | ||
associations[one_associations[i].name] = one_associations[i]; | ||
} | ||
for (k in conditions) { | ||
// B) | ||
if (!associations.hasOwnProperty(k)) continue; | ||
// C) | ||
var values = conditions[k]; | ||
if (!Array.isArray(values)) values = [values]; | ||
// D) | ||
delete conditions[k]; | ||
// E) | ||
var association_fields = Object.keys(associations[k].field); | ||
var model = associations[k].model; | ||
// F) | ||
for (i = 0; i < values.length; i++) { | ||
if (values[i].isInstance && values[i].model().uid === model.uid) { | ||
if (association_fields.length === 1) { | ||
if (typeof conditions[association_fields[0]] === 'undefined') { | ||
conditions[association_fields[0]] = values[i][model.id[0]]; | ||
} else if(Array.isArray(conditions[association_fields[0]])) { | ||
conditions[association_fields[0]].push(values[i][model.id[0]]); | ||
} else { | ||
conditions[association_fields[0]] = [conditions[association_fields[0]], values[i][model.id[0]]]; | ||
} | ||
} else { | ||
var _conds = {}; | ||
for (j = 0; j < association_fields.length; i++) { | ||
_conds[association_fields[j]] = values[i][model.id[j]]; | ||
} | ||
conditions.or = conditions.or || []; | ||
conditions.or.push(_conds); | ||
} | ||
} | ||
} | ||
} | ||
return conditions; | ||
}; | ||
/** | ||
* Gets all the values within an object or array, optionally | ||
@@ -84,3 +144,3 @@ * using a keys array to get only specific values | ||
for (var i = 0; i < model.id.length; i++) { | ||
if (typeof target[fields[i]] == 'undefined' || overwrite !== false) { | ||
if (typeof target[fields[i]] === 'undefined' || overwrite !== false) { | ||
target[fields[i]] = source[model.id[i]]; | ||
@@ -93,3 +153,3 @@ } else if (Array.isArray(target[fields[i]])) { | ||
} | ||
} | ||
}; | ||
@@ -102,3 +162,3 @@ exports.getConditions = function (model, fields, from) { | ||
return conditions; | ||
} | ||
}; | ||
@@ -109,7 +169,7 @@ exports.wrapFieldObject = function (obj, model, altName, alternatives) { | ||
if (typeof assoc_key == "function") { | ||
obj = assoc_key(altName.toLowerCase(), 'id'); | ||
if (typeof assoc_key === "function") { | ||
obj = assoc_key(altName.toLowerCase(), model.id[0]); | ||
} else { | ||
obj = assoc_key.replace("{name}", altName.toLowerCase()) | ||
.replace("{field}", 'id'); | ||
.replace("{field}", model.id[0]); | ||
} | ||
@@ -139,3 +199,3 @@ } | ||
field_name = keys[i]; | ||
} else if (typeof assoc_key == "function") { | ||
} else if (typeof assoc_key === "function") { | ||
field_name = assoc_key(name.toLowerCase(), keys[i]); | ||
@@ -180,3 +240,3 @@ } else { | ||
var err = new Error(); | ||
var tmp = err.stack.split(/\r?\n/)[typeof stack_index != "undefined" ? stack_index : 3], m; | ||
var tmp = err.stack.split(/\r?\n/)[typeof stack_index !== "undefined" ? stack_index : 3], m; | ||
@@ -191,6 +251,6 @@ if ((m = tmp.match(/^\s*at\s+(.+):\d+:\d+$/)) !== null) { | ||
if (path_str[0] != path.sep) { | ||
if (path_str[0] !== path.sep) { | ||
path_str = cwd + "/" + path_str; | ||
} | ||
if (path_str.substr(-1) == path.sep) { | ||
if (path_str.substr(-1) === path.sep) { | ||
path_str += "index"; | ||
@@ -197,0 +257,0 @@ } |
var enforce = require("enforce"); | ||
var util = require("util"); | ||
var validators = { | ||
@@ -27,3 +27,3 @@ required : enforce.required, | ||
return function (v, next, ctx) { | ||
if (v == this[name]) return next(); | ||
if (v === this[name]) return next(); | ||
return next(msg || 'not-equal-to-property'); | ||
@@ -35,13 +35,50 @@ }; | ||
* Check if a property is unique in the collection. | ||
* This can take a while because a query has to be | ||
* made against the Model, but if you use this | ||
* always you should not have not unique values | ||
* on this property so this should not worry you. | ||
* This can take a while because a query has to be made against the Model. | ||
* | ||
* Due to the async nature of node, and concurrent web server environments, | ||
* an index on the database column is the only way to gurantee uniqueness. | ||
* | ||
* For sensibility's sake, undefined and null values are ignored for uniqueness | ||
* checks. | ||
* | ||
* Options: | ||
* ignoreCase: for postgres; mysql ignores case by default. | ||
* scope: (Array) scope uniqueness to listed properties | ||
**/ | ||
validators.unique = function (msg) { | ||
validators.unique = function () { | ||
var arg, k; | ||
var msg = null, opts = {}; | ||
for (k in arguments) { | ||
arg = arguments[k]; | ||
if (typeof arg === 'string') msg = arg; | ||
else if (typeof arg === 'object') opts = arg; | ||
} | ||
return function (v, next, ctx) { | ||
var query = {}; | ||
query[ctx.property] = v; | ||
var s, scopeProp; | ||
ctx.model.find(query, function (err, records) { | ||
if (typeof v === 'undefined' || v === null) return next(); | ||
//Cannot process on database engines which don't support SQL syntax | ||
if (!ctx.driver.isSql) return next('not-supported'); | ||
var chain = ctx.model.find(); | ||
var chainQuery = function (prop, value) { | ||
var query = null; | ||
if (opts.ignoreCase === true && ctx.model.properties[prop].type === 'text') { | ||
query = util.format('LOWER(%s.%s) LIKE LOWER(?)', | ||
ctx.model.table, ctx.driver.query.escapeId(prop) | ||
); | ||
chain.where(query, [value]); | ||
} else { | ||
query = {}; | ||
query[prop] = value; | ||
chain.where(query); | ||
} | ||
}; | ||
var handler = function (err, records) { | ||
if (err) { | ||
@@ -53,7 +90,24 @@ return next(); | ||
} | ||
if (records.length == 1 && records[0][ctx.model.id] === this[ctx.model.id]) { | ||
if (records.length === 1 && records[0][ctx.model.id] === this[ctx.model.id]) { | ||
return next(); | ||
} | ||
return next(msg || 'not-unique'); | ||
}.bind(this)); | ||
}.bind(this); | ||
chainQuery(ctx.property, v); | ||
if (opts.scope) { | ||
for (s in opts.scope) { | ||
scopeProp = opts.scope[s]; | ||
// In SQL unique index land, NULL values are not considered equal. | ||
if (typeof ctx.instance[scopeProp] == 'undefined' || ctx.instance[scopeProp] === null) { | ||
return next(); | ||
} | ||
chainQuery(scopeProp, ctx.instance[scopeProp]); | ||
} | ||
} | ||
chain.all(handler); | ||
}; | ||
@@ -60,0 +114,0 @@ }; |
@@ -12,5 +12,6 @@ { | ||
"redshift", | ||
"sqlite" | ||
"sqlite", | ||
"mongodb" | ||
], | ||
"version" : "2.1.0", | ||
"version" : "2.1.1", | ||
"license" : "MIT", | ||
@@ -26,3 +27,3 @@ "homepage" : "http://dresende.github.io/node-orm2", | ||
{ "name" : "David Kosub" }, | ||
{ "name" : "Arek W" }, | ||
{ "name" : "Arek W", "email" : "arek01@gmail.com" }, | ||
{ "name" : "Joseph Gilley", "email" : "joe.gilley@gmail.com" }, | ||
@@ -40,17 +41,17 @@ { "name" : "Benjamin Pannell", "email" : "admin@sierrasoftworks.com" } | ||
"dependencies": { | ||
"enforce" : "0.1.1", | ||
"sql-query" : "0.1.9", | ||
"enforce" : "0.1.2", | ||
"sql-query" : "0.1.15", | ||
"hat" : "0.0.3", | ||
"lodash" : "1.3.1" | ||
"lodash" : "2.0.0" | ||
}, | ||
"devDependencies": { | ||
"mysql" : "2.0.0-alpha7", | ||
"mysql" : "2.0.0-alpha9", | ||
"pg" : "1.0.0", | ||
"sqlite3" : "2.1.7", | ||
"async" : "*", | ||
"mocha" : "1.12.0", | ||
"should" : "1.2.2", | ||
"mongodb" : "1.3.11" | ||
"mocha" : "1.12.1", | ||
"should" : "1.3.0", | ||
"mongodb" : "1.3.19" | ||
}, | ||
"optionalDependencies": {} | ||
} |
253
Readme.md
@@ -94,3 +94,3 @@ ## Object Relational Mapping | ||
app.use(orm.express("mysql://username:password@host/database", { | ||
define: function (db, models) { | ||
define: function (db, models, next) { | ||
models.person = db.define("person", { ... }); | ||
@@ -111,82 +111,14 @@ } | ||
## Settings | ||
## Documentation | ||
Settings are used to store key value pairs. A settings object is stored on the global orm object and on each database connection. | ||
Documentation is moving to the [wiki](https://github.com/dresende/node-orm2/wiki/). | ||
```js | ||
var orm = require("orm"); | ||
## Settings | ||
orm.settings.set("some.deep.value", 123); | ||
See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Settings). | ||
orm.connect("....", function (err, db) { | ||
// db.settings is a snapshot of the settings at the moment | ||
// of orm.connect(). changes to it don't affect orm.settings | ||
console.log(db.settings.get("some.deep.value")); // 123 | ||
console.log(db.settings.get("some.deep")); // { value: 123 } | ||
}); | ||
``` | ||
More in the [wiki](https://github.com/dresende/node-orm2/wiki/Settings). | ||
## Connecting | ||
First, add the correct driver to your `package.json`: | ||
See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Connecting-to-Database). | ||
driver | npm package | version | ||
:----------------------|:---------------------------|:----- | ||
mysql | mysql | 2.0.0-alpha7 | ||
postgres<br/>redshift | pg | ~1.0.0 | ||
sqlite | sqlite3 | 2.1.7 | ||
mongodb | mongodb | 1.3.11 | ||
These are the versions tested. Use others (older or newer) at your own risk. | ||
### Options | ||
You can pass in connection options either as a string: | ||
```js | ||
var orm = require("orm"); | ||
orm.connect("mysql://username:password@host/database?pool=true", function (err, db) { | ||
// ... | ||
}); | ||
``` | ||
**Note:** `pool` is only supported by mysql & postgres. When 'pool' is set to true, your database connections are cached so that connections can be reused, optimizing performance. | ||
**Note:** `strdates` is only supported by sqlite. When true, date fields are saved as strings, compatible with django | ||
Or as an object: | ||
```js | ||
var opts = { | ||
database : "dbname", | ||
protocol : "[mysql|postgres|redshift|sqlite]", | ||
host : "127.0.0.1", | ||
port : 3306, // optional, defaults to database default | ||
user : "..", | ||
password : "..", | ||
query : { | ||
pool : true|false, // optional, false by default | ||
debug : true|false, // optional, false by default | ||
strdates : true|false // optional, false by default | ||
} | ||
}; | ||
orm.connect(opts, function (err, db) { | ||
// ... | ||
}); | ||
``` | ||
You can also avoid passing a callback and just listen for the connect event: | ||
```js | ||
var orm = require("orm"); | ||
var db = orm.connect("mysql://username:password@host/database"); | ||
db.on("connect", function (err, db) { | ||
// ... | ||
}); | ||
``` | ||
## Models | ||
@@ -200,47 +132,8 @@ | ||
Call `define` on the database connection to setup a model. The name of the table and model is used as an identifier for the model on the database connection, so you can easily access the model later using the connection. | ||
See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Defining-Models). | ||
```js | ||
var Person = db.define('person', { // 'person' will be the table in the database as well as the model id | ||
// properties | ||
name : String, // you can use native objects to define the property type | ||
surname : { type: "text", size: 50 } // or you can be specific and define aditional options | ||
}, { | ||
// options (optional) | ||
}); | ||
``` | ||
### Properties | ||
#### Types | ||
See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Model-Properties). | ||
Native | String | Native | String | ||
:--------|:-----------|:---------|:--------- | ||
String | 'text' | Date | 'date ' | ||
Number | 'number' | Object | 'object' | ||
Boolean | 'boolean' | Buffer | 'binary' | ||
| | --- | 'enum' | ||
#### Options | ||
##### [all types] | ||
* `required`: true marks the column as `NOT NULL`, false (default) | ||
* `unique`: true marks the column with a `UNIQUE` index | ||
* `defaultValue`: sets the default value for the field | ||
##### string | ||
* `size`: max length of the string | ||
* `big`: true to make (LONG)TEXT columns instead of VARCHAR(size) | ||
##### number | ||
* `rational`: true (default) creates a FLOAT/REAL, false an INTEGER | ||
* `size`: byte size of number, default is 4. Note that 8 byte numbers [have limitations](http://stackoverflow.com/questions/307179/what-is-javascripts-max-int-whats-the-highest-integer-value-a-number-can-go-t) | ||
* `unsigned`: true to make INTEGER unsigned, default is false | ||
##### date | ||
* `time`: true (default) creates a DATETIME/TIMESTAMP, false a DATE | ||
Note that these may vary accross drivers. | ||
### Instance Methods | ||
@@ -390,41 +283,4 @@ | ||
If you want to listen for a type of event than occurs in instances of a Model, you can attach a function that | ||
will be called when that event happens. | ||
See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Model-Hooks). | ||
Currently the following events are supported: | ||
- `afterLoad` : (no parameters) Right after loading and preparing an instance to be used; | ||
- `afterAutoFetch` : (no parameters) Right after auto-fetching associations (if any), it will trigger regardless of having associations or not; | ||
- `beforeSave` : (no parameters) Right before trying to save; | ||
- `afterSave` : (bool success) Right after saving; | ||
- `beforeCreate` : (no parameters) Right before trying to save a new instance (prior to `beforeSave`); | ||
- `afterCreate` : (bool success) Right after saving a new instance; | ||
- `beforeRemove` : (no parameters) Right before trying to remove an instance; | ||
- `afterRemove` : (bool success) Right after removing an instance; | ||
- `beforeValidation` : (no parameters) Before all validations and prior to `beforeCreate` and `beforeSave`; | ||
All hook function are called with `this` as the instance so you can access anything you want related to it. | ||
For all `before*` hooks, you can add an additional parameter to the hook function. This parameter will be a function that | ||
must be called to tell if the hook allows the execution to continue or to break. You might be familiar with this workflow | ||
already from Express. Here's an example: | ||
```js | ||
var Person = db.define("person", { | ||
name : String, | ||
surname : String | ||
}, { | ||
hooks: { | ||
beforeCreate: function (next) { | ||
if (this.surname == "Doe") { | ||
return next(new Error("No Does allowed")); | ||
} | ||
return next(); | ||
} | ||
} | ||
}); | ||
``` | ||
This workflow allows you to make asynchronous work before calling `next`. | ||
## Finding Items | ||
@@ -551,2 +407,9 @@ | ||
You can also `order` or `orderRaw`: | ||
```js | ||
Person.find({ age: 18 }).order('-name').all( ... ); | ||
// see the 'Raw queries' section below for more details | ||
Person.find({ age: 18 }).orderRaw("?? DESC", ['age']).all( ... ); | ||
``` | ||
You can also chain and just get the count in the end. In this case, offset, limit and order are ignored. | ||
@@ -624,2 +487,24 @@ | ||
#### Raw queries | ||
```js | ||
db.driver.execQuery("SELECT id, email FROM user", function (err, data) { ... }) | ||
// You can escape identifiers and values. | ||
// For identifier substitution use: ?? | ||
// For value substitution use: ? | ||
db.driver.execQuery( | ||
"SELECT user.??, user.?? FROM user WHERE user.?? LIKE ? AND user.?? > ?", | ||
['id', 'name', 'name', 'john', 'id', 55], | ||
function (err, data) { ... } | ||
) | ||
// Identifiers don't need to be scaped most of the time | ||
db.driver.execQuery( | ||
"SELECT user.id, user.name FROM user WHERE user.name LIKE ? AND user.id > ?", | ||
['john', 55], | ||
function (err, data) { ... } | ||
) | ||
``` | ||
### Caching & Integrity | ||
@@ -718,64 +603,4 @@ | ||
The module [Enforce](http://github.com/dresende/node-enforce) is used for validations. For people using previous validators, | ||
they're still present, some as links to enforce, others not. We advise you to start using `orm.enforce` instead of `orm.validators`. | ||
For a list of possible validations, consult the [module](http://github.com/dresende/node-enforce). | ||
See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Model-Validations). | ||
You can define validations for every property of a Model. You can have one or more validations for each property. | ||
You can also use the predefined validations or create your own. | ||
```js | ||
var Person = db.define("person", { | ||
name : String, | ||
age : Number | ||
}, { | ||
validations : { | ||
name : orm.enforce.ranges.length(1, undefined, "missing"), // "missing" is a name given to this validation, instead of default | ||
age : [ orm.enforce.ranges.number(0, 10), orm.enforce.lists.inside([ 1, 3, 5, 7, 9 ]) ] | ||
} | ||
}); | ||
``` | ||
The code above defines that the `name` length must be between 1 and undefined (undefined means any) and `age` | ||
must be a number between 0 and 10 (inclusive) but also one of the listed values. The example might not make sense | ||
but you get the point. | ||
When saving an item, if it fails to validate any of the defined validations you'll get an `error` object with the property | ||
name and validation error description. This description should help you identify what happened. | ||
```js | ||
var John = new Person({ | ||
name : "", | ||
age : 20 | ||
}); | ||
John.save(function (err) { | ||
// err.field = "name" , err.value = "" , err.msg = "missing" | ||
}); | ||
``` | ||
The validation stops after the first validation error. If you want it to validate every property and return all validation | ||
errors, you can change this behavior on global or local settings: | ||
```js | ||
var orm = require("orm"); | ||
orm.settings.set("instance.returnAllErrors", true); // global or.. | ||
orm.connect("....", function (err, db) { | ||
db.settings.set("instance.returnAllErrors", true); // .. local | ||
// ... | ||
var John = new Person({ | ||
name : "", | ||
age : 15 | ||
}); | ||
John.save(function (err) { | ||
assert(Array.isArray(err)); | ||
// err[0].property = "name" , err[0].value = "" , err[0].msg = "missing" | ||
// err[1].property = "age" , err[1].value = 15 , err[1].msg = "out-of-range-number" | ||
// err[2].property = "age" , err[2].value = 15 , err[2].msg = "outside-list" | ||
}); | ||
}); | ||
``` | ||
## Associations | ||
@@ -782,0 +607,0 @@ |
@@ -1,5 +0,8 @@ | ||
var common = exports; | ||
var path = require('path'); | ||
var async = require('async'); | ||
var ORM = require('../'); | ||
var common = exports; | ||
var path = require('path'); | ||
var async = require('async'); | ||
var _ = require('lodash'); | ||
var util = require('util'); | ||
var querystring = require('querystring'); | ||
var ORM = require('../'); | ||
@@ -16,4 +19,4 @@ common.ORM = ORM; | ||
common.createConnection = function(cb) { | ||
ORM.connect(this.getConnectionString(), cb); | ||
common.createConnection = function(opts, cb) { | ||
ORM.connect(this.getConnectionString(opts), cb); | ||
}; | ||
@@ -24,2 +27,4 @@ | ||
if (common.isTravis()) return 'found'; | ||
try { | ||
@@ -54,54 +59,101 @@ config = require("./config"); | ||
common.getConnectionString = function () { | ||
var url; | ||
common.getConnectionString = function (opts) { | ||
var config, query; | ||
var protocol = this.protocol(); | ||
if (common.isTravis()) { | ||
switch (this.protocol()) { | ||
case 'mysql': | ||
return 'mysql://root@localhost/orm_test'; | ||
case 'postgres': | ||
case 'redshift': | ||
return 'postgres://postgres@localhost/orm_test'; | ||
case 'sqlite': | ||
return 'sqlite://'; | ||
case 'mongodb': | ||
return 'mongodb://localhost/test'; | ||
default: | ||
throw new Error("Unknown protocol"); | ||
} | ||
} else { | ||
var config = require("./config")[this.protocol()]; | ||
if (common.isTravis()) { | ||
config = {}; | ||
} else { | ||
config = require("./config")[protocol]; | ||
} | ||
switch (this.protocol()) { | ||
case 'mysql': | ||
return 'mysql://' + | ||
(config.user || 'root') + | ||
(config.password ? ':' + config.password : '') + | ||
'@' + (config.host || 'localhost') + | ||
'/' + (config.database || 'orm_test'); | ||
case 'postgres': | ||
return 'postgres://' + | ||
(config.user || 'postgres') + | ||
(config.password ? ':' + config.password : '') + | ||
'@' + (config.host || 'localhost') + | ||
'/' + (config.database || 'orm_test'); | ||
case 'redshift': | ||
return 'redshift://' + | ||
(config.user || 'postgres') + | ||
(config.password ? ':' + config.password : '') + | ||
'@' + (config.host || 'localhost') + | ||
'/' + (config.database || 'orm_test'); | ||
case 'mongodb': | ||
return 'mongodb://' + | ||
(config.user || '') + | ||
(config.password ? ':' + config.password : '') + | ||
'@' + (config.host || 'localhost') + | ||
'/' + (config.database || 'test'); | ||
case 'sqlite': | ||
return 'sqlite://' + (config.pathname || ""); | ||
default: | ||
throw new Error("Unknown protocol"); | ||
} | ||
} | ||
return url; | ||
opts = opts || {}; | ||
_.defaults(config, { | ||
user : { postgres: 'postgres', redshift: 'postgres', mongodb: '' }[protocol] || 'root', | ||
database : { mongodb: 'test' }[protocol] || 'orm_test', | ||
password : '', | ||
host : 'localhost', | ||
pathname : '', | ||
query : {} | ||
}); | ||
_.merge(config, opts); | ||
query = querystring.stringify(config.query); | ||
switch (protocol) { | ||
case 'mysql': | ||
case 'postgres': | ||
case 'redshift': | ||
case 'mongodb': | ||
if (common.isTravis()) { | ||
if (protocol == 'redshift') protocol = 'postgres'; | ||
return util.format("%s://%s@%s/%s?%s", | ||
protocol, config.user, config.host, config.database, query | ||
); | ||
} else { | ||
return util.format("%s://%s:%s@%s/%s?%s", | ||
protocol, config.user, config.password, | ||
config.host, config.database, query | ||
).replace(':@','@'); | ||
} | ||
case 'sqlite': | ||
return util.format("%s://%s?%s", protocol, config.pathname, query); | ||
default: | ||
throw new Error("Unknown protocol " + protocol); | ||
} | ||
}; | ||
common.retry = function (before, run, until, done, args) { | ||
if (typeof until === "number") { | ||
var countDown = until; | ||
until = function (err) { | ||
if (err && --countDown > 0) return false; | ||
return true; | ||
}; | ||
} | ||
if (typeof args === "undefined") args = []; | ||
var handler = function (err) { | ||
if (until(err)) return done.apply(this, arguments); | ||
return runNext(); | ||
}; | ||
args.push(handler); | ||
var runCurrent = function () { | ||
if (run.length == args.length) { | ||
return run.apply(this, args); | ||
} else { | ||
run.apply(this, args); | ||
handler(); | ||
} | ||
}; | ||
var runNext = function () { | ||
try { | ||
if (before.length > 0) { | ||
before(function (err) { | ||
if (until(err)) return done(err); | ||
return runCurrent(); | ||
}); | ||
} else { | ||
before(); | ||
runCurrent(); | ||
} | ||
} | ||
catch (e) { | ||
handler(e); | ||
} | ||
}; | ||
if (before.length > 0) { | ||
before(function (err) { | ||
if (err) return done(err); | ||
runNext(); | ||
}); | ||
} | ||
else { | ||
before(); | ||
runNext(); | ||
} | ||
}; |
@@ -20,1 +20,5 @@ // To test, rename this file to config.js and update | ||
}; | ||
exports.mongodb = { | ||
host: "localhost", | ||
database: "test" | ||
}; |
@@ -223,2 +223,28 @@ var should = require('should'); | ||
it("should accept arguments in different orders", function (done) { | ||
Pet.find({ name: "Mutt" }, function (err, pets) { | ||
Person.find({ name: "John" }, function (err, people) { | ||
should.equal(err, null); | ||
people[0].removePets(function (err) { | ||
should.equal(err, null); | ||
people[0].getPets(function (err, pets) { | ||
should.equal(err, null); | ||
should(Array.isArray(pets)); | ||
pets.length.should.equal(1); | ||
pets[0].name.should.equal("Deco"); | ||
return done(); | ||
}); | ||
}, pets[0]); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("delAccessor", function () { | ||
before(setup()); | ||
it("should remove specific associations if passed", function (done) { | ||
@@ -225,0 +251,0 @@ Pet.find({ name: "Mutt" }, function (err, pets) { |
@@ -1,25 +0,27 @@ | ||
var ORM = require('../../'); | ||
var ORM = require('../../'); | ||
var helper = require('../support/spec_helper'); | ||
var should = require('should'); | ||
var async = require('async'); | ||
var _ = require('lodash'); | ||
var async = require('async'); | ||
var common = require('../common'); | ||
var _ = require('lodash'); | ||
describe("hasOne", function () { | ||
var db = null; | ||
var Person = null; | ||
var db = null; | ||
var Person = null; | ||
var setup = function () { | ||
return function (done) { | ||
Person = db.define('person', { | ||
name : String | ||
}); | ||
Pet = db.define('pet', { | ||
name : String | ||
}); | ||
Person.hasOne('pet', Pet, { | ||
reverse : 'owner' | ||
}); | ||
var setup = function () { | ||
return function (done) { | ||
Person = db.define('person', { | ||
name: String | ||
}); | ||
Pet = db.define('pet', { | ||
name: String | ||
}); | ||
Person.hasOne('pet', Pet, { | ||
reverse: 'owner', | ||
field: 'pet_id' | ||
}); | ||
return helper.dropSync([ Person, Pet ], function () { | ||
async.parallel([ | ||
return helper.dropSync([Person, Pet], function () { | ||
async.parallel([ | ||
Person.create.bind(Person, { name: "John Doe" }), | ||
@@ -29,87 +31,180 @@ Person.create.bind(Person, { name: "Jane Doe" }), | ||
Pet.create.bind(Pet, { name: "Fido" }), | ||
], done); | ||
}); | ||
}; | ||
}; | ||
], done); | ||
}); | ||
}; | ||
}; | ||
before(function(done) { | ||
helper.connect(function (connection) { | ||
db = connection; | ||
done(); | ||
}); | ||
}); | ||
before(function (done) { | ||
helper.connect(function (connection) { | ||
db = connection; | ||
done(); | ||
}); | ||
}); | ||
describe("reverse", function () { | ||
before(setup()); | ||
describe("reverse", function () { | ||
before(setup()); | ||
it("should create methods in both models", function (done) { | ||
var person = Person(1); | ||
var pet = Pet(1); | ||
it("should create methods in both models", function (done) { | ||
var person = Person(1); | ||
var pet = Pet(1); | ||
person.getPet.should.be.a("function"); | ||
person.setPet.should.be.a("function"); | ||
person.removePet.should.be.a("function"); | ||
person.hasPet.should.be.a("function"); | ||
person.getPet.should.be.a("function"); | ||
person.setPet.should.be.a("function"); | ||
person.removePet.should.be.a("function"); | ||
person.hasPet.should.be.a("function"); | ||
pet.getOwner.should.be.a("function"); | ||
pet.setOwner.should.be.a("function"); | ||
pet.hasOwner.should.be.a("function"); | ||
pet.getOwner.should.be.a("function"); | ||
pet.setOwner.should.be.a("function"); | ||
pet.hasOwner.should.be.a("function"); | ||
return done(); | ||
}); | ||
return done(); | ||
}); | ||
it("should be able to fetch model from reverse model", function (done) { | ||
Person.find({ name: "John Doe" }).first(function (err, John) { | ||
Pet.find({ name: "Deco" }).first(function (err, Deco) { | ||
Deco.hasOwner(function (err, has_owner) { | ||
should.not.exist(err); | ||
has_owner.should.be.false; | ||
it("should be able to fetch model from reverse model", function (done) { | ||
Person.find({ name: "John Doe" }).first(function (err, John) { | ||
Pet.find({ name: "Deco" }).first(function (err, Deco) { | ||
Deco.hasOwner(function (err, has_owner) { | ||
should.not.exist(err); | ||
has_owner.should.be.false; | ||
Deco.setOwner(John, function (err) { | ||
should.not.exist(err); | ||
Deco.setOwner(John, function (err) { | ||
should.not.exist(err); | ||
Deco.getOwner(function (err, JohnCopy) { | ||
should.not.exist(err); | ||
should(Array.isArray(JohnCopy)); | ||
John.should.eql(JohnCopy[0]); | ||
Deco.getOwner(function (err, JohnCopy) { | ||
should.not.exist(err); | ||
should(Array.isArray(JohnCopy)); | ||
John.should.eql(JohnCopy[0]); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it("should be able to set an array of people as the owner", function (done) { | ||
Person.find({ name: ["John Doe", "Jane Doe"] }, function (err, owners) { | ||
Pet.find({ name: "Fido" }).first(function (err, Fido) { | ||
Fido.hasOwner(function (err, has_owner) { | ||
should.not.exist(err); | ||
has_owner.should.be.false; | ||
it("should be able to set an array of people as the owner", function (done) { | ||
Person.find({ name: ["John Doe", "Jane Doe"] }, function (err, owners) { | ||
Pet.find({ name: "Fido" }).first(function (err, Fido) { | ||
Fido.hasOwner(function (err, has_owner) { | ||
should.not.exist(err); | ||
has_owner.should.be.false; | ||
Fido.setOwner(owners, function (err) { | ||
should.not.exist(err); | ||
Fido.setOwner(owners, function (err) { | ||
should.not.exist(err); | ||
Fido.getOwner(function (err, ownersCopy) { | ||
should.not.exist(err); | ||
should(Array.isArray(owners)); | ||
owners.length.should.equal(2); | ||
Fido.getOwner(function (err, ownersCopy) { | ||
should.not.exist(err); | ||
should(Array.isArray(owners)); | ||
owners.length.should.equal(2); | ||
if (owners[0] == ownersCopy[0]) { | ||
owners[0].should.eql(ownersCopy[0]); | ||
owners[1].should.eql(ownersCopy[1]); | ||
} else { | ||
owners[0].should.eql(ownersCopy[1]); | ||
owners[1].should.eql(ownersCopy[0]); | ||
} | ||
if (owners[0] == ownersCopy[0]) { | ||
owners[0].should.eql(ownersCopy[0]); | ||
owners[1].should.eql(ownersCopy[1]); | ||
} else { | ||
owners[0].should.eql(ownersCopy[1]); | ||
owners[1].should.eql(ownersCopy[0]); | ||
} | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("reverse find", function () { | ||
it("should be able to find given an association id", function (done) { | ||
common.retry(setup(), function (done) { | ||
Person.find({ name: "John Doe" }).first(function (err, John) { | ||
should.not.exist(err); | ||
should.exist(John); | ||
Pet.find({ name: "Deco" }).first(function (err, Deco) { | ||
should.not.exist(err); | ||
should.exist(Deco); | ||
Deco.hasOwner(function (err, has_owner) { | ||
should.not.exist(err); | ||
has_owner.should.be.false; | ||
Deco.setOwner(John, function (err) { | ||
should.not.exist(err); | ||
Person.find({ pet_id: Deco[Pet.id[0]] }).first(function (err, owner) { | ||
should.not.exist(err); | ||
should.exist(owner); | ||
should.equal(owner.name, John.name); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, 3, done); | ||
}); | ||
it("should be able to find given an association instance", function (done) { | ||
common.retry(setup(), function (done) { | ||
Person.find({ name: "John Doe" }).first(function (err, John) { | ||
should.not.exist(err); | ||
should.exist(John); | ||
Pet.find({ name: "Deco" }).first(function (err, Deco) { | ||
should.not.exist(err); | ||
should.exist(Deco); | ||
Deco.hasOwner(function (err, has_owner) { | ||
should.not.exist(err); | ||
has_owner.should.be.false; | ||
Deco.setOwner(John, function (err) { | ||
should.not.exist(err); | ||
Person.find({ pet: Deco }).first(function (err, owner) { | ||
should.not.exist(err); | ||
should.exist(owner); | ||
should.equal(owner.name, John.name); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, 3, done); | ||
}); | ||
it("should be able to find given a number of association instances with a single primary key", function (done) { | ||
common.retry(setup(), function (done) { | ||
Person.find({ name: "John Doe" }).first(function (err, John) { | ||
should.not.exist(err); | ||
should.exist(John); | ||
Pet.all(function (err, pets) { | ||
should.not.exist(err); | ||
should.exist(pets); | ||
should.equal(pets.length, 2); | ||
pets[0].hasOwner(function (err, has_owner) { | ||
should.not.exist(err); | ||
has_owner.should.be.false; | ||
pets[0].setOwner(John, function (err) { | ||
should.not.exist(err); | ||
Person.find({ pet: pets }, function (err, owners) { | ||
should.not.exist(err); | ||
should.exist(owners); | ||
owners.length.should.equal(1); | ||
should.equal(owners[0].name, John.name); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, 3, done); | ||
}); | ||
}); | ||
}); |
@@ -232,2 +232,24 @@ var ORM = require('../../'); | ||
}); | ||
it("shouldn't cause an infinite loop when getting and saving with no changes", function (done) { | ||
Leaf.get(leafId, function (err, leaf) { | ||
should.not.exist(err); | ||
leaf.save( function (err) { | ||
should.not.exist(err); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it("shouldn't cause an infinite loop when getting and saving with changes", function (done) { | ||
Leaf.get(leafId, function (err, leaf) { | ||
should.not.exist(err); | ||
leaf.save({ size: 14 }, function (err) { | ||
should.not.exist(err); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -234,0 +256,0 @@ }); |
@@ -1,4 +0,5 @@ | ||
var should = require('should'); | ||
var helper = require('../support/spec_helper'); | ||
var ORM = require('../../'); | ||
var should = require('should'); | ||
var helper = require('../support/spec_helper'); | ||
var ORM = require('../../'); | ||
var common = require('../common'); | ||
@@ -131,2 +132,52 @@ describe("db.use()", function () { | ||
describe("db.load()", function () { | ||
var db = null; | ||
before(function (done) { | ||
helper.connect(function (connection) { | ||
db = connection; | ||
return done(); | ||
}); | ||
}); | ||
after(function () { | ||
return db.close(); | ||
}); | ||
it("should be able to load more than one file", function (done) { | ||
db.load("../support/spec_load_second", "../support/spec_load_third", function () { | ||
db.models.should.have.property("person"); | ||
db.models.should.have.property("pet"); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
describe("db.load()", function () { | ||
var db = null; | ||
before(function (done) { | ||
helper.connect(function (connection) { | ||
db = connection; | ||
return done(); | ||
}); | ||
}); | ||
after(function () { | ||
return db.close(); | ||
}); | ||
it("should be able to load more than one file passed as Array", function (done) { | ||
db.load([ "../support/spec_load_second", "../support/spec_load_third" ], function () { | ||
db.models.should.have.property("person"); | ||
db.models.should.have.property("pet"); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
describe("db.serial()", function () { | ||
@@ -180,1 +231,65 @@ var db = null; | ||
}); | ||
describe("db.driver", function () { | ||
var db = null; | ||
before(function (done) { | ||
helper.connect(function (connection) { | ||
db = connection; | ||
var Log = db.define('log', { | ||
what : { type: 'text' }, | ||
when : { type: 'date', time: true }, | ||
who : { type: 'text' } | ||
}); | ||
helper.dropSync(Log, function (err) { | ||
if (err) return done(err); | ||
Log.create([ | ||
{ what: "password reset", when: new Date('2013/04/07 12:33:05'), who: "jane" }, | ||
{ what: "user login", when: new Date('2013/04/07 13:01:44'), who: "jane" }, | ||
{ what: "user logout", when: new Date('2013/05/12 04:09:31'), who: "john" } | ||
], done); | ||
}); | ||
}); | ||
}); | ||
after(function () { | ||
return db.close(); | ||
}); | ||
it("should be available", function () { | ||
should.exist(db.driver); | ||
}); | ||
if (common.protocol() == "mongodb") return; | ||
describe("query", function () { | ||
it("should be available", function () { | ||
should.exist(db.driver.query); | ||
}); | ||
describe("#execQuery", function () { | ||
it("should execute sql queries", function (done) { | ||
db.driver.execQuery("SELECT id FROM log", function (err, data) { | ||
should.not.exist(err); | ||
should(JSON.stringify(data) == JSON.stringify([{ id: 1 }, { id: 2 }, { id: 3 }])); | ||
done(); | ||
}); | ||
}); | ||
it("should escape sql queries", function (done) { | ||
var query = "SELECT log.?? FROM log WHERE log.?? LIKE ? AND log.?? > ?"; | ||
var args = ['what', 'who', 'jane', 'when', new Date('2013/04/07 12:40:00')]; | ||
db.driver.execQuery(query, args, function (err, data) { | ||
should.not.exist(err); | ||
should(JSON.stringify(data) == JSON.stringify([{ "what": "user login" }])); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -489,2 +489,21 @@ var _ = require('lodash'); | ||
}); | ||
describe("if hook returns an error", function () { | ||
before(setup({ | ||
afterLoad : function (next) { | ||
return next(new Error("AFTERLOAD_FAIL")); | ||
} | ||
})); | ||
it("should return error", function (done) { | ||
this.timeout(500); | ||
Person.create([{ name: "John Doe" }], function (err, items) { | ||
err.should.exist; | ||
err.message.should.equal("AFTERLOAD_FAIL"); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -491,0 +510,0 @@ }); |
@@ -1,3 +0,4 @@ | ||
var should = require('should'); | ||
var helper = require('../support/spec_helper'); | ||
var should = require('should'); | ||
var helper = require('../support/spec_helper'); | ||
var common = require('../common'); | ||
var ORM = require('../../'); | ||
@@ -8,2 +9,3 @@ | ||
var Person = null; | ||
var protocol = common.protocol(); | ||
@@ -15,5 +17,8 @@ var setup = function () { | ||
Person = db.define("person", { | ||
name : String, | ||
age : { type: 'number', rational: false, required: false } | ||
name : String, | ||
age : { type: 'number', rational: false, required: false }, | ||
height : { type: 'number', rational: false, required: false }, | ||
weight : { type: 'number', required: false } | ||
}, { | ||
cache: false, | ||
validations: { | ||
@@ -127,2 +132,110 @@ age: ORM.validators.rangeNumber(0, 150) | ||
}); | ||
describe("properties", function () { | ||
describe("Number", function () { | ||
it("should be saved for valid numbers, using both save & create", function (done) { | ||
var person1 = new Person({ height: 190 }); | ||
person1.save(function (err) { | ||
should.not.exist(err); | ||
Person.create({ height: 170 }, function (err, person2) { | ||
should.not.exist(err); | ||
Person.get(person1[Person.id], function (err, item) { | ||
should.not.exist(err); | ||
should.equal(item.height, 190); | ||
Person.get(person2[Person.id], function (err, item) { | ||
should.not.exist(err); | ||
should.equal(item.height, 170); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
if (protocol == 'postgres') { | ||
// Only postgres raises propper errors. | ||
// Sqlite & Mysql fail silently and insert nulls. | ||
it("should raise an error for NaN integers", function (done) { | ||
var person = new Person({ height: NaN }); | ||
person.save(function (err) { | ||
should.exist(err); | ||
var msg = { | ||
postgres : 'invalid input syntax for integer: "NaN"' | ||
}[protocol]; | ||
should.equal(err.message, msg); | ||
done(); | ||
}); | ||
}); | ||
it("should raise an error for Infinity integers", function (done) { | ||
var person = new Person({ height: Infinity }); | ||
person.save(function (err) { | ||
should.exist(err); | ||
var msg = { | ||
postgres : 'invalid input syntax for integer: "Infinity"' | ||
}[protocol]; | ||
should.equal(err.message, msg); | ||
done(); | ||
}); | ||
}); | ||
it("should raise an error for nonsensical integers, for both save & create", function (done) { | ||
var person = new Person({ height: 'bugz' }); | ||
person.save(function (err) { | ||
should.exist(err); | ||
var msg = { | ||
postgres : 'invalid input syntax for integer: "bugz"' | ||
}[protocol]; | ||
should.equal(err.message, msg); | ||
Person.create({ height: 'bugz' }, function (err, instance) { | ||
should.exist(err); | ||
should.equal(err.message, msg); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
} | ||
if (protocol != 'mysql') { | ||
// Mysql doesn't support IEEE floats (NaN, Infinity, -Infinity) | ||
it("should store NaN & Infinite floats", function (done) { | ||
var person = new Person({ weight: NaN }); | ||
person.save(function (err) { | ||
should.not.exist(err); | ||
Person.get(person[Person.id], function (err, person) { | ||
should.not.exist(err); | ||
should(isNaN(person.weight)); | ||
person.save({ weight: Infinity, name: 'black hole' }, function (err) { | ||
should.not.exist(err); | ||
Person.get(person[Person.id], function (err, person) { | ||
should.not.exist(err); | ||
should.strictEqual(person.weight, Infinity); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); | ||
}); | ||
}); |
@@ -92,6 +92,6 @@ var should = require('should'); | ||
describe(".order('property')", function () { | ||
describe("order", function () { | ||
before(setup()); | ||
it("should order by that property ascending", function (done) { | ||
it("('property') should order by that property ascending", function (done) { | ||
Person.find().order("age").run(function (err, instances) { | ||
@@ -106,8 +106,4 @@ should.equal(err, null); | ||
}); | ||
}); | ||
describe(".order('-property')", function () { | ||
before(setup()); | ||
it("should order by that property descending", function (done) { | ||
it("('-property') should order by that property descending", function (done) { | ||
Person.find().order("-age").run(function (err, instances) { | ||
@@ -122,9 +118,22 @@ should.equal(err, null); | ||
}); | ||
it("('property', 'Z') should order by that property descending", function (done) { | ||
Person.find().order("age", "Z").run(function (err, instances) { | ||
should.equal(err, null); | ||
instances.should.have.property("length", 3); | ||
instances[0].age.should.equal(20); | ||
instances[2].age.should.equal(18); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
describe(".order('property', 'Z')", function () { | ||
describe("orderRaw", function () { | ||
if (common.protocol() == 'mongodb') return; | ||
before(setup()); | ||
it("should order by that property descending", function (done) { | ||
Person.find().order("age", "Z").run(function (err, instances) { | ||
it("should allow ordering by SQL", function (done) { | ||
Person.find().orderRaw("age DESC").run(function (err, instances) { | ||
should.equal(err, null); | ||
@@ -138,8 +147,19 @@ instances.should.have.property("length", 3); | ||
}); | ||
it("should allow ordering by SQL with escaping", function (done) { | ||
Person.find().orderRaw("?? DESC", ['age']).run(function (err, instances) { | ||
should.equal(err, null); | ||
instances.should.have.property("length", 3); | ||
instances[0].age.should.equal(20); | ||
instances[2].age.should.equal(18); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
describe(".only('property', ...)", function () { | ||
describe("only", function () { | ||
before(setup()); | ||
it("should return only those properties, others null", function (done) { | ||
it("('property', ...) should return only those properties, others null", function (done) { | ||
Person.find().only("age", "surname").order("-age").run(function (err, instances) { | ||
@@ -155,8 +175,5 @@ should.equal(err, null); | ||
}); | ||
}); | ||
describe(".only('property1', ...)", function () { | ||
before(setup()); | ||
it("should return only those properties, others null", function (done) { | ||
// This works if cache is disabled. I suspect a cache bug. | ||
xit("(['property', ...]) should return only those properties, others null", function (done) { | ||
Person.find().only([ "age", "surname" ]).order("-age").run(function (err, instances) { | ||
@@ -469,2 +486,30 @@ should.equal(err, null); | ||
}); | ||
describe(".success()", function () { | ||
before(setup()); | ||
it("should return a Promise with .fail() method", function (done) { | ||
Person.find().success(function (people) { | ||
should(Array.isArray(people)); | ||
return done(); | ||
}).fail(function (err) { | ||
// never called.. | ||
}); | ||
}); | ||
}); | ||
describe(".fail()", function () { | ||
before(setup()); | ||
it("should return a Promise with .success() method", function (done) { | ||
Person.find().fail(function (err) { | ||
// never called.. | ||
}).success(function (people) { | ||
should(Array.isArray(people)); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); |
var should = require('should'); | ||
var helper = require('../support/spec_helper'); | ||
var common = require('../common'); | ||
var ORM = require('../../'); | ||
@@ -285,2 +286,4 @@ | ||
describe("with a point property type", function() { | ||
if (common.protocol() == 'sqlite' || common.protocol() == 'mongodb') return; | ||
it("should deserialize the point to an array", function (done) { | ||
@@ -296,7 +299,3 @@ db.settings.set('properties.primary_key', 'id'); | ||
return helper.dropSync(Person, function (err) { | ||
if (err) { | ||
return done(); // not supported | ||
} | ||
return helper.dropSync(Person, function () { | ||
Person.create({ | ||
@@ -303,0 +302,0 @@ name : "John Doe", |
@@ -56,2 +56,5 @@ var should = require('should'); | ||
DoorAccessHistory = db.define("door_access_history", { | ||
year : { type: 'number', rational: false }, | ||
month : { type: 'number', rational: false }, | ||
day : { type: 'number', rational: false }, | ||
user : String, | ||
@@ -106,11 +109,3 @@ action : [ "in", "out" ] | ||
}); | ||
it("should throw if defining hasMany association", function (done) { | ||
(function () { | ||
DoorAccessHistory.hasMany("..."); | ||
}).should.throw(); | ||
return done(); | ||
}); | ||
}); | ||
}); |
var should = require('should'); | ||
var helper = require('../support/spec_helper'); | ||
var common = require('../common'); | ||
var ORM = require('../../'); | ||
@@ -201,8 +202,6 @@ | ||
describe("with a point property", function () { | ||
if (common.protocol() == 'sqlite' || common.protocol() == 'mongodb') return; | ||
it("should save the instance as a geospatial point", function (done) { | ||
setup({ type: "point" }, null)(function (err) { | ||
if (err) { | ||
return done(); // not supported | ||
} | ||
setup({ type: "point" }, null)(function () { | ||
var John = new Person({ | ||
@@ -209,0 +208,0 @@ name: { x: 51.5177, y: -0.0968 } |
@@ -201,3 +201,3 @@ var sqlite = require('sqlite3'); | ||
ORM.use(db, "sqlite", function (err) { | ||
should.equal(err, null); | ||
should.not.exist(err); | ||
@@ -204,0 +204,0 @@ return done(); |
var should = require('should'); | ||
var helper = require('../support/spec_helper'); | ||
var validators = require('../../').validators; | ||
var common = require('../common'); | ||
var protocol = common.protocol().toLowerCase(); | ||
var undef = undefined; | ||
@@ -29,2 +31,4 @@ | ||
describe("unique()", function () { | ||
if (protocol === "mongodb") return; | ||
var db = null; | ||
@@ -31,0 +35,0 @@ var Person = null; |
@@ -8,3 +8,3 @@ var should = require('should'); | ||
it("should return type: 'text'", function (done) { | ||
Property.normalize(String, ORM.settings).type.should.equal("text"); | ||
Property.normalize(String, {}, ORM.settings).type.should.equal("text"); | ||
@@ -16,3 +16,3 @@ return done(); | ||
it("should return type: 'number'", function (done) { | ||
Property.normalize(Number, ORM.settings).type.should.equal("number"); | ||
Property.normalize(Number, {}, ORM.settings).type.should.equal("number"); | ||
@@ -24,3 +24,3 @@ return done(); | ||
it("should return type: 'boolean'", function (done) { | ||
Property.normalize(Boolean, ORM.settings).type.should.equal("boolean"); | ||
Property.normalize(Boolean, {}, ORM.settings).type.should.equal("boolean"); | ||
@@ -32,3 +32,3 @@ return done(); | ||
it("should return type: 'date'", function (done) { | ||
Property.normalize(Date, ORM.settings).type.should.equal("date"); | ||
Property.normalize(Date, {}, ORM.settings).type.should.equal("date"); | ||
@@ -40,3 +40,3 @@ return done(); | ||
it("should return type: 'object'", function (done) { | ||
Property.normalize(Object, ORM.settings).type.should.equal("object"); | ||
Property.normalize(Object, {}, ORM.settings).type.should.equal("object"); | ||
@@ -48,3 +48,3 @@ return done(); | ||
it("should return type: 'binary'", function (done) { | ||
Property.normalize(Buffer, ORM.settings).type.should.equal("binary"); | ||
Property.normalize(Buffer, {}, ORM.settings).type.should.equal("binary"); | ||
@@ -56,3 +56,3 @@ return done(); | ||
it("should return type: 'enum' with list of items", function (done) { | ||
var prop = Property.normalize([ 1, 2, 3 ], ORM.settings); | ||
var prop = Property.normalize([ 1, 2, 3 ], {}, ORM.settings); | ||
@@ -67,3 +67,3 @@ prop.type.should.equal("enum"); | ||
it("should return type: <type>", function (done) { | ||
Property.normalize("text", ORM.settings).type.should.equal("text"); | ||
Property.normalize("text", {}, ORM.settings).type.should.equal("text"); | ||
@@ -73,4 +73,4 @@ return done(); | ||
it("should accept: 'point'", function(done) { | ||
Property.normalize("point", ORM.settings).type.should.equal("point"); | ||
Property.normalize("point", {}, ORM.settings).type.should.equal("point"); | ||
return done(); | ||
@@ -82,3 +82,3 @@ }); | ||
(function () { | ||
Property.normalize("string", ORM.settings); | ||
Property.normalize("string", {}, ORM.settings); | ||
}).should.throw(); | ||
@@ -85,0 +85,0 @@ |
@@ -5,2 +5,4 @@ var _ = require('lodash'); | ||
var async = require('async'); | ||
var common = require('../common'); | ||
var protocol = common.protocol().toLowerCase(); | ||
var ORM = require('../../'); | ||
@@ -60,23 +62,165 @@ | ||
it("unique validator should work", function(done) { | ||
var Product = db.define("person_unique", { name: String }, { | ||
validations: { name: ORM.validators.unique() } | ||
}); | ||
var create = function (cb) { | ||
var p = new Product({ name: 'broom' }); | ||
describe("unique", function () { | ||
if (protocol === "mongodb") return; | ||
return p.save(cb); | ||
var Product = null; | ||
var setupUnique = function (ignoreCase, scope, msg) { | ||
return function (done) { | ||
Product = db.define("product_unique", { | ||
instock : { type: 'boolean', required: true, defaultValue: false }, | ||
name : String, | ||
category : String | ||
}, { | ||
cache: false, | ||
validations: { | ||
name : ORM.validators.unique({ ignoreCase: ignoreCase, scope: scope }, msg), | ||
instock : ORM.validators.required(), | ||
productId : ORM.validators.unique() // this must be straight after a required & validated row. | ||
} | ||
}); | ||
Product.hasOne('product', Product, { field: 'productId', required: false, autoFetch: true }); | ||
return helper.dropSync(Product, done); | ||
}; | ||
}; | ||
helper.dropSync(Product, function() { | ||
create(function (err) { | ||
should.equal(err, null); | ||
create(function (err) { | ||
should.deepEqual(err, _.extend(new Error(),{ | ||
property: 'name', value: 'broom', msg: 'not-unique' | ||
})); | ||
return done(); | ||
describe("simple", function () { | ||
before(setupUnique(false, false)); | ||
it("should return validation error for duplicate name", function (done) { | ||
Product.create({name: 'fork'}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({name: 'fork'}, function (err, product) { | ||
should.exist(err); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
it("should pass with different names", function (done) { | ||
Product.create({name: 'spatula'}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({name: 'plate'}, function (err, product) { | ||
should.not.exist(err); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
// Technically this is covered by the tests above, but I'm putting it here for clarity's sake. 3 HOURS WASTED *sigh. | ||
it("should not leak required state from previous validation for association properties [regression test]", function (done) { | ||
Product.create({ name: 'pencil', productId: null}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({ name: 'pencilcase', productId: null }, function (err, product) { | ||
should.not.exist(err); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("scope", function () { | ||
describe("to other property", function () { | ||
before(setupUnique(false, ['category'])); | ||
it("should return validation error if other property also matches", function(done) { | ||
Product.create({name: 'red', category: 'chair'}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({name: 'red', category: 'chair'}, function (err, product) { | ||
should.exist(err); | ||
should.equal(err.msg, 'not-unique'); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
it("should pass if other peroperty is different", function (done) { | ||
Product.create({name: 'blue', category: 'chair'}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({name: 'blue', category: 'pen'}, function (err, product) { | ||
should.not.exist(err); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
// In SQL unique index land, NULL values are not considered equal. | ||
it("should pass if other peroperty is null", function (done) { | ||
Product.create({name: 'blue', category: null}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({name: 'blue', category: null}, function (err, product) { | ||
should.not.exist(err); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("ignoreCase", function () { | ||
if (protocol != 'mysql') { | ||
it("false should do a case sensitive comparison", function (done) { | ||
setupUnique(false, false)(function (err) { | ||
should.not.exist(err); | ||
Product.create({name: 'spork'}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({name: 'spOrk'}, function (err, product) { | ||
should.not.exist(err); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
it("true should do a case insensitive comparison", function (done) { | ||
setupUnique(true, false)(function (err) { | ||
should.not.exist(err); | ||
Product.create({name: 'stapler'}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({name: 'staplER'}, function (err, product) { | ||
should.exist(err); | ||
should.equal(err.msg, 'not-unique'); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it("true should do a case insensitive comparison on scoped properties too", function (done) { | ||
setupUnique(true, ['category'], "name already taken for this category")(function (err) { | ||
should.not.exist(err); | ||
Product.create({name: 'black', category: 'pen'}, function (err, product) { | ||
should.not.exist(err); | ||
Product.create({name: 'Black', category: 'Pen'}, function (err, product) { | ||
should.exist(err); | ||
should.equal(err.msg, "name already taken for this category"); | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -83,0 +227,0 @@ }); |
var common = require('../common'); | ||
var async = require('async'); | ||
var should = require('should'); | ||
module.exports.connect = function(cb) { | ||
common.createConnection(function (err, conn) { | ||
var opts = {}; | ||
if (1 in arguments) { | ||
opts = arguments[0]; | ||
cb = arguments[1]; | ||
} | ||
common.createConnection(opts, function (err, conn) { | ||
if (err) throw err; | ||
@@ -22,3 +29,8 @@ cb(conn); | ||
}); | ||
}, done); | ||
}, function (err) { | ||
if (common.protocol() != 'sqlite') { | ||
should.not.exist(err); | ||
} | ||
done(err); | ||
}); | ||
}; |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
370020
83
10846
0
827
+ Addedenforce@0.1.2(transitive)
+ Addedlodash@2.0.0(transitive)
+ Addedsql-query@0.1.15(transitive)
- Removedenforce@0.1.1(transitive)
- Removedlodash@1.3.1(transitive)
- Removedsql-query@0.1.9(transitive)
Updatedenforce@0.1.2
Updatedlodash@2.0.0
Updatedsql-query@0.1.15