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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.md +36 -0
- data/Rakefile +2 -0
- data/lib/generators/persistence/install_generator.rb +22 -0
- data/lib/generators/persistence/templates/application.js +10 -0
- data/lib/persistence-rails.rb +1 -0
- data/lib/persistence/rails.rb +8 -0
- data/lib/persistence/rails/engine.rb +6 -0
- data/lib/persistence/rails/version.rb +5 -0
- data/persistence-rails.gemspec +17 -0
- data/vendor/assets/javascript/persistence.all.js +16 -0
- data/vendor/assets/javascript/persistence.core.js +2419 -0
- data/vendor/assets/javascript/persistence.jquery.js +103 -0
- data/vendor/assets/javascript/persistence.jquery.mobile.js +256 -0
- data/vendor/assets/javascript/persistence.js +5 -0
- data/vendor/assets/javascript/persistence.migrations.js +303 -0
- data/vendor/assets/javascript/persistence.pool.js +47 -0
- data/vendor/assets/javascript/persistence.search.js +293 -0
- data/vendor/assets/javascript/persistence.store.appengine.js +412 -0
- data/vendor/assets/javascript/persistence.store.config.js +29 -0
- data/vendor/assets/javascript/persistence.store.memory.js +239 -0
- data/vendor/assets/javascript/persistence.store.mysql.js +127 -0
- data/vendor/assets/javascript/persistence.store.sql.js +900 -0
- data/vendor/assets/javascript/persistence.store.sqlite.js +123 -0
- data/vendor/assets/javascript/persistence.store.sqlite3.js +124 -0
- data/vendor/assets/javascript/persistence.store.titanium.js +193 -0
- data/vendor/assets/javascript/persistence.store.websql.js +218 -0
- data/vendor/assets/javascript/persistence.sync.js +353 -0
- 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
|
+
|