diff --git a/History.md b/History.md index c068a197..852a4531 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,10 @@ +#0.7.0 + +* Fix for issue [#121](https://github.com/C2FO/patio/issues/121) added the table name to the error thrown. +* Merged [#120](https://github.com/C2FO/patio/pull/120) this allows tables registered with DB to be looked up properly. + * This will break any `getModel` call where a table with the same name is added twice. +* Added documentation about running tests. + #0.6.1 * Added details for logging if the err.detail exists. diff --git a/README.md b/README.md index 5ab64017..e0592ee3 100755 --- a/README.md +++ b/README.md @@ -12,6 +12,26 @@ If you want to use the patio executable for migrations `npm install -g patio` +###Running Tests + +To run the tests + +``` +make test +``` + +To run just the postgres tests + +``` +make test-pg +``` + +To run just the mysql tests + +``` +make test-mysql +``` + ###Why Use Patio? Patio is different because it allows the developers to choose the level of abtraction they are comfortable with. diff --git a/docs-md/coverage.html b/docs-md/coverage.html index 8835c382..b4db6f30 100644 --- a/docs-md/coverage.html +++ b/docs-md/coverage.html @@ -256,10 +256,10 @@
try {
this.connection.setMaxListeners(0);
var fields = [];
query = new QueryStream(query);
query = new QueryStream(query, null, {batchSize: 1, highWaterMark: 1});
ret = this.connection.query(query);
var orig = ret.handleRowDescription;
ret.handleRowDescription = function (msg) {
* different databases.
*/
getModel: function (name, db) {
return model.getModel(name, db);
return model.getModel(name, db);
},
/**
* @default []
*/
DATABASES: function () {
return Database.DATABASES;
return Database.DATABASES;
},
/**
* Returns the default database. This is the first database created using {@link patio#connect}.
* @default null
*/
defaultDatabase: function () {
return this.DATABASES.length ? this.DATABASES[0] : null;
return this.DATABASES.length ? this.DATABASES[0] : null;
},
/**@ignore*/
Database: function () {
* @return the typecasted value.
* */
typecastValue: function (columnType, value) {
if (isNull(value) || isUndefined(value)) {
if (isNull(value) || isUndefined(value)) {
return null;
}
var meth = "__typecastValue" + columnType.charAt(0).toUpperCase() + columnType.substr(1).toLowerCase();
try {
if (isFunction(this[meth])) {
return this[meth](value);
var meth = "__typecastValue" + columnType.charAt(0).toUpperCase() + columnType.substr(1).toLowerCase();
try {
if (isFunction(this[meth])) {
return this[meth](value);
} else {
return value;
}
// Typecast the value to a String
__typecastValueString: function (value) {
return "" + value;
return "" + value;
}
},
* @property {Boolean} hasSelectSource true if this dataset already has a select sources.
*/
constructor:function (db, opts) {
this._super(arguments);
this.db = db;
this.__opts = {};
this.__rowCb = null;
if (db) {
this._super(arguments);
this.db = db;
this.__opts = {};
this.__rowCb = null;
if (db) {
this.__quoteIdentifiers = db.quoteIdentifiers;
this.__identifierInputMethod = db.identifierInputMethod;
this.__identifierOutputMethod = db.identifierOutputMethod;
* </ul>
*/
_splitString:function (s) {
var ret, m;
if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
var ret, m;
if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
ret = m.slice(1);
}
else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
ret = [null, m[1], m[2]];
}
else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
ret = [m[1], m[2], null];
}
else {
ret = [null, s, null];
ret = [null, s, null];
}
return ret;
return ret;
},
/**
},
firstSourceAlias:function () {
var source = this.__opts.from;
if (isUndefinedOrNull(source) || !source.length) {
var source = this.__opts.from;
if (isUndefinedOrNull(source) || !source.length) {
throw new DatasetError("No source specified for the query");
}
source = source[0];
if (isInstanceOf(source, AliasedExpression)) {
source = source[0];
if (isInstanceOf(source, AliasedExpression)) {
return source.alias;
} else if (isString(source)) {
} else if (isString(source)) {
var parts = this._splitString(source);
var alias = parts[2];
return alias ? alias : source;
} else {
return source;
return source;
}
},
var MODELS = new HashTable();
var applyColumnTransformMethod = function (val, meth) {
return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val;
};
var checkAndTransformName = function (name) {
return isString(name) ? applyColumnTransformMethod(name, Model.camelize === true ? "camelize" : Model.underscore === true ? "underscore" : null) : name;
return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val;
};
var Model = define([QueryPlugin, Middleware], {
* @borrows patio.Dataset#leftJoin as leftJoin
* */
constructor: function (options, fromDb) {
if (this.synced) {
this.__emitter = new EventEmitter();
this._super(arguments);
this.patio = patio || require("./index");
fromDb = isBoolean(fromDb) ? fromDb : false;
this.__changed = {};
this.__values = {};
if (fromDb) {
this._hook("pre", "load");
this.__isNew = false;
this.__setFromDb(options, true);
if (this._static.emitOnLoad) {
this.emit("load", this);
this._static.emit("load", this);
if (this.synced) {
this.__emitter = new EventEmitter();
this._super(arguments);
this.patio = patio || require("./index");
fromDb = isBoolean(fromDb) ? fromDb : false;
this.__changed = {};
this.__values = {};
if (fromDb) {
this._hook("pre", "load");
this.__isNew = false;
this.__setFromDb(options, true);
if (this._static.emitOnLoad) {
this.emit("load", this);
this._static.emit("load", this);
}
} else {
this.__isNew = true;
this.__set(options);
this.__isNew = true;
this.__set(options);
}
} else {
throw new ModelError("Model " + this.tableName + " has not been synced");
},
__set: function (values, ignore) {
values = values || {};
this.__ignore = ignore === true;
Object.keys(values).forEach(function (attribute) {
values = values || {};
this.__ignore = ignore === true;
Object.keys(values).forEach(function (attribute) {
var value = values[attribute];
//check if the column is a constrained value and is allowed to be set
!ignore && this._checkIfColumnIsConstrained(attribute);
this[attribute] = value;
}, this);
this.__ignore = false;
this.__ignore = false;
},
__setFromDb: function (values, ignore) {
values = values || {};
this.__ignore = ignore === true;
var schema = this.schema;
Object.keys(values).forEach(function (column) {
var value = values[column];
values = values || {};
this.__ignore = ignore === true;
var schema = this.schema;
Object.keys(values).forEach(function (column) {
var value = values[column];
// Typecast value retrieved from db
if (schema.hasOwnProperty(column)) {
this.__values[column] = this._typeCastValue(column, value, ignore);
if (schema.hasOwnProperty(column)) {
this.__values[column] = this._typeCastValue(column, value, ignore);
} else {
this[column] = value;
this[column] = value;
}
}, this);
this.__ignore = false;
this.__ignore = false;
},
},
_setColumnValue: function (name, val) {
var ignore = this.__ignore;
val = this._typeCastValue(name, val, ignore);
var ignore = this.__ignore;
val = this._typeCastValue(name, val, ignore);
var setterFunc = this["_set" + name.charAt(0).toUpperCase() + name.substr(1)];
var columnValue = isFunction(setterFunc) ? setterFunc.call(this, val, ignore) : val;
this._addColumnToIsChanged(name, columnValue);
//typecast_value method, so database adapters can override/augment the handling
//for database specific column types.
_typeCastValue: function (column, value, fromDatabase) {
var colSchema, clazz = this._static;
if (((fromDatabase && clazz.typecastOnLoad) || (!fromDatabase && clazz.typecastOnAssignment)) && !isUndefinedOrNull(this.schema) && !isUndefinedOrNull((colSchema = this.schema[column]))) {
var type = colSchema.type;
if (value === "" && clazz.typecastEmptyStringToNull === true && !isUndefinedOrNull(type) && ["string", "blob"].indexOf(type) === -1) {
var colSchema, clazz = this._static;
if (((fromDatabase && clazz.typecastOnLoad) || (!fromDatabase && clazz.typecastOnAssignment)) && !isUndefinedOrNull(this.schema) && !isUndefinedOrNull((colSchema = this.schema[column]))) {
var type = colSchema.type;
if (value === "" && clazz.typecastEmptyStringToNull === true && !isUndefinedOrNull(type) && ["string", "blob"].indexOf(type) === -1) {
value = null;
}
var raiseOnError = clazz.raiseOnTypecastError;
if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) {
throw new ModelError("null is not allowed for the " + column + " column.");
var raiseOnError = clazz.raiseOnTypecastError;
if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) {
throw new ModelError("null is not allowed for the " + column + " column on model " + clazz.tableName);
}
try {
value = clazz.db.typecastValue(type, value);
try {
value = clazz.db.typecastValue(type, value);
} catch (e) {
if (raiseOnError === true) {
throw e;
}
}
}
return value;
return value;
},
/**
return emitter.listeners.apply(emitter, arguments);
},
emit: function () {
var emitter = this.__emitter;
return emitter.emit.apply(emitter, arguments);
var emitter = this.__emitter;
return emitter.emit.apply(emitter, arguments);
},
},
schema: function () {
return this._static.schema;
return this._static.schema;
},
columns: function () {
},
synced: function () {
return this._static.synced;
return this._static.synced;
}
}
return when(supers.map(function (sup) {
return sup.sync();
})).chain(function () {
self.synced = true;
supers.forEach(self.inherits, self);
return self;
});
self.synced = true;
supers.forEach(self.inherits, self);
return self;
});
} else {
self.synced = true;
return self;
var retVal = null, errored = false, self = this;
return this.sync().chain(function () {
if (self.useTransaction(opts)) {
return self.db.transaction(opts,function () {
return self.db.transaction(opts, function () {
return when(cb()).chain(function (val) {
retVal = val;
}, function (err) {
errored = true;
});
}).chain(function () {
if (errored) {
throw retVal;
} else {
return retVal;
}
}, function (err) {
if (errored) {
throw retVal;
} else {
throw err;
}
});
if (errored) {
throw retVal;
} else {
return retVal;
}
}, function (err) {
if (errored) {
throw retVal;
} else {
throw err;
}
});
} else {
return when(cb());
}
_defineColumnSetter: function (name) {
/*Adds a setter to an object*/
this.prototype.__defineSetter__(name, function (val) {
this._setColumnValue(name, val);
this._setColumnValue(name, val);
});
},
* @type {Boolean}
*/
camelize: function (camelize) {
return this.__camelize;
return this.__camelize;
},
* @type {Boolean}
*/
underscore: function (underscore) {
return this.__underscore;
return this.__underscore;
},
* @type String
*/
tableName: function () {
return this.__tableName;
return this.__tableName;
},
/**
* @type patio.Database
*/
db: function () {
var db = this.__db;
if (!db) {
var db = this.__db;
if (!db) {
db = this.__db = patio.defaultDatabase;
}
if (!db) {
if (!db) {
throw new ModelError("patio has not been connected to a database");
}
return db;
return db;
},
/**
* @type Object
*/
schema: function () {
if (this.synced) {
return this.__schema;
if (this.synced) {
return this.__schema;
} else {
throw new ModelError("Model has not been synced yet");
}
}
}
var allModels = [];
/**@ignore*/
exports.create = function (name, supers, modelOptions) {
if (!patio) {
(patio = require("./index"));
patio.on("disconnect", function () {
allModels.length = 0;
MODELS.clear();
});
}
var db, ds, tableName;
var key, modelKey;
var db, ds, tableName, modelKey;
if (isString(name)) {
tableName = name;
key = db = patio.defaultDatabase || "default";
db = patio.defaultDatabase || "default";
} else if (isInstanceOf(name, patio.Dataset)) {
ds = name;
tableName = ds.firstSourceAlias;
key = db = ds.db;
tableName = ds.firstSourceAlias.toString();
db = ds.db;
}
var hasSuper = false;
if (isHash(supers) || isUndefinedOrNull(supers)) {
}
var model;
checkAndAddDBToTable(key, MODELS);
checkAndAddDBToTable(db, MODELS);
var DEFAULT_PROTO = {instance: {}, "static": {}};
modelOptions = merge(DEFAULT_PROTO, modelOptions || {});
}
}
});
if (!(MODELS.get(key).contains(checkAndTransformName(name)))) {
MODELS.get(key).set(name, model);
allModels.push(model);
if (!(MODELS.get(db).contains(tableName))) {
MODELS.get(db).set(tableName, model);
}
return model;
};
exports.syncModels = function (cb) {
return asyncArray(MODELS.entrySet).map(function (entry) {
var value = entry.value;
return asyncArray(value.entrySet).map(function (m) {
return m.value.sync();
}, 1);
return asyncArray(allModels).forEach(function (model) {
return model.sync();
}, 1).classic(cb).promise();
};
var checkAndGetModel = function (db, name) {
var ret;
if (MODELS.contains(db)) {
ret = MODELS.get(db).get(checkAndTransformName(name));
var ret;
if (MODELS.contains(db)) {
ret = MODELS.get(db).get(name);
}
return ret;
return ret;
};
exports.getModel = function (name, db) {
var ret = null;
if (isDefined(name)) {
!patio && (patio = require("./index"));
if (isFunction(name)) {
var ret = null;
if (isDefined(name)) {
!patio && (patio = require("./index"));
if (isFunction(name)) {
ret = name;
} else {
if (!db && isInstanceOf(name, patio.Dataset)) {
if (!db && isInstanceOf(name, patio.Dataset)) {
db = name.db;
name = name.firstSourceAlias.toString();
}
var defaultDb = patio.defaultDatabase;
if (db) {
ret = checkAndGetModel(db, name);
if (!ret && db === defaultDb) {
var defaultDb = patio.defaultDatabase;
if (db) {
ret = checkAndGetModel(db, name);
if (!ret && db === defaultDb) {
ret = checkAndGetModel("default", name);
}
} else {
} else {
ret = name;
}
if (isUndefinedOrNull(ret)) {
if (isUndefinedOrNull(ret)) {
throw new ModelError("Model " + name + " has not been registered with patio");
}
return ret;
return ret;
};
var virtualRow = function (name) {
var DOUBLE_UNDERSCORE = '__';
var DOUBLE_UNDERSCORE = '__';
var parts = name.split(DOUBLE_UNDERSCORE);
var table = parts[0], column = parts[1];
var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
var prox = methodMissing(ident, function (m) {
var parts = name.split(DOUBLE_UNDERSCORE);
var table = parts[0], column = parts[1];
var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
var prox = methodMissing(ident, function (m) {
return function () {
var args = argsToArray(arguments);
return SQLFunction.fromArgs([m, name].concat(args));
};
}, column ? QualifiedIdentifier : Identifier);
var ret = createFunctionWrapper(prox, function (m) {
var ret = createFunctionWrapper(prox, function (m) {
var args = argsToArray(arguments);
if (args.length) {
return SQLFunction.fromArgs([name].concat(args));
}, function () {
return SQLFunction.fromArgs(arguments);
});
ret["__proto__"] = ident;
return ret;
ret["__proto__"] = ident;
return ret;
};
var DATE_METHODS = ["getDate", "getDay", "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds",
});
exports.sql = methodMissing(sql, function (name) {
return virtualRow(name);
return virtualRow(name);
});
var OPERTATOR_INVERSIONS = {
* @return {patio.sql.Expression} an expression.
*/
fromArgs: function (args) {
var ret, Self = this;
try {
ret = new Self();
var ret, Self = this;
try {
ret = new Self();
} catch (ignore) {
}
this.apply(ret, args);
return ret;
this.apply(ret, args);
return ret;
},
/**
* @property {String} value <b>READ ONLY</b> the column or table this identifier represents.
*/
constructor: function (value) {
this.__value = value;
this.__value = value;
},
/**
* @return String the SQL version of the {@link patio.sql.Identifier}.
*/
toString: function (ds) {
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.quoteIdentifier(this);
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.quoteIdentifier(this);
},
/**@ignore*/
getters: {
value: function () {
return this.__value;
return this.__value;
}
}
}
constructor: function () {
//We initialize these here because otherwise
//the will be blank because of recursive dependencies.
!patio && (patio = require("../index"));
!Dataset && (Dataset = patio.Dataset);
this.outputIdentifier = hitch(this, this.outputIdentifier);
this._super(arguments);
!patio && (patio = require("../index"));
!Dataset && (Dataset = patio.Dataset);
this.outputIdentifier = hitch(this, this.outputIdentifier);
this._super(arguments);
},
/**
* quote the name with {@link patio.dataset._Sql#_quotedIdentifier}.
*/
quoteIdentifier: function (name) {
if (isInstanceOf(name, LiteralString)) {
if (isInstanceOf(name, LiteralString)) {
return name;
} else {
if (isInstanceOf(name, Identifier)) {
name = name.value;
if (isInstanceOf(name, Identifier)) {
name = name.value;
}
name = this.inputIdentifier(name);
if (this.quoteIdentifiers) {
name = this.inputIdentifier(name);
if (this.quoteIdentifiers) {
name = this._quotedIdentifier(name)
}
}
return name;
return name;
},
/**
* identifierOutputMethod.
*/
inputIdentifier: function (v) {
var i = this.__identifierInputMethod;
v = v.toString(this);
return !isUndefinedOrNull(i) ?
var i = this.__identifierInputMethod;
v = v.toString(this);
return !isUndefinedOrNull(i) ?
isFunction(v[i]) ?
v[i]() :
isFunction(comb[i]) ?
schemaAndTable: function (tableName) {
var sch = this.db ? this.db.defaultSchema || null : null;
if (isString(tableName)) {
var parts = this._splitString(tableName);
var s = parts[0], table = parts[1];
return [s || sch, table];
} else if (isInstanceOf(tableName, QualifiedIdentifier)) {
var parts = this._splitString(tableName);
var s = parts[0], table = parts[1];
return [s || sch, table];
} else if (isInstanceOf(tableName, QualifiedIdentifier)) {
return [tableName.table, tableName.column]
} else if (isInstanceOf(tableName, Identifier)) {
return [null, tableName.value];
} else if (isInstanceOf(tableName, Identifier)) {
return [null, tableName.value];
} else {
throw new QueryError("table should be a QualifiedIdentifier, Identifier, or String");
}
* @param {String} message the message to show.
*/
patio.ModelError = function(message) {
return new Error("Model error : " + message);
return new Error("Model error : " + message);
};
/**
/**@ignore*/
constructor: function () {
if (!Dataset) {
if (!Dataset) {
Dataset = require("../index").Dataset;
}
this._super(arguments);
this._super(arguments);
},
*
*/
constructor: function () {
if (comb.isUndefinedOrNull(this.__associations)) {
this.__associations = {};
if (comb.isUndefinedOrNull(this.__associations)) {
this.__associations = {};
}
this._super(arguments);
this._super(arguments);
},
reload: function () {
* @ignore
*/
constructor: function () {
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
if (!this[type + "Join"]) {
this[type + "Join"] = hitch(this, conditionedJoin, type);
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
if (!this[type + "Join"]) {
this[type + "Join"] = hitch(this, conditionedJoin, type);
}
}, this);
this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
if (!this[type + "Join"]) {
this[type + "Join"] = hitch(this, unConditionJoin, type);
this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
if (!this[type + "Join"]) {
this[type + "Join"] = hitch(this, unConditionJoin, type);
}
}, this);
* @ignore
*/
constructor: function () {
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
},
/**
self.logDebug("Duration: % 6dms; %s", new Date() - start, sql);
spreadArgs(ret.callback, arguments);
}, function (err) {
err = new QueryError(format("%s: %s", err.message, sql));
var details = err.detail;
err = new QueryError(format("%s%s: %s", err.message, details ? " DETAIL('" + details + "')" : "", sql));
self.logError(err);
ret.errback(err);
}).addErrback(ret.errback);
// Whether this dataset quotes identifiers.
/**@ignore*/
quoteIdentifiers: function () {
return this.__quoteIdentifiers;
return this.__quoteIdentifiers;
},
// Whether this dataset will provide accurate number of rows matched for
getModel
call where a table with the same name is added twice. try {
this.connection.setMaxListeners(0);
var fields = [];
query = new QueryStream(query);
query = new QueryStream(query, null, {batchSize: 1, highWaterMark: 1});
ret = this.connection.query(query);
var orig = ret.handleRowDescription;
ret.handleRowDescription = function (msg) {
* different databases.
*/
getModel: function (name, db) {
return model.getModel(name, db);
return model.getModel(name, db);
},
/**
* @default []
*/
DATABASES: function () {
return Database.DATABASES;
return Database.DATABASES;
},
/**
* Returns the default database. This is the first database created using {@link patio#connect}.
* @default null
*/
defaultDatabase: function () {
return this.DATABASES.length ? this.DATABASES[0] : null;
return this.DATABASES.length ? this.DATABASES[0] : null;
},
/**@ignore*/
Database: function () {
* @return the typecasted value.
* */
typecastValue: function (columnType, value) {
if (isNull(value) || isUndefined(value)) {
if (isNull(value) || isUndefined(value)) {
return null;
}
var meth = "__typecastValue" + columnType.charAt(0).toUpperCase() + columnType.substr(1).toLowerCase();
try {
if (isFunction(this[meth])) {
return this[meth](value);
var meth = "__typecastValue" + columnType.charAt(0).toUpperCase() + columnType.substr(1).toLowerCase();
try {
if (isFunction(this[meth])) {
return this[meth](value);
} else {
return value;
}
// Typecast the value to a String
__typecastValueString: function (value) {
return "" + value;
return "" + value;
}
},
* @property {Boolean} hasSelectSource true if this dataset already has a select sources.
*/
constructor:function (db, opts) {
this._super(arguments);
this.db = db;
this.__opts = {};
this.__rowCb = null;
if (db) {
this._super(arguments);
this.db = db;
this.__opts = {};
this.__rowCb = null;
if (db) {
this.__quoteIdentifiers = db.quoteIdentifiers;
this.__identifierInputMethod = db.identifierInputMethod;
this.__identifierOutputMethod = db.identifierOutputMethod;
* </ul>
*/
_splitString:function (s) {
var ret, m;
if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
var ret, m;
if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
ret = m.slice(1);
}
else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
ret = [null, m[1], m[2]];
}
else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
ret = [m[1], m[2], null];
}
else {
ret = [null, s, null];
ret = [null, s, null];
}
return ret;
return ret;
},
/**
},
firstSourceAlias:function () {
var source = this.__opts.from;
if (isUndefinedOrNull(source) || !source.length) {
var source = this.__opts.from;
if (isUndefinedOrNull(source) || !source.length) {
throw new DatasetError("No source specified for the query");
}
source = source[0];
if (isInstanceOf(source, AliasedExpression)) {
source = source[0];
if (isInstanceOf(source, AliasedExpression)) {
return source.alias;
} else if (isString(source)) {
} else if (isString(source)) {
var parts = this._splitString(source);
var alias = parts[2];
return alias ? alias : source;
} else {
return source;
return source;
}
},
var MODELS = new HashTable();
var applyColumnTransformMethod = function (val, meth) {
return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val;
};
var checkAndTransformName = function (name) {
return isString(name) ? applyColumnTransformMethod(name, Model.camelize === true ? "camelize" : Model.underscore === true ? "underscore" : null) : name;
return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val;
};
var Model = define([QueryPlugin, Middleware], {
* @borrows patio.Dataset#leftJoin as leftJoin
* */
constructor: function (options, fromDb) {
if (this.synced) {
this.__emitter = new EventEmitter();
this._super(arguments);
this.patio = patio || require("./index");
fromDb = isBoolean(fromDb) ? fromDb : false;
this.__changed = {};
this.__values = {};
if (fromDb) {
this._hook("pre", "load");
this.__isNew = false;
this.__setFromDb(options, true);
if (this._static.emitOnLoad) {
this.emit("load", this);
this._static.emit("load", this);
if (this.synced) {
this.__emitter = new EventEmitter();
this._super(arguments);
this.patio = patio || require("./index");
fromDb = isBoolean(fromDb) ? fromDb : false;
this.__changed = {};
this.__values = {};
if (fromDb) {
this._hook("pre", "load");
this.__isNew = false;
this.__setFromDb(options, true);
if (this._static.emitOnLoad) {
this.emit("load", this);
this._static.emit("load", this);
}
} else {
this.__isNew = true;
this.__set(options);
this.__isNew = true;
this.__set(options);
}
} else {
throw new ModelError("Model " + this.tableName + " has not been synced");
},
__set: function (values, ignore) {
values = values || {};
this.__ignore = ignore === true;
Object.keys(values).forEach(function (attribute) {
values = values || {};
this.__ignore = ignore === true;
Object.keys(values).forEach(function (attribute) {
var value = values[attribute];
//check if the column is a constrained value and is allowed to be set
!ignore && this._checkIfColumnIsConstrained(attribute);
this[attribute] = value;
}, this);
this.__ignore = false;
this.__ignore = false;
},
__setFromDb: function (values, ignore) {
values = values || {};
this.__ignore = ignore === true;
var schema = this.schema;
Object.keys(values).forEach(function (column) {
var value = values[column];
values = values || {};
this.__ignore = ignore === true;
var schema = this.schema;
Object.keys(values).forEach(function (column) {
var value = values[column];
// Typecast value retrieved from db
if (schema.hasOwnProperty(column)) {
this.__values[column] = this._typeCastValue(column, value, ignore);
if (schema.hasOwnProperty(column)) {
this.__values[column] = this._typeCastValue(column, value, ignore);
} else {
this[column] = value;
this[column] = value;
}
}, this);
this.__ignore = false;
this.__ignore = false;
},
},
_setColumnValue: function (name, val) {
var ignore = this.__ignore;
val = this._typeCastValue(name, val, ignore);
var ignore = this.__ignore;
val = this._typeCastValue(name, val, ignore);
var setterFunc = this["_set" + name.charAt(0).toUpperCase() + name.substr(1)];
var columnValue = isFunction(setterFunc) ? setterFunc.call(this, val, ignore) : val;
this._addColumnToIsChanged(name, columnValue);
//typecast_value method, so database adapters can override/augment the handling
//for database specific column types.
_typeCastValue: function (column, value, fromDatabase) {
var colSchema, clazz = this._static;
if (((fromDatabase && clazz.typecastOnLoad) || (!fromDatabase && clazz.typecastOnAssignment)) && !isUndefinedOrNull(this.schema) && !isUndefinedOrNull((colSchema = this.schema[column]))) {
var type = colSchema.type;
if (value === "" && clazz.typecastEmptyStringToNull === true && !isUndefinedOrNull(type) && ["string", "blob"].indexOf(type) === -1) {
var colSchema, clazz = this._static;
if (((fromDatabase && clazz.typecastOnLoad) || (!fromDatabase && clazz.typecastOnAssignment)) && !isUndefinedOrNull(this.schema) && !isUndefinedOrNull((colSchema = this.schema[column]))) {
var type = colSchema.type;
if (value === "" && clazz.typecastEmptyStringToNull === true && !isUndefinedOrNull(type) && ["string", "blob"].indexOf(type) === -1) {
value = null;
}
var raiseOnError = clazz.raiseOnTypecastError;
if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) {
throw new ModelError("null is not allowed for the " + column + " column.");
var raiseOnError = clazz.raiseOnTypecastError;
if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) {
throw new ModelError("null is not allowed for the " + column + " column on model " + clazz.tableName);
}
try {
value = clazz.db.typecastValue(type, value);
try {
value = clazz.db.typecastValue(type, value);
} catch (e) {
if (raiseOnError === true) {
throw e;
}
}
}
return value;
return value;
},
/**
return emitter.listeners.apply(emitter, arguments);
},
emit: function () {
var emitter = this.__emitter;
return emitter.emit.apply(emitter, arguments);
var emitter = this.__emitter;
return emitter.emit.apply(emitter, arguments);
},
},
schema: function () {
return this._static.schema;
return this._static.schema;
},
columns: function () {
},
synced: function () {
return this._static.synced;
return this._static.synced;
}
}
return when(supers.map(function (sup) {
return sup.sync();
})).chain(function () {
self.synced = true;
supers.forEach(self.inherits, self);
return self;
});
self.synced = true;
supers.forEach(self.inherits, self);
return self;
});
} else {
self.synced = true;
return self;
var retVal = null, errored = false, self = this;
return this.sync().chain(function () {
if (self.useTransaction(opts)) {
return self.db.transaction(opts,function () {
return self.db.transaction(opts, function () {
return when(cb()).chain(function (val) {
retVal = val;
}, function (err) {
errored = true;
});
}).chain(function () {
if (errored) {
throw retVal;
} else {
return retVal;
}
}, function (err) {
if (errored) {
throw retVal;
} else {
throw err;
}
});
if (errored) {
throw retVal;
} else {
return retVal;
}
}, function (err) {
if (errored) {
throw retVal;
} else {
throw err;
}
});
} else {
return when(cb());
}
_defineColumnSetter: function (name) {
/*Adds a setter to an object*/
this.prototype.__defineSetter__(name, function (val) {
this._setColumnValue(name, val);
this._setColumnValue(name, val);
});
},
* @type {Boolean}
*/
camelize: function (camelize) {
return this.__camelize;
return this.__camelize;
},
* @type {Boolean}
*/
underscore: function (underscore) {
return this.__underscore;
return this.__underscore;
},
* @type String
*/
tableName: function () {
return this.__tableName;
return this.__tableName;
},
/**
* @type patio.Database
*/
db: function () {
var db = this.__db;
if (!db) {
var db = this.__db;
if (!db) {
db = this.__db = patio.defaultDatabase;
}
if (!db) {
if (!db) {
throw new ModelError("patio has not been connected to a database");
}
return db;
return db;
},
/**
* @type Object
*/
schema: function () {
if (this.synced) {
return this.__schema;
if (this.synced) {
return this.__schema;
} else {
throw new ModelError("Model has not been synced yet");
}
}
}
var allModels = [];
/**@ignore*/
exports.create = function (name, supers, modelOptions) {
if (!patio) {
(patio = require("./index"));
patio.on("disconnect", function () {
allModels.length = 0;
MODELS.clear();
});
}
var db, ds, tableName;
var key, modelKey;
var db, ds, tableName, modelKey;
if (isString(name)) {
tableName = name;
key = db = patio.defaultDatabase || "default";
db = patio.defaultDatabase || "default";
} else if (isInstanceOf(name, patio.Dataset)) {
ds = name;
tableName = ds.firstSourceAlias;
key = db = ds.db;
tableName = ds.firstSourceAlias.toString();
db = ds.db;
}
var hasSuper = false;
if (isHash(supers) || isUndefinedOrNull(supers)) {
}
var model;
checkAndAddDBToTable(key, MODELS);
checkAndAddDBToTable(db, MODELS);
var DEFAULT_PROTO = {instance: {}, "static": {}};
modelOptions = merge(DEFAULT_PROTO, modelOptions || {});
}
}
});
if (!(MODELS.get(key).contains(checkAndTransformName(name)))) {
MODELS.get(key).set(name, model);
allModels.push(model);
if (!(MODELS.get(db).contains(tableName))) {
MODELS.get(db).set(tableName, model);
}
return model;
};
exports.syncModels = function (cb) {
return asyncArray(MODELS.entrySet).map(function (entry) {
var value = entry.value;
return asyncArray(value.entrySet).map(function (m) {
return m.value.sync();
}, 1);
return asyncArray(allModels).forEach(function (model) {
return model.sync();
}, 1).classic(cb).promise();
};
var checkAndGetModel = function (db, name) {
var ret;
if (MODELS.contains(db)) {
ret = MODELS.get(db).get(checkAndTransformName(name));
var ret;
if (MODELS.contains(db)) {
ret = MODELS.get(db).get(name);
}
return ret;
return ret;
};
exports.getModel = function (name, db) {
var ret = null;
if (isDefined(name)) {
!patio && (patio = require("./index"));
if (isFunction(name)) {
var ret = null;
if (isDefined(name)) {
!patio && (patio = require("./index"));
if (isFunction(name)) {
ret = name;
} else {
if (!db && isInstanceOf(name, patio.Dataset)) {
if (!db && isInstanceOf(name, patio.Dataset)) {
db = name.db;
name = name.firstSourceAlias.toString();
}
var defaultDb = patio.defaultDatabase;
if (db) {
ret = checkAndGetModel(db, name);
if (!ret && db === defaultDb) {
var defaultDb = patio.defaultDatabase;
if (db) {
ret = checkAndGetModel(db, name);
if (!ret && db === defaultDb) {
ret = checkAndGetModel("default", name);
}
} else {
} else {
ret = name;
}
if (isUndefinedOrNull(ret)) {
if (isUndefinedOrNull(ret)) {
throw new ModelError("Model " + name + " has not been registered with patio");
}
return ret;
return ret;
};
var virtualRow = function (name) {
var DOUBLE_UNDERSCORE = '__';
var DOUBLE_UNDERSCORE = '__';
var parts = name.split(DOUBLE_UNDERSCORE);
var table = parts[0], column = parts[1];
var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
var prox = methodMissing(ident, function (m) {
var parts = name.split(DOUBLE_UNDERSCORE);
var table = parts[0], column = parts[1];
var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
var prox = methodMissing(ident, function (m) {
return function () {
var args = argsToArray(arguments);
return SQLFunction.fromArgs([m, name].concat(args));
};
}, column ? QualifiedIdentifier : Identifier);
var ret = createFunctionWrapper(prox, function (m) {
var ret = createFunctionWrapper(prox, function (m) {
var args = argsToArray(arguments);
if (args.length) {
return SQLFunction.fromArgs([name].concat(args));
}, function () {
return SQLFunction.fromArgs(arguments);
});
ret["__proto__"] = ident;
return ret;
ret["__proto__"] = ident;
return ret;
};
var DATE_METHODS = ["getDate", "getDay", "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds",
});
exports.sql = methodMissing(sql, function (name) {
return virtualRow(name);
return virtualRow(name);
});
var OPERTATOR_INVERSIONS = {
* @return {patio.sql.Expression} an expression.
*/
fromArgs: function (args) {
var ret, Self = this;
try {
ret = new Self();
var ret, Self = this;
try {
ret = new Self();
} catch (ignore) {
}
this.apply(ret, args);
return ret;
this.apply(ret, args);
return ret;
},
/**
* @property {String} value <b>READ ONLY</b> the column or table this identifier represents.
*/
constructor: function (value) {
this.__value = value;
this.__value = value;
},
/**
* @return String the SQL version of the {@link patio.sql.Identifier}.
*/
toString: function (ds) {
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.quoteIdentifier(this);
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.quoteIdentifier(this);
},
/**@ignore*/
getters: {
value: function () {
return this.__value;
return this.__value;
}
}
}
constructor: function () {
//We initialize these here because otherwise
//the will be blank because of recursive dependencies.
!patio && (patio = require("../index"));
!Dataset && (Dataset = patio.Dataset);
this.outputIdentifier = hitch(this, this.outputIdentifier);
this._super(arguments);
!patio && (patio = require("../index"));
!Dataset && (Dataset = patio.Dataset);
this.outputIdentifier = hitch(this, this.outputIdentifier);
this._super(arguments);
},
/**
* quote the name with {@link patio.dataset._Sql#_quotedIdentifier}.
*/
quoteIdentifier: function (name) {
if (isInstanceOf(name, LiteralString)) {
if (isInstanceOf(name, LiteralString)) {
return name;
} else {
if (isInstanceOf(name, Identifier)) {
name = name.value;
if (isInstanceOf(name, Identifier)) {
name = name.value;
}
name = this.inputIdentifier(name);
if (this.quoteIdentifiers) {
name = this.inputIdentifier(name);
if (this.quoteIdentifiers) {
name = this._quotedIdentifier(name)
}
}
return name;
return name;
},
/**
* identifierOutputMethod.
*/
inputIdentifier: function (v) {
var i = this.__identifierInputMethod;
v = v.toString(this);
return !isUndefinedOrNull(i) ?
var i = this.__identifierInputMethod;
v = v.toString(this);
return !isUndefinedOrNull(i) ?
isFunction(v[i]) ?
v[i]() :
isFunction(comb[i]) ?
schemaAndTable: function (tableName) {
var sch = this.db ? this.db.defaultSchema || null : null;
if (isString(tableName)) {
var parts = this._splitString(tableName);
var s = parts[0], table = parts[1];
return [s || sch, table];
} else if (isInstanceOf(tableName, QualifiedIdentifier)) {
var parts = this._splitString(tableName);
var s = parts[0], table = parts[1];
return [s || sch, table];
} else if (isInstanceOf(tableName, QualifiedIdentifier)) {
return [tableName.table, tableName.column]
} else if (isInstanceOf(tableName, Identifier)) {
return [null, tableName.value];
} else if (isInstanceOf(tableName, Identifier)) {
return [null, tableName.value];
} else {
throw new QueryError("table should be a QualifiedIdentifier, Identifier, or String");
}
* @param {String} message the message to show.
*/
patio.ModelError = function(message) {
return new Error("Model error : " + message);
return new Error("Model error : " + message);
};
/**
/**@ignore*/
constructor: function () {
if (!Dataset) {
if (!Dataset) {
Dataset = require("../index").Dataset;
}
this._super(arguments);
this._super(arguments);
},
*
*/
constructor: function () {
if (comb.isUndefinedOrNull(this.__associations)) {
this.__associations = {};
if (comb.isUndefinedOrNull(this.__associations)) {
this.__associations = {};
}
this._super(arguments);
this._super(arguments);
},
reload: function () {
* @ignore
*/
constructor: function () {
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
if (!this[type + "Join"]) {
this[type + "Join"] = hitch(this, conditionedJoin, type);
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
if (!this[type + "Join"]) {
this[type + "Join"] = hitch(this, conditionedJoin, type);
}
}, this);
this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
if (!this[type + "Join"]) {
this[type + "Join"] = hitch(this, unConditionJoin, type);
this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
if (!this[type + "Join"]) {
this[type + "Join"] = hitch(this, unConditionJoin, type);
}
}, this);
* @ignore
*/
constructor: function () {
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
},
/**
self.logDebug("Duration: % 6dms; %s", new Date() - start, sql);
spreadArgs(ret.callback, arguments);
}, function (err) {
err = new QueryError(format("%s: %s", err.message, sql));
var details = err.detail;
err = new QueryError(format("%s%s: %s", err.message, details ? " DETAIL('" + details + "')" : "", sql));
self.logError(err);
ret.errback(err);
}).addErrback(ret.errback);
// Whether this dataset quotes identifiers.
/**@ignore*/
quoteIdentifiers: function () {
return this.__quoteIdentifiers;
return this.__quoteIdentifiers;
},
// Whether this dataset will provide accurate number of rows matched for
npm install comb patio
If you want to use the patio executable for migrations
npm install -g patio
To run the tests
+make test
+To run just the postgres tests
+make test-pg
+To run just the mysql tests
+make test-mysql
Patio is different because it allows the developers to choose the level of abtraction they are comfortable with.
If you want to use the ORM functionality you can. If you don't you can just use the Database and Datasets as a querying API, and if you need to you can write plain SQL
diff --git a/lib/model.js b/lib/model.js index 5e7d1a58..7e1b95e0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -33,10 +33,6 @@ var applyColumnTransformMethod = function (val, meth) { return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val; }; -var checkAndTransformName = function (name) { - return isString(name) ? applyColumnTransformMethod(name, Model.camelize === true ? "camelize" : Model.underscore === true ? "underscore" : null) : name; -}; - var Model = define([QueryPlugin, Middleware], { instance: { /** @@ -328,7 +324,7 @@ var Model = define([QueryPlugin, Middleware], { } var raiseOnError = clazz.raiseOnTypecastError; if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) { - throw new ModelError("null is not allowed for the " + column + " column."); + throw new ModelError("null is not allowed for the " + column + " column on model " + clazz.tableName); } try { value = clazz.db.typecastValue(type, value); @@ -628,10 +624,10 @@ var Model = define([QueryPlugin, Middleware], { return when(supers.map(function (sup) { return sup.sync(); })).chain(function () { - self.synced = true; - supers.forEach(self.inherits, self); - return self; - }); + self.synced = true; + supers.forEach(self.inherits, self); + return self; + }); } else { self.synced = true; return self; @@ -704,7 +700,7 @@ var Model = define([QueryPlugin, Middleware], { var retVal = null, errored = false, self = this; return this.sync().chain(function () { if (self.useTransaction(opts)) { - return self.db.transaction(opts,function () { + return self.db.transaction(opts, function () { return when(cb()).chain(function (val) { retVal = val; }, function (err) { @@ -712,18 +708,18 @@ var Model = define([QueryPlugin, Middleware], { errored = true; }); }).chain(function () { - if (errored) { - throw retVal; - } else { - return retVal; - } - }, function (err) { - if (errored) { - throw retVal; - } else { - throw err; - } - }); + if (errored) { + throw retVal; + } else { + return retVal; + } + }, function (err) { + if (errored) { + throw retVal; + } else { + throw err; + } + }); } else { return when(cb()); } @@ -1010,11 +1006,13 @@ function checkAndAddDBToTable(db, table) { } } +var allModels = []; /**@ignore*/ exports.create = function (name, supers, modelOptions) { if (!patio) { (patio = require("./index")); patio.on("disconnect", function () { + allModels.length = 0; MODELS.clear(); }); } @@ -1061,6 +1059,7 @@ exports.create = function (name, supers, modelOptions) { } } }); + allModels.push(model); if (!(MODELS.get(db).contains(tableName))) { MODELS.get(db).set(tableName, model); } @@ -1068,11 +1067,8 @@ exports.create = function (name, supers, modelOptions) { }; exports.syncModels = function (cb) { - return asyncArray(MODELS.entrySet).map(function (entry) { - var value = entry.value; - return asyncArray(value.entrySet).map(function (m) { - return m.value.sync(); - }, 1); + return asyncArray(allModels).forEach(function (model) { + return model.sync(); }, 1).classic(cb).promise(); }; diff --git a/package.json b/package.json index 306913e0..aeaee427 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "patio", "description": "Patio query engine and ORM", - "version": "0.6.1", + "version": "0.7.0", "keywords": [ "ORM", "object relation mapper", diff --git a/test/model.customAccessors.test.js b/test/model.customAccessors.test.js index 128ee30a..e05b85f1 100644 --- a/test/model.customAccessors.test.js +++ b/test/model.customAccessors.test.js @@ -5,7 +5,7 @@ var it = require('it'), comb = require("comb-proxy"); -it.describe("A model with custom accessors",function (it) { +it.describe("A model with custom accessors", function (it) { it.beforeAll(function () { return helper.createSchemaAndSync(); @@ -38,10 +38,7 @@ it.describe("A model with custom accessors",function (it) { } } }); - - return CustomSettersEmployee.sync().chain(function() { - return CustomGettersEmployee.sync(); - }); + return patio.syncModels(); }); it.beforeEach(function () { @@ -91,5 +88,4 @@ it.describe("A model with custom accessors",function (it) { return helper.dropModels(); }); -}); - +}); \ No newline at end of file diff --git a/test/model.test.js b/test/model.test.js index e619e67a..b245d347 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -437,10 +437,16 @@ it.describe("patio.Model", function (it) { assert.equal(m.firstname, "dougie"); }); + it.should("throw an error when setting a non nullable column to null", function () { + var m = new Employee({otherVal: "otherVal", firstname: "dougie"}, true); + try { + m.street = null; + } catch (e) { + assert.equal(e.message, "Model error : null is not allowed for the street column on model employee"); + } + }); + it.afterAll(function () { return helper.dropModels(); }); - - -}); - +}); \ No newline at end of file