Permalink
Browse files

Merge branch 'eager-assn'

  • Loading branch information...
2 parents a0f51a2 + b82c3c6 commit 0751c613276e4f143d1a76b40b930e5bb3515e75 mde committed Mar 29, 2013
View
@@ -1,4 +1,5 @@
-var Adapter
+var model = require('../../index')
+ , Adapter
, BaseAdapter = require('../base_adapter').BaseAdapter
, sql = require('../transformers/sql')
, datatypes = require('../../datatypes')
@@ -13,9 +14,114 @@ Adapter.prototype = new BaseAdapter();
utils.mixin(Adapter.prototype, new (function () {
this._tableizeModelName = function (name) {
- var tableName = utils.inflection.pluralize(name);
- tableName = utils.string.snakeize(tableName);
- return tableName;
+ return utils.string.getInflection(name, 'filename', 'plural');
+ };
+
+ this._modelizeTableName = function (name, ownerName) {
+ var modelName
+ , ownerModelName
+ modelName = utils.string.getInflection(name, 'constructor', 'singular');
+ if (ownerName && name != ownerName) {
+ ownerModelName = utils.string.getInflection(ownerName, 'constructor', 'singular');
+ modelName = model.getAssociation(ownerModelName, modelName).model;
+ }
+
+ return model[modelName] || null;
+ };
+
+ this._createSelectStatement = function (modelName, ownerModelName) {
+ var name
+ , assumedName = utils.string.getInflection(modelName, 'constructor', 'singular')
+ , ownerName
+ , tableName
+ , assumedTableName
+ , props
+ , propArr;
+
+ // Assumed name is a real model
+ if (model.descriptionRegistry[assumedName]) {
+ name = assumedName;
+ }
+ // Otherwise it's a named association, need to look up the
+ // actual model via it's owner's associations list
+ else {
+ ownerName = utils.string.getInflection(ownerModelName, 'constructor', 'singular');
+ name = model.getAssociation(ownerName, assumedName).model;
+ }
+
+ tableName = this._tableizeModelName(name);
+ assumedTableName = this._tableizeModelName(assumedName);
+ props = model.descriptionRegistry[name].properties;
+ propArr = [];
+ propArr.push(assumedTableName + '."id" AS "' + assumedTableName + '#id"');
+
+ for (var p in props) {
+ propArr.push(assumedTableName + '.' + this._columnizePropertyName(p) + ' AS "' +
+ assumedTableName + '#' + this._columnizePropertyName(p, {useQuotes: false}) + '"');
+ }
+ return propArr.join(', ');
+ };
+
+ this._createLeftOuterJoinStatement = function (mainModelName, assnModelName) {
+ var assn
+ , mainName
+ , mainTableName
+ , assnName
+ , assnModelName
+ , assnTableName
+ , assnModelTableName
+ , assnColName
+ , through
+ , throughTableName
+ , throughAssnColName
+ , sql;
+
+ mainName = utils.string.getInflection(mainModelName, 'constructor', 'singular');
+ mainTableName = this._tableizeModelName(mainName);
+ assnName = utils.string.getInflection(assnModelName, 'constructor', 'singular');
+ assn = model.getAssociation(mainName, assnName);
+ assnModelName = assn.model;
+ assnTableName = this._tableizeModelName(assnName);
+ assnModelTableName = this._tableizeModelName(assnModelName);
+
+ // Normal assn
+ if (assnName == assnModelName) {
+ assnColName = mainName + 'Id';
+ }
+ // Named assn, namespace the id
+ else {
+ assnColName = assnName + mainName + 'Id';
+ }
+ assnColName = this._columnizePropertyName(assnColName, {useQuotes: false});
+
+ // Through assn
+ // Ex., Baz model has association Foo through Bar
+ // LEFT OUTER JOIN bars ON (bazes."id" = bars."baz_id")
+ // LEFT OUTER JOIN foos foos ON (bars."foo_id" = foos."id")
+ through = assn.through;
+ if (through) {
+ throughTableName = this._tableizeModelName(through);
+ throughAssnColName =
+ utils.string.getInflection(assnModelName, 'property', 'singular') + 'Id';
+ throughAssnColName = this._columnizePropertyName(throughAssnColName,
+ {useQuotes: false});
+
+ sql = 'LEFT OUTER JOIN ' + throughTableName + ' ON (' +
+ mainTableName + '."id" = ' + throughTableName + '."' + assnColName + '")';
+ sql += ' ';
+ sql += 'LEFT OUTER JOIN ' + assnModelTableName + ' ' + assnTableName + ' ON (' +
+ throughTableName + '."' + throughAssnColName + '" = ' +
+ assnTableName + '."id")';
+ }
+ // Normal
+ // Ex., Baz model has named association Foo {model: Bar}
+ // LEFT OUTER JOIN bars foos ON (bazes."id" = foos."baz_id")
+ else {
+ sql = 'LEFT OUTER JOIN ' + assnModelTableName + ' ' + assnTableName + ' ON (' +
+ mainTableName + '."id" = ' + assnTableName + '.' + assnColName + ')';
+ }
+
+ return sql;
};
this._createInsertStatement = function (item, props, useAutoIncrementId) {
@@ -34,21 +34,85 @@ Adapter.prototype.constructor = Adapter;
utils.mixin(Adapter.prototype, new (function () {
- var _itemsWithSQL = function (sql, model, callback) {
+ var _itemsWithSQL = function (sql, tables, mainTable, callback) {
+ var self = this;
this.exec(sql, function (err, data) {
var res
- , rows;
+ , rows
+ , inst
+ , models = {}
+ , mainId;
+
if (err) {
callback(err, null);
}
else {
+ // Make a map of the constructors for each table-name
+ // Note that the table-name may be an alias for an association
+ // First item in this list should be the owner table for
+ // any subsequent associations, e.g., ['users', 'profiles']
+ tables.forEach(function (t) {
+ models[t] = {
+ ctor: self._modelizeTableName(t, mainTable)
+ , assnType: t == mainTable ? null : model.getAssociation(mainTable, t).type
+ }
+ });
+
rows = data.rows;
res = [];
+
rows.forEach(function (row) {
- var inst = model.create(row);
- inst.id = row.id;
- inst._saved = true;
- res.push(inst);
+ var obj = {}
+ , colArr
+ , table
+ , key;
+
+ // Create a column object with mapped params
+ // users#login: 'foo', users#name: 'adsf', profiles#nickname: 'qwer'
+ //{ users: {login: 'foo', name: 'asdf'}
+ //, profile: {nickname: 'qewr'}
+ //}
+ for (var p in row) {
+ colArr = p.split('#');
+ table = colArr[0];
+ key = colArr[1];
+ obj[table] = obj[table] || {}
+ obj[table][key] = row[p];
+ }
+
+ // Iterate over the list of table names, create a base owner object
+ // when a new id shows up. (Owner object record is repeated for multiple
+ // associations.) For each subsquent record, instantiate the association
+ // and append it to an array in the named property for that association
+ tables.forEach(function (p) {
+ var params = obj[p]
+ , modelItem = models[p];
+ // Owner table-name
+ if (p == mainTable) {
+ // This is a new id -- create an owner object
+ if (params.id != mainId) {
+ inst = modelItem.ctor.create(params);
+ inst._saved = true;
+ res.push(inst);
+ }
+ // Update to check against next record
+ mainId = params.id;
+ }
+ // Association table-name
+ else {
+ // Ignore empty records
+ if (params.id) {
+ // Create an array to hold the items if necessary
+ if (modelItem.assnType == 'hasMany') {
+ inst[p] = inst[p] || [];
+ inst[p].push(modelItem.ctor.create(params));
+ }
+ else {
+ inst[p] = modelItem.ctor.create(params);
+ }
+ }
+ }
+ });
});
callback(null, res);
}
@@ -81,33 +145,58 @@ utils.mixin(Adapter.prototype, new (function () {
};
this.load = function (query, callback) {
- var sql = ''
+ var self = this
+ , sql = ''
, conditions = this.transformConditions(query.conditions)
+ , tableName = this._tableizeModelName(query.model.modelName)
+ , selects
+ , includesTableName
+ , includesColName
, opts = query.opts
+ , includes = opts.includes
, sort = opts.sort
, limit = opts.limit
, skip = opts.skip;
- sql += 'SELECT * FROM ' + this._tableizeModelName(query.model.modelName);
+ selects = [tableName];
+ if (includes) {
+ includes = Array.isArray(includes) ? includes : [includes];
+ selects = selects.concat(includes);
+ }
+
+ sql += 'SELECT ';
+ sql += selects.map(function (item) {
+ return self._createSelectStatement(item, tableName);
+ }).join(', ');
sql += ' ';
+ sql += 'FROM ' + tableName;
+ if (includes) {
+ sql += ' ' + includes.map(function (item) {
+ return self._createLeftOuterJoinStatement(tableName, item);
+ }).join(' ');
+ }
if (conditions) {
+ sql += ' ';
sql += 'WHERE ' + conditions;
}
if (sort) {
+ sql += ' ';
sql += this.transformSortOrder(sort);
}
if (skip) {
- sql += ' OFFSET ' + skip;
+ sql += ' ';
+ sql += 'OFFSET ' + skip;
}
if (limit) {
- sql += ' LIMIT ' + limit;
+ sql += ' ';
+ sql += 'LIMIT ' + limit;
}
sql += ';'
- _itemsWithSQL.apply(this, [sql, query.model, function (err, res) {
+ _itemsWithSQL.apply(this, [sql, selects, tableName, function (err, res) {
// If explicitly limited to one, just return the single instance
// This is also used by the `first` method
- if (query.opts.limit == 1) {
+ if (res && query.opts.limit == 1) {
res = res[0];
}
callback(null, res);
@@ -119,7 +208,7 @@ utils.mixin(Adapter.prototype, new (function () {
var name = this._tableizeModelName(model.modelName)
, sql = 'SELECT ' + name + '.* FROM ' + name;
sql += ' ' + query;
- _itemsWithSQL.apply(this, [sql, model, callback]);
+ _itemsWithSQL.apply(this, [sql, [name], name, callback]);
};
this.update = function (data, query, callback) {
@@ -67,7 +67,10 @@ var sql = utils.mixin(new BaseTransformer(), new (function () {
};
this.transformComparisonFieldName = function (comp) {
- var name = this._columnizePropertyName(comp.field);
+ var name;
+ name = this._tableizeModelName(comp.model);
+ name += '.';
+ name += this._columnizePropertyName(comp.field);
if (comp.opts.nocase) {
name = 'LOWER(' + name + ')';
}
Oops, something went wrong.

0 comments on commit 0751c61

Please sign in to comment.