Browse files

Support querying collections of orphaned items

Extended ManyToManyDbQueryCollection to support null objects, thus returning
all objects that have no related objects for a given relationship. The
relationship() method for QueryCollection facilitates the creation of such a
collection.

This setup can later be extended to support whole queries instead of single
objects to model more complex relationship queries.
  • Loading branch information...
1 parent 7c7a457 commit 6ebe909b74b5a970a9d6ca5d4f8a8287ebbb13d0 @superquadratic superquadratic committed Apr 10, 2012
Showing with 66 additions and 34 deletions.
  1. +12 −4 lib/persistence.js
  2. +32 −30 lib/persistence.store.sql.js
  3. +22 −0 test/browser/test.persistence.js
View
16 lib/persistence.js
@@ -422,7 +422,7 @@ persistence.get = function(arg1, arg2) {
var rel = meta.hasMany[coll];
var inverseMeta = rel.type.meta;
var queryColl = new persistence.ManyToManyDbQueryCollection(session, inverseMeta.name);
- queryColl.initManyToMany(that, coll);
+ queryColl.initManyToMany(rel.inverseProperty, that);
that._data[coll] = queryColl;
return session.uniqueQueryCollection(queryColl);
}
@@ -1662,6 +1662,14 @@ persistence.get = function(arg1, arg2) {
return session.uniqueQueryCollection(c);
};
+ QueryCollection.prototype.relationship = function (rel, obj) {
+ var c = new ManyToManyDbQueryCollection(this._session, this._entityName);
+ c.initManyToMany(rel, obj);
+ var session = this._session;
+ c = session.uniqueQueryCollection(c);
+ return session.uniqueQueryCollection(c);
+ };
+
/**
* Returns a new query collection with an OR condition between the
* current filter and the filter specified as argument
@@ -1912,17 +1920,17 @@ persistence.get = function(arg1, arg2) {
ManyToManyDbQueryCollection.prototype = new DbQueryCollection();
- ManyToManyDbQueryCollection.prototype.initManyToMany = function(obj, coll) {
- this._obj = obj;
+ ManyToManyDbQueryCollection.prototype.initManyToMany = function(coll, obj) {
this._coll = coll;
+ this._obj = obj;
};
ManyToManyDbQueryCollection.prototype.clone = function() {
var c = DbQueryCollection.prototype.clone.call(this);
c._localAdded = this._localAdded;
c._localRemoved = this._localRemoved;
- c._obj = this._obj;
c._coll = this._coll;
+ c._obj = this._obj;
return c;
};
View
62 lib/persistence.store.sql.js
@@ -807,19 +807,19 @@ function config(persistence, dialect) {
};
var originalInitManyToMany = persistence.ManyToManyDbQueryCollection.prototype.initManyToMany;
- persistence.ManyToManyDbQueryCollection.prototype.initManyToMany = function(obj, coll) {
- originalInitManyToMany.call(this, obj, coll);
- var meta = persistence.getMeta(this._obj._type);
- var rel = meta.hasMany[coll];
- var inverseMeta = rel.type.meta;
- var inv = inverseMeta.hasMany[rel.inverseProperty];
- var direct = rel.mixin ? rel.mixin.meta.name : meta.name;
- var inverse = inv.mixin ? inv.mixin.meta.name : inverseMeta.name;
+ persistence.ManyToManyDbQueryCollection.prototype.initManyToMany = function(coll, obj) {
+ originalInitManyToMany.call(this, coll, obj);
+ this.meta = persistence.getMeta(this._entityName);
+ this.rel = this.meta.hasMany[coll];
+ this.inverseMeta = this.rel.type.meta;
+ this.inv = this.inverseMeta.hasMany[this.rel.inverseProperty];
+ var direct = this.rel.mixin ? this.rel.mixin.meta.name : this.meta.name;
+ var inverse = this.inv.mixin ? this.inv.mixin.meta.name : this.inverseMeta.name;
this._manyToManyFetch = {
- table: rel.tableName,
+ table: this.rel.tableName,
prop: direct + '_' + coll,
- inverseProp: inverse + '_' + rel.inverseProperty,
- id: obj.id
+ inverseProp: inverse + '_' + this.rel.inverseProperty,
+ obj: obj
};
};
@@ -835,51 +835,53 @@ function config(persistence, dialect) {
persistence.ManyToManyDbQueryCollection.prototype.joinSql = function (mainAlias) {
var joinSql = persistence.DbQueryCollection.prototype.joinSql.call(this, mainAlias);
var mtm = this._manyToManyFetch;
- var mtmJoin = "`" + mtm.table + "` AS mtm ON mtm.`" + mtm.inverseProp + "` = `" + mainAlias + "`.`id`";
+ var mtmJoin = "`" + mtm.table + "` AS mtm ON mtm.`" + mtm.prop + "` = `" + mainAlias + "`.`id`";
return " LEFT JOIN " + mtmJoin + " " + joinSql;
}
persistence.ManyToManyDbQueryCollection.prototype.whereSql = function (mainAlias, args) {
var tm = persistence.typeMapper;
var mtm = this._manyToManyFetch;
var whereSql = persistence.DbQueryCollection.prototype.whereSql.call(this, mainAlias, args);
- return whereSql + " AND mtm.`" + mtm.prop + "` = " + tm.outId(mtm.id);
+ whereSql += " AND mtm.`" + mtm.inverseProp + "`";
+ if (mtm.obj === null) {
+ whereSql += " IS NULL"
+ } else {
+ whereSql += " = " + tm.outId(mtm.obj.id);
+ }
+ return whereSql;
}
persistence.ManyToManyDbQueryCollection.prototype.persistQueries = function() {
var queries = [];
- var meta = persistence.getMeta(this._obj._type);
- var inverseMeta = meta.hasMany[this._coll].type.meta;
+
var tm = persistence.typeMapper;
- var rel = meta.hasMany[this._coll];
- var inv = inverseMeta.hasMany[rel.inverseProperty];
- var direct = rel.mixin ? rel.mixin.meta.name : meta.name;
- var inverse = inv.mixin ? inv.mixin.meta.name : inverseMeta.name;
+ var mtm = this._manyToManyFetch;
// Added
for(var i = 0; i < this._localAdded.length; i++) {
- var columns = [direct + "_" + this._coll, inverse + '_' + rel.inverseProperty];
+ var columns = [mtm.inverseProp, mtm.prop];
var vars = [tm.outIdVar("?"), tm.outIdVar("?")];
var args = [tm.entityIdToDbId(this._obj.id), tm.entityIdToDbId(this._localAdded[i].id)];
- if (rel.mixin) {
- columns.push(direct + "_" + this._coll + "_class");
+ if (this.rel.mixin) {
+ columns.push(mtm.inverseProp + "_class");
vars.push("?");
- args.push(meta.name);
+ args.push(this.meta.name);
}
- if (inv.mixin) {
- columns.push(inverse + "_" + rel.inverseProperty + "_class");
+ if (this.inv.mixin) {
+ columns.push(mtm.prop + "_class");
vars.push("?");
- args.push(inverseMeta.name);
+ args.push(this.inverseMeta.name);
}
- queries.push(["INSERT OR IGNORE INTO " + rel.tableName +
+ queries.push(["INSERT OR IGNORE INTO " + mtm.table +
" (`" + columns.join("`, `") + "`) VALUES (" + vars.join(",") + ")", args]);
}
this._localAdded = [];
// Removed
for(var i = 0; i < this._localRemoved.length; i++) {
- queries.push(["DELETE FROM " + rel.tableName +
- " WHERE `" + direct + "_" + this._coll + "` = " + tm.outIdVar("?") + " AND `" +
- inverse + '_' + rel.inverseProperty +
+ queries.push(["DELETE FROM " + mtm.table +
+ " WHERE `" + mtm.inverseProp + "` = " + tm.outIdVar("?") + " AND `" +
+ mtm.prop +
"` = " + tm.outIdVar("?"), [tm.entityIdToDbId(this._obj.id), tm.entityIdToDbId(this._localRemoved[i].id)]]);
}
this._localRemoved = [];
View
22 test/browser/test.persistence.js
@@ -452,6 +452,28 @@ $(document).ready(function(){
dateFilterTests(coll, start);
});
+ asyncTest("Relationship filter", function() {
+ var tags = [];
+ for(var i = 0; i < 4; i++) {
+ var tag = new Tag({name: "Tag " + i});
+ persistence.add(tag);
+ tags.push(tag);
+ }
+ var tasks = [];
+ for(var i = 0; i < 15; i++) {
+ var task = new Task({name: "Task " + i, counter: i});
+ persistence.add(task);
+ task.tags.add(tags[Math.floor(i / 5)]);
+ tasks.push(task);
+ }
+
+ Tag.all().relationship('tasks', null).list(function (result) {
+ equals(result.length, 1, 'Correct number of orphaned tags');
+ equals(result[0].id, tags[3].id, 'Correct orphaned tag');
+ start();
+ });
+ });
+
function intOrderTests(coll, callback) {
var tasks = [];

0 comments on commit 6ebe909

Please sign in to comment.