persistence-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+