Skip to content

Commit

Permalink
Merge pull request #393 from Tape/eager-loading
Browse files Browse the repository at this point in the history
Implement eager loading
  • Loading branch information
dresende committed Nov 27, 2013
2 parents 0a5e1c9 + 129c904 commit d393b98
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 1 deletion.
47 changes: 46 additions & 1 deletion lib/ChainFind.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@ function ChainFind(Model, opts) {
}
return promise.fail(cb);
},
eager: function () {
// This will allow params such as ("abc", "def") or (["abc", "def"])
var associations = _.flatten(arguments);

// TODO: Implement eager loading for Mongo and delete this.
if (opts.driver.config.protocol == "mongodb:") {
throw new Error("MongoDB does not currently support eager loading");
}

opts.__eager = _.filter(opts.associations, function (association) {
return ~associations.indexOf(association.name);
});

return this;
},
all: function (cb) {
opts.driver.find(opts.only, opts.table, opts.conditions, {
limit : opts.limit,
Expand All @@ -153,8 +168,38 @@ function ChainFind(Model, opts) {
data[idx] = instance;

if (--pending === 0) {
return cb(null, data);
return (opts.__eager && opts.__eager.length ? eagerLoading : cb)(null, data);
}
});
};

var eagerLoading = function (err, data) {
var pending = opts.__eager.length;
var idMap = {};
var count = 0;

var ids = _.map(data, function (instance) {
var id = instance[opts.id[0]];
// Create the association arrays
for (var i = 0, association; association = opts.__eager[i]; i++) {
instance[association.name] = [];
}

idMap[id] = count++;
return id;
});

_.map(opts.__eager, function (association) {
opts.driver.eagerQuery(association, opts, ids, function (err, instances) {
for (var i = 0, instance; instance = instances[i]; i++) {
// Perform a parent lookup with $p, and initialize it as an instance.
data[idMap[instance.$p]][association.name].push(association.model(instance));
}

if (--pending === 0) {
return cb(null, data);
}
});
});
};

Expand Down
18 changes: 18 additions & 0 deletions lib/Drivers/DML/mysql.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,24 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) {
this.execSimpleQuery(q, cb);
};

Driver.prototype.eagerQuery = function (association, opts, ids, cb) {
var desiredKey = Object.keys(association.field);
var assocKey = Object.keys(association.mergeAssocId);

var where = {};
where[desiredKey] = ids;

var query = this.query.select()
.from(association.model.table)
.select(opts.only)
.from(association.mergeTable, assocKey, opts.id)
.select(desiredKey).as("$p")
.where(association.mergeTable, where)
.build();

this.execSimpleQuery(query, cb);
};

Driver.prototype.count = function (table, conditions, opts, cb) {
var q = this.query.select()
.from(table)
Expand Down
18 changes: 18 additions & 0 deletions lib/Drivers/DML/postgres.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,24 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) {
this.execSimpleQuery(q, cb);
};

Driver.prototype.eagerQuery = function (association, opts, ids, cb) {
var desiredKey = Object.keys(association.field);
var assocKey = Object.keys(association.mergeAssocId);

var where = {};
where[desiredKey] = ids;

var query = this.query.select()
.from(association.model.table)
.select(opts.only)
.from(association.mergeTable, assocKey, opts.id)
.select(desiredKey).as("$p")
.where(association.mergeTable, where)
.build();

this.execSimpleQuery(query, cb);
};

Driver.prototype.count = function (table, conditions, opts, cb) {
var q = this.query.select().from(table).count(null, 'c');

Expand Down
18 changes: 18 additions & 0 deletions lib/Drivers/DML/sqlite.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) {
this.db.all(q, cb);
};

Driver.prototype.eagerQuery = function (association, opts, ids, cb) {
var desiredKey = Object.keys(association.field);
var assocKey = Object.keys(association.mergeAssocId);

var where = {};
where[desiredKey] = ids;

var query = this.query.select()
.from(association.model.table)
.select(opts.only)
.from(association.mergeTable, assocKey, opts.id)
.select(desiredKey).as("$p")
.where(association.mergeTable, where)
.build();

this.execSimpleQuery(query, cb);
};

Driver.prototype.count = function (table, conditions, opts, cb) {
var q = this.query.select()
.from(table)
Expand Down
95 changes: 95 additions & 0 deletions test/integration/model-find-chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var common = require('../common');
describe("Model.find() chaining", function() {
var db = null;
var Person = null;
var Dog = null;

var setup = function () {
return function (done) {
Expand Down Expand Up @@ -36,6 +37,30 @@ describe("Model.find() chaining", function() {
};
};

var setup2 = function () {
return function (done) {
Dog = db.define("dog", {
name: String,
});
Dog.hasMany("friends");
Dog.hasMany("family");

ORM.singleton.clear(); // clear cache

return helper.dropSync(Dog, function () {
Dog.create([{
name : "Fido",
friends : [{ name: "Gunner" }, { name: "Chainsaw" }],
family : [{ name: "Chester" }]
}, {
name : "Thumper",
friends : [{ name: "Bambi" }],
family : [{ name: "Princess" }, { name: "Butch" }]
}], done);
});
};
};

before(function (done) {
helper.connect(function (connection) {
db = connection;
Expand Down Expand Up @@ -479,6 +504,76 @@ describe("Model.find() chaining", function() {
});
});

describe(".eager()", function () {
before(setup2());

// TODO: Remove this code once the Mongo eager loading is implemented
var isMongo = function () {
if (db.driver.config.protocol == "mongodb:") {
(function () {
Dog.find().eager("friends").all(function () {
// Should not ever run.
});
}).should.throw();

return true;
}
return false;
};

it("should fetch all listed associations in a single query", function (done) {
if (isMongo()) { return done(); };

Dog.find({ name: ["Fido", "Thumper"] }).eager("friends").all(function (err, dogs) {
should.equal(err, null);

should(Array.isArray(dogs));

dogs.length.should.equal(2);

dogs[0].friends.length.should.equal(2);
dogs[1].friends.length.should.equal(1);
done();
});
});

it("should be able to handle multiple associations", function (done) {
if (isMongo()) { return done(); };

Dog.find({ name: ["Fido", "Thumper"] }).eager("friends", "family").all(function (err, dogs) {
should.equal(err, null);

should(Array.isArray(dogs));

dogs.length.should.equal(2);

dogs[0].friends.length.should.equal(2);
dogs[0].family.length.should.equal(1);
dogs[1].friends.length.should.equal(1);
dogs[1].family.length.should.equal(2);
done();
});
});

it("should work with array parameters too", function (done) {
if (isMongo()) { return done(); };

Dog.find({ name: ["Fido", "Thumper"] }).eager(["friends", "family"]).all(function (err, dogs) {
should.equal(err, null);

should(Array.isArray(dogs));

dogs.length.should.equal(2);

dogs[0].friends.length.should.equal(2);
dogs[0].family.length.should.equal(1);
dogs[1].friends.length.should.equal(1);
dogs[1].family.length.should.equal(2);
done();
});
});
});

describe(".success()", function () {
before(setup());

Expand Down

0 comments on commit d393b98

Please sign in to comment.