persistence-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/README.md +36 -0
  4. data/Rakefile +2 -0
  5. data/lib/generators/persistence/install_generator.rb +22 -0
  6. data/lib/generators/persistence/templates/application.js +10 -0
  7. data/lib/persistence-rails.rb +1 -0
  8. data/lib/persistence/rails.rb +8 -0
  9. data/lib/persistence/rails/engine.rb +6 -0
  10. data/lib/persistence/rails/version.rb +5 -0
  11. data/persistence-rails.gemspec +17 -0
  12. data/vendor/assets/javascript/persistence.all.js +16 -0
  13. data/vendor/assets/javascript/persistence.core.js +2419 -0
  14. data/vendor/assets/javascript/persistence.jquery.js +103 -0
  15. data/vendor/assets/javascript/persistence.jquery.mobile.js +256 -0
  16. data/vendor/assets/javascript/persistence.js +5 -0
  17. data/vendor/assets/javascript/persistence.migrations.js +303 -0
  18. data/vendor/assets/javascript/persistence.pool.js +47 -0
  19. data/vendor/assets/javascript/persistence.search.js +293 -0
  20. data/vendor/assets/javascript/persistence.store.appengine.js +412 -0
  21. data/vendor/assets/javascript/persistence.store.config.js +29 -0
  22. data/vendor/assets/javascript/persistence.store.memory.js +239 -0
  23. data/vendor/assets/javascript/persistence.store.mysql.js +127 -0
  24. data/vendor/assets/javascript/persistence.store.sql.js +900 -0
  25. data/vendor/assets/javascript/persistence.store.sqlite.js +123 -0
  26. data/vendor/assets/javascript/persistence.store.sqlite3.js +124 -0
  27. data/vendor/assets/javascript/persistence.store.titanium.js +193 -0
  28. data/vendor/assets/javascript/persistence.store.websql.js +218 -0
  29. data/vendor/assets/javascript/persistence.sync.js +353 -0
  30. metadata +76 -0
@@ -0,0 +1,47 @@
1
+ //= require persistence.core
2
+
3
+ var sys = require('sys');
4
+ var mysql = require('mysql');
5
+
6
+ function log(o) {
7
+ sys.print(sys.inspect(o) + "\n");
8
+ }
9
+
10
+ function ConnectionPool(getSession, initialPoolSize) {
11
+ this.newConnection = getSession;
12
+ this.pool = [];
13
+ for(var i = 0; i < initialPoolSize; i++) {
14
+ this.pool.push({available: true, session: getSession()});
15
+ }
16
+ }
17
+
18
+ ConnectionPool.prototype.obtain = function() {
19
+ var session = null;
20
+ for(var i = 0; i < this.pool.length; i++) {
21
+ if(this.pool[i].available) {
22
+ var pool = this.pool[i];
23
+ session = pool.session;
24
+ pool.available = false;
25
+ pool.claimed = new Date();
26
+ break;
27
+ }
28
+ }
29
+ if(!session) {
30
+ session = getSession();
31
+ this.pool.push({available: false, session: session, claimed: new Date() });
32
+ }
33
+ };
34
+
35
+ ConnectionPool.prototype.release = function(session) {
36
+ for(var i = 0; i < this.pool.length; i++) {
37
+ if(this.pool[i].session === session) {
38
+ var pool = this.pool[i];
39
+ pool.available = true;
40
+ pool.claimed = null;
41
+ return;
42
+ }
43
+ }
44
+ return false;
45
+ };
46
+
47
+ exports.ConnectionPool = ConnectionPool;
@@ -0,0 +1,293 @@
1
+ //= require persistence.core
2
+
3
+ /**
4
+ * @license
5
+ * Copyright (c) 2010 Zef Hemel <zef@zef.me>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person
8
+ * obtaining a copy of this software and associated documentation
9
+ * files (the "Software"), to deal in the Software without
10
+ * restriction, including without limitation the rights to use,
11
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the
13
+ * Software is furnished to do so, subject to the following
14
+ * conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be
17
+ * included in all copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26
+ * OTHER DEALINGS IN THE SOFTWARE.
27
+ */
28
+
29
+ try {
30
+ if(!window) {
31
+ window = {};
32
+ }
33
+ } catch(e) {
34
+ window = {};
35
+ exports.console = console;
36
+ }
37
+
38
+ var persistence = (window && window.persistence) ? window.persistence : {};
39
+
40
+ persistence.search = {};
41
+
42
+ persistence.search.config = function(persistence, dialect) {
43
+ var filteredWords = {'and':true, 'the': true, 'are': true};
44
+
45
+ var argspec = persistence.argspec;
46
+
47
+ function normalizeWord(word, filterShortWords) {
48
+ if(!(word in filteredWords || (filterShortWords && word.length < 3))) {
49
+ word = word.replace(/ies$/, 'y');
50
+ word = word.length > 3 ? word.replace(/s$/, '') : word;
51
+ return word;
52
+ } else {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Does extremely basic tokenizing of text. Also includes some basic stemming.
59
+ */
60
+ function searchTokenizer(text) {
61
+ var words = text.toLowerCase().split(/[^\w\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/);
62
+ var wordDict = {};
63
+ // Prefixing words with _ to also index Javascript keywords and special fiels like 'constructor'
64
+ for(var i = 0; i < words.length; i++) {
65
+ var normalizedWord = normalizeWord(words[i]);
66
+ if(normalizedWord) {
67
+ var word = '_' + normalizedWord;
68
+ // Some extremely basic stemming
69
+ if(word in wordDict) {
70
+ wordDict[word]++;
71
+ } else {
72
+ wordDict[word] = 1;
73
+ }
74
+ }
75
+ }
76
+ return wordDict;
77
+ }
78
+
79
+ /**
80
+ * Parses a search query and returns it as list SQL parts later to be OR'ed or AND'ed.
81
+ */
82
+ function searchPhraseParser(query, indexTbl, prefixByDefault) {
83
+ query = query.toLowerCase().replace(/['"]/, '').replace(/(^\s+|\s+$)/g, '');
84
+ var words = query.split(/\s+/);
85
+ var sqlParts = [];
86
+ var restrictedToColumn = null;
87
+ for(var i = 0; i < words.length; i++) {
88
+ var word = normalizeWord(words[i]);
89
+ if(!word) {
90
+ continue;
91
+ }
92
+ if(word.search(/:$/) !== -1) {
93
+ restrictedToColumn = word.substring(0, word.length-1);
94
+ continue;
95
+ }
96
+ var sql = '(';
97
+ if(word.search(/\*/) !== -1) {
98
+ sql += "`" + indexTbl + "`.`word` LIKE '" + word.replace(/\*/g, '%') + "'";
99
+ } else if(prefixByDefault) {
100
+ sql += "`" + indexTbl + "`.`word` LIKE '" + word + "%'";
101
+ } else {
102
+ sql += "`" + indexTbl + "`.`word` = '" + word + "'";
103
+ }
104
+ if(restrictedToColumn) {
105
+ sql += ' AND `' + indexTbl + "`.`prop` = '" + restrictedToColumn + "'";
106
+ }
107
+ sql += ')';
108
+ sqlParts.push(sql);
109
+ }
110
+ return sqlParts.length === 0 ? ["1=1"] : sqlParts;
111
+ }
112
+
113
+ var queryCollSubscribers = {}; // entityName -> subscription obj
114
+ persistence.searchQueryCollSubscribers = queryCollSubscribers;
115
+
116
+ function SearchFilter(query, entityName) {
117
+ this.query = query;
118
+ this.entityName = entityName;
119
+ }
120
+
121
+ SearchFilter.prototype.match = function (o) {
122
+ var meta = persistence.getMeta(this.entityName);
123
+ var query = this.query.toLowerCase();
124
+ var text = '';
125
+ for(var p in o) {
126
+ if(meta.textIndex.hasOwnProperty(p)) {
127
+ if(o[p]) {
128
+ text += o[p];
129
+ }
130
+ }
131
+ }
132
+ text = text.toLowerCase();
133
+ return text && text.indexOf(query) !== -1;
134
+ }
135
+
136
+ SearchFilter.prototype.sql = function (o) {
137
+ return "1=1";
138
+ }
139
+
140
+ SearchFilter.prototype.subscribeGlobally = function(coll, entityName) {
141
+ var meta = persistence.getMeta(entityName);
142
+ for(var p in meta.textIndex) {
143
+ if(meta.textIndex.hasOwnProperty(p)) {
144
+ persistence.subscribeToGlobalPropertyListener(coll, entityName, p);
145
+ }
146
+ }
147
+ };
148
+
149
+ SearchFilter.prototype.unsubscribeGlobally = function(coll, entityName) {
150
+ var meta = persistence.getMeta(entityName);
151
+ for(var p in meta.textIndex) {
152
+ if(meta.textIndex.hasOwnProperty(p)) {
153
+ persistence.unsubscribeFromGlobalPropertyListener(coll, entityName, p);
154
+ }
155
+ }
156
+ };
157
+
158
+ SearchFilter.prototype.toUniqueString = function() {
159
+ return "SEARCH: " + this.query;
160
+ }
161
+
162
+ function SearchQueryCollection(session, entityName, query, prefixByDefault) {
163
+ this.init(session, entityName, SearchQueryCollection);
164
+ this.subscribers = queryCollSubscribers[entityName];
165
+ this._filter = new SearchFilter(query, entityName);
166
+
167
+
168
+ if(query) {
169
+ this._additionalJoinSqls.push(', `' + entityName + '_Index`');
170
+ this._additionalWhereSqls.push('`root`.id = `' + entityName + '_Index`.`entityId`');
171
+ this._additionalWhereSqls.push('(' + searchPhraseParser(query, entityName + '_Index', prefixByDefault).join(' OR ') + ')');
172
+ this._additionalGroupSqls.push(' GROUP BY (`' + entityName + '_Index`.`entityId`)');
173
+ this._additionalGroupSqls.push(' ORDER BY SUM(`' + entityName + '_Index`.`occurrences`) DESC');
174
+ }
175
+ }
176
+
177
+ SearchQueryCollection.prototype = new persistence.DbQueryCollection();
178
+
179
+ SearchQueryCollection.prototype.oldClone = SearchQueryCollection.prototype.clone;
180
+
181
+
182
+ SearchQueryCollection.prototype.clone = function() {
183
+ var clone = this.oldClone(false);
184
+ var entityName = this._entityName;
185
+ clone.subscribers = queryCollSubscribers[entityName];
186
+ return clone;
187
+ };
188
+
189
+ SearchQueryCollection.prototype.order = function() {
190
+ throw new Error("Imposing additional orderings is not support for search query collections.");
191
+ };
192
+
193
+ /*
194
+ SearchQueryCollection.prototype.filter = function (property, operator, value) {
195
+ var c = this.clone();
196
+ c._filter = new persistence.AndFilter(this._filter, new persistence.PropertyFilter(property, operator, value));
197
+ // Add global listener (TODO: memory leak waiting to happen!)
198
+ //session.subscribeToGlobalPropertyListener(c, this._entityName, property);
199
+ return c;
200
+ };
201
+ */
202
+
203
+ persistence.entityDecoratorHooks.push(function(Entity) {
204
+ /**
205
+ * Declares a property to be full-text indexed.
206
+ */
207
+ Entity.textIndex = function(prop) {
208
+ if(!Entity.meta.textIndex) {
209
+ Entity.meta.textIndex = {};
210
+ }
211
+ Entity.meta.textIndex[prop] = true;
212
+ // Subscribe
213
+ var entityName = Entity.meta.name;
214
+ if(!queryCollSubscribers[entityName]) {
215
+ queryCollSubscribers[entityName] = {};
216
+ }
217
+ };
218
+
219
+ /**
220
+ * Returns a query collection representing the result of a search
221
+ * @param query an object with the following fields:
222
+ */
223
+ Entity.search = function(session, query, prefixByDefault) {
224
+ var args = argspec.getArgs(arguments, [
225
+ { name: 'session', optional: true, check: function(obj) { return obj.schemaSync; }, defaultValue: persistence },
226
+ { name: 'query', optional: false, check: argspec.hasType('string') },
227
+ { name: 'prefixByDefault', optional: false }
228
+ ]);
229
+ session = args.session;
230
+ query = args.query;
231
+ prefixByDefault = args.prefixByDefault;
232
+
233
+ return session.uniqueQueryCollection(new SearchQueryCollection(session, Entity.meta.name, query, prefixByDefault));
234
+ };
235
+ });
236
+
237
+ persistence.schemaSyncHooks.push(function(tx) {
238
+ var entityMeta = persistence.getEntityMeta();
239
+ var queries = [];
240
+ for(var entityName in entityMeta) {
241
+ var meta = entityMeta[entityName];
242
+ if(meta.textIndex) {
243
+ queries.push([dialect.createTable(entityName + '_Index', [['entityId', 'VARCHAR(32)'], ['prop', 'VARCHAR(30)'], ['word', 'VARCHAR(100)'], ['occurrences', 'INT']]), null]);
244
+ queries.push([dialect.createIndex(entityName + '_Index', ['prop', 'word']), null]);
245
+ queries.push([dialect.createIndex(entityName + '_Index', ['word']), null]);
246
+ persistence.generatedTables[entityName + '_Index'] = true;
247
+ }
248
+ }
249
+ queries.reverse();
250
+ persistence.executeQueriesSeq(tx, queries);
251
+ });
252
+
253
+
254
+ persistence.flushHooks.push(function(session, tx, callback) {
255
+ var queries = [];
256
+ for (var id in session.getTrackedObjects()) {
257
+ if (session.getTrackedObjects().hasOwnProperty(id)) {
258
+ var obj = session.getTrackedObjects()[id];
259
+ var meta = session.define(obj._type).meta;
260
+ var indexTbl = obj._type + '_Index';
261
+ if(meta.textIndex) {
262
+ for ( var p in obj._dirtyProperties) {
263
+ if (obj._dirtyProperties.hasOwnProperty(p) && p in meta.textIndex) {
264
+ queries.push(['DELETE FROM `' + indexTbl + '` WHERE `entityId` = ? AND `prop` = ?', [id, p]]);
265
+ var occurrences = searchTokenizer(obj._data[p]);
266
+ for(var word in occurrences) {
267
+ if(occurrences.hasOwnProperty(word)) {
268
+ queries.push(['INSERT INTO `' + indexTbl + '` VALUES (?, ?, ?, ?)', [obj.id, p, word.substring(1), occurrences[word]]]);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+ for (var id in persistence.getObjectsToRemove()) {
277
+ if (persistence.getObjectsToRemove().hasOwnProperty(id)) {
278
+ var obj = persistence.getObjectsToRemove()[id];
279
+ var meta = persistence.getEntityMeta()[obj._type];
280
+ if(meta.textIndex) {
281
+ queries.push(['DELETE FROM `' + obj._type + '_Index` WHERE `entityId` = ?', [id]]);
282
+ }
283
+ }
284
+ }
285
+ queries.reverse();
286
+ persistence.executeQueriesSeq(tx, queries, callback);
287
+ });
288
+ };
289
+
290
+ if(typeof exports === 'object') {
291
+ exports.config = persistence.search.config;
292
+ }
293
+
@@ -0,0 +1,412 @@
1
+ //= require persistence.core
2
+
3
+ var jdatastore = Packages.com.google.appengine.api.datastore,
4
+ JDatastoreServiceFactory = jdatastore.DatastoreServiceFactory,
5
+ JKeyFactory = jdatastore.KeyFactory,
6
+ JDatastoreService = jdatastore.DatastoreService,
7
+ JFilterOperator = jdatastore.Query.FilterOperator,
8
+ JSortDirection = jdatastore.Query.SortDirection,
9
+ JQuery = jdatastore.Query,
10
+ JInteger = java.lang.Integer;
11
+
12
+ exports.config = function(persistence) {
13
+ var argspec = persistence.argspec;
14
+
15
+ exports.getSession = function() {
16
+ var session = new persistence.Session();
17
+ session.dsService = JDatastoreServiceFactory.getDatastoreService();
18
+ session.transaction = function (fn) {
19
+ fn({executeSql: function() {}});
20
+ };
21
+
22
+ session.close = function() { };
23
+ return session;
24
+ };
25
+
26
+ /**
27
+ * Converts a value from the data store to a value suitable for the entity
28
+ * (also does type conversions, if necessary)
29
+ */
30
+ function dbValToEntityVal(val, type) {
31
+ if (val === null || val === undefined) {
32
+ return val;
33
+ }
34
+ switch (type) {
35
+ case 'DATE':
36
+ // SQL is in seconds and JS in miliseconds
37
+ if (val > 1000000000000) {
38
+ // usually in seconds, but sometimes it's milliseconds
39
+ return new Date(parseInt(val, 10));
40
+ } else {
41
+ return new Date(parseInt(val, 10) * 1000);
42
+ }
43
+ case 'BOOL':
44
+ return val === 1 || val === '1';
45
+ break;
46
+ case 'INT':
47
+ return +val;
48
+ break;
49
+ case 'BIGINT':
50
+ return +val;
51
+ break;
52
+ case 'JSON':
53
+ if (val) {
54
+ return JSON.parse(val);
55
+ }
56
+ else {
57
+ return val;
58
+ }
59
+ break;
60
+ default:
61
+ return val;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Converts an entity value to a data store value, inverse of
67
+ * dbValToEntityVal
68
+ */
69
+ function entityValToDbVal(val, type) {
70
+ if (val === undefined || val === null) {
71
+ return null;
72
+ } else if (type === 'JSON' && val) {
73
+ return JSON.stringify(val);
74
+ } else if (val.id) {
75
+ return val.id;
76
+ } else if (type === 'BOOL') {
77
+ return (val === 'false') ? 0 : (val ? 1 : 0);
78
+ } else if (type === 'DATE' || val.getTime) {
79
+ val = new Date(val);
80
+ return new JInteger(Math.round(val.getTime() / 1000));
81
+ } else {
82
+ return val;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Converts a data store entity to an entity object
88
+ */
89
+ function aeEntityToEntity(session, aeEnt, Entity) {
90
+ if(!aeEnt) return null;
91
+
92
+ var o = new Entity(session);
93
+ var meta = Entity.meta;
94
+ o.id = aeEnt.key.name;
95
+ var propMap = aeEnt.properties;
96
+ for(var prop in Iterator(propMap.keySet())) {
97
+ persistence.set(o, prop, dbValToEntityVal(propMap.get(prop), meta.fields[prop]));
98
+ }
99
+ return o;
100
+ }
101
+
102
+ /**
103
+ * Converts a data store entity to an entity object
104
+ */
105
+ function entityToAEEntity(meta, o) {
106
+ var ent = new jdatastore.Entity(o._type, o.id);
107
+ for(var k in meta.fields) {
108
+ if(meta.fields.hasOwnProperty(k)) {
109
+ ent.setProperty(k, entityValToDbVal(o._data[k], meta.fields[k]));
110
+ }
111
+ }
112
+ for(var k in meta.hasOne) {
113
+ if(meta.hasOne.hasOwnProperty(k)) {
114
+ ent.setProperty(k, entityValToDbVal(o._data[k], meta.fields[k]));
115
+ }
116
+ }
117
+ return ent;
118
+ }
119
+
120
+ var allEntities = [];
121
+
122
+ persistence.schemaSync = function (tx, callback, emulate) {
123
+ var args = argspec.getArgs(arguments, [
124
+ { name: "tx", optional: true, check: persistence.isTransaction, defaultValue: null },
125
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: function(){} },
126
+ { name: "emulate", optional: true, check: argspec.hasType('boolean') }
127
+ ]);
128
+ tx = args.tx;
129
+ callback = args.callback;
130
+
131
+ var entityMeta = persistence.getEntityMeta();
132
+ for (var entityName in entityMeta) {
133
+ if (entityMeta.hasOwnProperty(entityName)) {
134
+ allEntities.push(persistence.define(entityName));
135
+ }
136
+ }
137
+
138
+ callback();
139
+ };
140
+
141
+ persistence.flush = function (tx, callback) {
142
+ var args = argspec.getArgs(arguments, [
143
+ { name: "tx", optional: true, check: persistence.isTransaction },
144
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: null }
145
+ ]);
146
+ tx = args.tx;
147
+ callback = args.callback;
148
+
149
+ var session = this;
150
+ var fns = persistence.flushHooks;
151
+ persistence.asyncForEach(fns, function(fn, callback) {
152
+ fn(session, tx, callback);
153
+ }, function() {
154
+ // After applying the hooks
155
+ var entitiesToPersist = [];
156
+ var removeArrayList = new java.util.ArrayList();
157
+
158
+ for (var id in session.trackedObjects) {
159
+ if (session.trackedObjects.hasOwnProperty(id)) {
160
+ var ent = prepareDbEntity(session.trackedObjects[id]);
161
+ if(ent) {
162
+ entitiesToPersist.push(ent);
163
+ }
164
+ }
165
+ }
166
+ for (var id in session.objectsToRemove) {
167
+ if (session.objectsToRemove.hasOwnProperty(id)) {
168
+ removeArrayList.add(JKeyFactory.createKey(session.trackedObjects[id]._type, id));
169
+ delete session.trackedObjects[id]; // Stop tracking
170
+ }
171
+ }
172
+ if(entitiesToPersist.length > 0) {
173
+ session.dsService.put(entitiesToPersist);
174
+ }
175
+ if(removeArrayList.size() > 0) {
176
+ session.dsService['delete'](removeArrayList);
177
+ }
178
+ callback();
179
+ });
180
+ };
181
+
182
+ persistence.reset = function (tx, callback) {
183
+ var args = argspec.getArgs(arguments, [
184
+ { name: "tx", optional: true, check: persistence.isTransaction, defaultValue: null },
185
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: function(){} }
186
+ ]);
187
+ callback = args.callback;
188
+
189
+ var session = this;
190
+
191
+ persistence.asyncParForEach(allEntities, function(Entity, callback) {
192
+ Entity.all(session).destroyAll(callback);
193
+ }, callback);
194
+ };
195
+
196
+ /**
197
+ * Internal function to persist an object to the database
198
+ * this function is invoked by persistence.flush()
199
+ */
200
+ function prepareDbEntity(obj) {
201
+ var meta = persistence.getMeta(obj._type);
202
+ var isDirty = obj._new;
203
+ if(Object.keys(obj._dirtyProperties).length > 0) {
204
+ isDirty = true;
205
+ }
206
+ if(isDirty) {
207
+ return entityToAEEntity(meta, obj);
208
+ } else {
209
+ return null; // no saving required
210
+ }
211
+ }
212
+
213
+ /////////////////////////// QueryCollection patches to work in AppEngine environment
214
+
215
+ persistence.NullFilter.prototype.addFilter = function (meta, query) {
216
+ };
217
+
218
+ persistence.AndFilter.prototype.addFilter = function (meta, query) {
219
+ this.left.addFilter(meta, query);
220
+ this.right.addFilter(meta, query);
221
+ };
222
+
223
+ persistence.OrFilter.prototype.addFilter = function (meta, query) {
224
+ throw new Error("OrFilter Not supported");
225
+ };
226
+
227
+ persistence.PropertyFilter.prototype.addFilter = function (meta, query) {
228
+ var filterOp;
229
+ var value = this.value;
230
+ switch(this.operator) {
231
+ case '=':
232
+ filterOp = JFilterOperator.EQUAL;
233
+ break;
234
+ case '!=':
235
+ filterOp = JFilterOperator.NOT_EQUAL;
236
+ break;
237
+ case '>':
238
+ filterOp = JFilterOperator.GREATER_THAN;
239
+ break;
240
+ case '>=':
241
+ filterOp = JFilterOperator.GREATER_THAN_OR_EQUAL;
242
+ break;
243
+ case '<':
244
+ filterOp = JFilterOperator.LESS_THAN;
245
+ break;
246
+ case '<=':
247
+ filterOp = JFilterOperator.LESS_THAN_OR_EQUAL;
248
+ break;
249
+ case 'in':
250
+ var values = new java.util.ArrayList();
251
+ var type = meta.fields[this.property];
252
+ for(var i = 0; i < value.length; i++) {
253
+ values.add(entityValToDbVal(value[i], type));
254
+ }
255
+ value = values;
256
+ filterOp = JFilterOperator.IN;
257
+ break;
258
+ };
259
+ query.addFilter(this.property, filterOp, entityValToDbVal(value, meta.fields[this.property]));
260
+ };
261
+
262
+
263
+ function prepareQuery(coll, callback) {
264
+ var session = coll._session;
265
+ var entityName = coll._entityName;
266
+ var meta = persistence.getMeta(entityName);
267
+
268
+ // handles mixin case -- this logic is generic and could be in persistence.
269
+ if (meta.isMixin) {
270
+ var result = [];
271
+ persistence.asyncForEach(meta.mixedIns, function(realMeta, next) {
272
+ var query = coll.clone();
273
+ query._entityName = realMeta.name;
274
+ query.list(tx, function(array) {
275
+ result = result.concat(array);
276
+ next();
277
+ });
278
+ }, function() {
279
+ var query = new persistence.LocalQueryCollection(result);
280
+ query._orderColumns = coll._orderColumns;
281
+ query._reverse = coll._reverse;
282
+ // TODO: handle skip and limit -- do we really want to do it?
283
+ query.list(null, callback);
284
+ });
285
+ return;
286
+ }
287
+
288
+ var query = new JQuery(entityName); // Not tbe confused with jQuery
289
+ coll._filter.addFilter(meta, query);
290
+
291
+ coll._orderColumns.forEach(function(col) {
292
+ query.addSort(col[0], col[1] ? JSortDirection.ASCENDING : JSortDirection.DESCENDING);
293
+ });
294
+
295
+ callback(session.dsService.prepare(query));
296
+ }
297
+
298
+
299
+ /**
300
+ * Asynchronous call to actually fetch the items in the collection
301
+ * @param tx transaction to use
302
+ * @param callback function to be called taking an array with
303
+ * result objects as argument
304
+ */
305
+ persistence.DbQueryCollection.prototype.list = function (tx, callback) {
306
+ var args = argspec.getArgs(arguments, [
307
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
308
+ { name: 'callback', optional: false, check: argspec.isCallback() }
309
+ ]);
310
+ callback = args.callback;
311
+ var that = this;
312
+ var entityName = this._entityName;
313
+ var session = this._session;
314
+
315
+ // TODO: Check if filtering for 'id' property, then use key lookup
316
+ //
317
+ if(this._filter.right && this._filter.right.property && this._filter.right.property === 'id') {
318
+ var idToLoad = this._filter.right.value;
319
+ var obj;
320
+ try {
321
+ obj = session.dsService.get(JKeyFactory.createKey(entityName, idToLoad));
322
+ } catch(e) { // com.google.appengine.api.datastore.EntityNotFoundException
323
+ obj = null;
324
+ }
325
+ if(obj) {
326
+ callback([aeEntityToEntity(session, obj, persistence.define(entityName))]);
327
+ } else {
328
+ callback([]);
329
+ }
330
+ } else {
331
+ session.flush(function() {
332
+ prepareQuery(that, function(preparedQuery) {
333
+ if(that._limit === 1) {
334
+ var row = preparedQuery.asSingleEntity();
335
+ var e = aeEntityToEntity(session, row, persistence.define(entityName));
336
+ callback([e]);
337
+ } else {
338
+ var rows = preparedQuery.asList(jdatastore.FetchOptions.Builder.withLimit(that._limit === -1 ? 1000 : that._limit).offset(that._skip));
339
+ var results = [];
340
+
341
+ var Entity = persistence.define(entityName);
342
+ for (var i = 0; i < rows.size(); i++) {
343
+ var r = rows.get(i);
344
+ var e = aeEntityToEntity(session, r, Entity);
345
+ results.push(e);
346
+ session.add(e);
347
+ }
348
+ if(that._reverse) {
349
+ results.reverse();
350
+ }
351
+ that.triggerEvent('list', that, results);
352
+ callback(results);
353
+ }
354
+ });
355
+ });
356
+ }
357
+ };
358
+
359
+ persistence.DbQueryCollection.prototype.destroyAll = function (tx, callback) {
360
+ var args = argspec.getArgs(arguments, [
361
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
362
+ { name: 'callback', optional: true, check: argspec.isCallback(), defaultValue: function(){} }
363
+ ]);
364
+ callback = args.callback;
365
+
366
+ var that = this;
367
+ var session = this._session;
368
+
369
+ session.flush(function() {
370
+ prepareQuery(that, function(preparedQuery) {
371
+ var rows = preparedQuery.asList(jdatastore.FetchOptions.Builder.withLimit(that._limit === -1 ? 1000 : that._limit).offset(that._skip));
372
+ var keys = new java.util.ArrayList();
373
+ for (var i = 0; i < rows.size(); i++) {
374
+ var r = rows.get(i);
375
+ keys.add(r.getKey());
376
+ }
377
+ that._session.dsService['delete'](keys);
378
+ that.triggerEvent('change', that);
379
+ callback();
380
+ });
381
+ });
382
+ };
383
+
384
+ persistence.DbQueryCollection.prototype.count = function (tx, callback) {
385
+ var args = argspec.getArgs(arguments, [
386
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
387
+ { name: 'callback', optional: false, check: argspec.isCallback() }
388
+ ]);
389
+ tx = args.tx;
390
+ callback = args.callback;
391
+
392
+ var that = this;
393
+ var session = this._session;
394
+
395
+ session.flush(function() {
396
+ prepareQuery(that, function(preparedQuery) {
397
+ var n = preparedQuery.countEntities(jdatastore.FetchOptions.Builder.withDefaults());
398
+ callback(n);
399
+ });
400
+ });
401
+
402
+ };
403
+
404
+ persistence.isSession = function(obj) {
405
+ var isSession = !obj || (obj && obj.schemaSync);
406
+ if(!isSession) {
407
+ throw Error("No session argument passed, you should!");
408
+ }
409
+ return isSession;
410
+ };
411
+ };
412
+