Skip to content

Commit d393b98

Browse files
committed
Merge pull request #393 from Tape/eager-loading
Implement eager loading
2 parents 0a5e1c9 + 129c904 commit d393b98

File tree

5 files changed

+195
-1
lines changed

5 files changed

+195
-1
lines changed

lib/ChainFind.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,21 @@ function ChainFind(Model, opts) {
132132
}
133133
return promise.fail(cb);
134134
},
135+
eager: function () {
136+
// This will allow params such as ("abc", "def") or (["abc", "def"])
137+
var associations = _.flatten(arguments);
138+
139+
// TODO: Implement eager loading for Mongo and delete this.
140+
if (opts.driver.config.protocol == "mongodb:") {
141+
throw new Error("MongoDB does not currently support eager loading");
142+
}
143+
144+
opts.__eager = _.filter(opts.associations, function (association) {
145+
return ~associations.indexOf(association.name);
146+
});
147+
148+
return this;
149+
},
135150
all: function (cb) {
136151
opts.driver.find(opts.only, opts.table, opts.conditions, {
137152
limit : opts.limit,
@@ -153,8 +168,38 @@ function ChainFind(Model, opts) {
153168
data[idx] = instance;
154169

155170
if (--pending === 0) {
156-
return cb(null, data);
171+
return (opts.__eager && opts.__eager.length ? eagerLoading : cb)(null, data);
172+
}
173+
});
174+
};
175+
176+
var eagerLoading = function (err, data) {
177+
var pending = opts.__eager.length;
178+
var idMap = {};
179+
var count = 0;
180+
181+
var ids = _.map(data, function (instance) {
182+
var id = instance[opts.id[0]];
183+
// Create the association arrays
184+
for (var i = 0, association; association = opts.__eager[i]; i++) {
185+
instance[association.name] = [];
157186
}
187+
188+
idMap[id] = count++;
189+
return id;
190+
});
191+
192+
_.map(opts.__eager, function (association) {
193+
opts.driver.eagerQuery(association, opts, ids, function (err, instances) {
194+
for (var i = 0, instance; instance = instances[i]; i++) {
195+
// Perform a parent lookup with $p, and initialize it as an instance.
196+
data[idMap[instance.$p]][association.name].push(association.model(instance));
197+
}
198+
199+
if (--pending === 0) {
200+
return cb(null, data);
201+
}
202+
});
158203
});
159204
};
160205

lib/Drivers/DML/mysql.js

100755100644
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,24 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) {
143143
this.execSimpleQuery(q, cb);
144144
};
145145

146+
Driver.prototype.eagerQuery = function (association, opts, ids, cb) {
147+
var desiredKey = Object.keys(association.field);
148+
var assocKey = Object.keys(association.mergeAssocId);
149+
150+
var where = {};
151+
where[desiredKey] = ids;
152+
153+
var query = this.query.select()
154+
.from(association.model.table)
155+
.select(opts.only)
156+
.from(association.mergeTable, assocKey, opts.id)
157+
.select(desiredKey).as("$p")
158+
.where(association.mergeTable, where)
159+
.build();
160+
161+
this.execSimpleQuery(query, cb);
162+
};
163+
146164
Driver.prototype.count = function (table, conditions, opts, cb) {
147165
var q = this.query.select()
148166
.from(table)

lib/Drivers/DML/postgres.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,24 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) {
185185
this.execSimpleQuery(q, cb);
186186
};
187187

188+
Driver.prototype.eagerQuery = function (association, opts, ids, cb) {
189+
var desiredKey = Object.keys(association.field);
190+
var assocKey = Object.keys(association.mergeAssocId);
191+
192+
var where = {};
193+
where[desiredKey] = ids;
194+
195+
var query = this.query.select()
196+
.from(association.model.table)
197+
.select(opts.only)
198+
.from(association.mergeTable, assocKey, opts.id)
199+
.select(desiredKey).as("$p")
200+
.where(association.mergeTable, where)
201+
.build();
202+
203+
this.execSimpleQuery(query, cb);
204+
};
205+
188206
Driver.prototype.count = function (table, conditions, opts, cb) {
189207
var q = this.query.select().from(table).count(null, 'c');
190208

lib/Drivers/DML/sqlite.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,24 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) {
127127
this.db.all(q, cb);
128128
};
129129

130+
Driver.prototype.eagerQuery = function (association, opts, ids, cb) {
131+
var desiredKey = Object.keys(association.field);
132+
var assocKey = Object.keys(association.mergeAssocId);
133+
134+
var where = {};
135+
where[desiredKey] = ids;
136+
137+
var query = this.query.select()
138+
.from(association.model.table)
139+
.select(opts.only)
140+
.from(association.mergeTable, assocKey, opts.id)
141+
.select(desiredKey).as("$p")
142+
.where(association.mergeTable, where)
143+
.build();
144+
145+
this.execSimpleQuery(query, cb);
146+
};
147+
130148
Driver.prototype.count = function (table, conditions, opts, cb) {
131149
var q = this.query.select()
132150
.from(table)

test/integration/model-find-chain.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var common = require('../common');
66
describe("Model.find() chaining", function() {
77
var db = null;
88
var Person = null;
9+
var Dog = null;
910

1011
var setup = function () {
1112
return function (done) {
@@ -36,6 +37,30 @@ describe("Model.find() chaining", function() {
3637
};
3738
};
3839

40+
var setup2 = function () {
41+
return function (done) {
42+
Dog = db.define("dog", {
43+
name: String,
44+
});
45+
Dog.hasMany("friends");
46+
Dog.hasMany("family");
47+
48+
ORM.singleton.clear(); // clear cache
49+
50+
return helper.dropSync(Dog, function () {
51+
Dog.create([{
52+
name : "Fido",
53+
friends : [{ name: "Gunner" }, { name: "Chainsaw" }],
54+
family : [{ name: "Chester" }]
55+
}, {
56+
name : "Thumper",
57+
friends : [{ name: "Bambi" }],
58+
family : [{ name: "Princess" }, { name: "Butch" }]
59+
}], done);
60+
});
61+
};
62+
};
63+
3964
before(function (done) {
4065
helper.connect(function (connection) {
4166
db = connection;
@@ -479,6 +504,76 @@ describe("Model.find() chaining", function() {
479504
});
480505
});
481506

507+
describe(".eager()", function () {
508+
before(setup2());
509+
510+
// TODO: Remove this code once the Mongo eager loading is implemented
511+
var isMongo = function () {
512+
if (db.driver.config.protocol == "mongodb:") {
513+
(function () {
514+
Dog.find().eager("friends").all(function () {
515+
// Should not ever run.
516+
});
517+
}).should.throw();
518+
519+
return true;
520+
}
521+
return false;
522+
};
523+
524+
it("should fetch all listed associations in a single query", function (done) {
525+
if (isMongo()) { return done(); };
526+
527+
Dog.find({ name: ["Fido", "Thumper"] }).eager("friends").all(function (err, dogs) {
528+
should.equal(err, null);
529+
530+
should(Array.isArray(dogs));
531+
532+
dogs.length.should.equal(2);
533+
534+
dogs[0].friends.length.should.equal(2);
535+
dogs[1].friends.length.should.equal(1);
536+
done();
537+
});
538+
});
539+
540+
it("should be able to handle multiple associations", function (done) {
541+
if (isMongo()) { return done(); };
542+
543+
Dog.find({ name: ["Fido", "Thumper"] }).eager("friends", "family").all(function (err, dogs) {
544+
should.equal(err, null);
545+
546+
should(Array.isArray(dogs));
547+
548+
dogs.length.should.equal(2);
549+
550+
dogs[0].friends.length.should.equal(2);
551+
dogs[0].family.length.should.equal(1);
552+
dogs[1].friends.length.should.equal(1);
553+
dogs[1].family.length.should.equal(2);
554+
done();
555+
});
556+
});
557+
558+
it("should work with array parameters too", function (done) {
559+
if (isMongo()) { return done(); };
560+
561+
Dog.find({ name: ["Fido", "Thumper"] }).eager(["friends", "family"]).all(function (err, dogs) {
562+
should.equal(err, null);
563+
564+
should(Array.isArray(dogs));
565+
566+
dogs.length.should.equal(2);
567+
568+
dogs[0].friends.length.should.equal(2);
569+
dogs[0].family.length.should.equal(1);
570+
dogs[1].friends.length.should.equal(1);
571+
dogs[1].family.length.should.equal(2);
572+
done();
573+
});
574+
});
575+
});
576+
482577
describe(".success()", function () {
483578
before(setup());
484579

0 commit comments

Comments
 (0)