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,29 @@
1
+ //= require persistence.core
2
+
3
+ exports.init = function(persistence, config) {
4
+ var persistenceStore;
5
+ switch (config.adaptor) {
6
+ case 'memory':
7
+ persistenceStore = require('./persistence.store.memory');
8
+ break;
9
+ case 'mysql':
10
+ persistenceStore = require('./persistence.store.mysql');
11
+ break;
12
+ case 'sqlite3':
13
+ persistenceStore = require('./persistence.store.sqlite3');
14
+ break;
15
+ default:
16
+ persistenceStore = require('./persistence.store.mysql');
17
+ break;
18
+ }
19
+
20
+ if (config.username) config.user = config.username;
21
+ if (config.hostname) config.host = config.hostname;
22
+ persistenceStore.config(persistence,
23
+ config.host,
24
+ config.port,
25
+ config.database,
26
+ config.user,
27
+ config.password);
28
+ return persistenceStore;
29
+ };
@@ -0,0 +1,239 @@
1
+ //= require persistence.core
2
+
3
+ try {
4
+ if(!window) {
5
+ window = {};
6
+ //exports.console = console;
7
+ }
8
+ } catch(e) {
9
+ window = {};
10
+ exports.console = console;
11
+ }
12
+
13
+ var persistence = (window && window.persistence) ? window.persistence : {};
14
+
15
+ if(!persistence.store) {
16
+ persistence.store = {};
17
+ }
18
+
19
+ persistence.store.memory = {};
20
+
21
+ persistence.store.memory.config = function(persistence, dbname) {
22
+ var argspec = persistence.argspec;
23
+ dbname = dbname || 'persistenceData';
24
+
25
+ var allObjects = {}; // entityName -> LocalQueryCollection
26
+
27
+ persistence.getAllObjects = function() { return allObjects; };
28
+
29
+ var defaultAdd = persistence.add;
30
+
31
+ persistence.add = function(obj) {
32
+ if(!this.trackedObjects[obj.id]) {
33
+ defaultAdd.call(this, obj);
34
+ var entityName = obj._type;
35
+ if(!allObjects[entityName]) {
36
+ allObjects[entityName] = new persistence.LocalQueryCollection();
37
+ allObjects[entityName]._session = persistence;
38
+ }
39
+ allObjects[entityName].add(obj);
40
+ }
41
+ return this;
42
+ };
43
+
44
+ var defaultRemove = persistence.remove;
45
+
46
+ persistence.remove = function(obj) {
47
+ defaultRemove.call(this, obj);
48
+ var entityName = obj._type;
49
+ allObjects[entityName].remove(obj);
50
+ };
51
+
52
+ persistence.schemaSync = function (tx, callback, emulate) {
53
+ var args = argspec.getArgs(arguments, [
54
+ { name: "tx", optional: true, check: persistence.isTransaction, defaultValue: null },
55
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: function(){} },
56
+ { name: "emulate", optional: true, check: argspec.hasType('boolean') }
57
+ ]);
58
+
59
+ args.callback();
60
+ };
61
+
62
+ persistence.flush = function (tx, callback) {
63
+ var args = argspec.getArgs(arguments, [
64
+ { name: "tx", optional: true, check: persistence.isTransaction },
65
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: function(){} }
66
+ ]);
67
+
68
+ var fns = persistence.flushHooks;
69
+ var session = this;
70
+ persistence.asyncForEach(fns, function(fn, callback) {
71
+ fn(session, tx, callback);
72
+ }, function() {
73
+ var trackedObjects = persistence.trackedObjects;
74
+ for(var id in trackedObjects) {
75
+ if(trackedObjects.hasOwnProperty(id)) {
76
+ if (persistence.objectsToRemove.hasOwnProperty(id)) {
77
+ delete trackedObjects[id];
78
+ } else {
79
+ trackedObjects[id]._dirtyProperties = {};
80
+ }
81
+ }
82
+ }
83
+ args.callback();
84
+ });
85
+ };
86
+
87
+ persistence.transaction = function(callback) {
88
+ setTimeout(function() {
89
+ callback({executeSql: function() {} });
90
+ }, 0);
91
+ };
92
+
93
+ persistence.loadFromLocalStorage = function(callback) {
94
+ var dump = window.localStorage.getItem(dbname);
95
+ if(dump) {
96
+ this.loadFromJson(dump, callback);
97
+ } else {
98
+ callback && callback();
99
+ }
100
+ };
101
+
102
+ persistence.saveToLocalStorage = function(callback) {
103
+ this.dumpToJson(function(dump) {
104
+ window.localStorage.setItem(dbname, dump);
105
+ if(callback) {
106
+ callback();
107
+ }
108
+ });
109
+ };
110
+
111
+ /**
112
+ * Remove all tables in the database (as defined by the model)
113
+ */
114
+ persistence.reset = function (tx, callback) {
115
+ var args = argspec.getArgs(arguments, [
116
+ { name: "tx", optional: true, check: persistence.isTransaction, defaultValue: null },
117
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: function(){} }
118
+ ]);
119
+ tx = args.tx;
120
+ callback = args.callback;
121
+
122
+ allObjects = {};
123
+ this.clean();
124
+ callback();
125
+ };
126
+
127
+ /**
128
+ * Dummy
129
+ */
130
+ persistence.close = function() {};
131
+
132
+ // QueryCollection's list
133
+
134
+ function makeLocalClone(otherColl) {
135
+ var coll = allObjects[otherColl._entityName];
136
+ if(!coll) {
137
+ coll = new persistence.LocalQueryCollection();
138
+ }
139
+ coll = coll.clone();
140
+ coll._filter = otherColl._filter;
141
+ coll._prefetchFields = otherColl._prefetchFields;
142
+ coll._orderColumns = otherColl._orderColumns;
143
+ coll._limit = otherColl._limit;
144
+ coll._skip = otherColl._skip;
145
+ coll._reverse = otherColl._reverse;
146
+ return coll;
147
+ }
148
+ /**
149
+ * Asynchronous call to actually fetch the items in the collection
150
+ * @param tx transaction to use
151
+ * @param callback function to be called taking an array with
152
+ * result objects as argument
153
+ */
154
+ persistence.DbQueryCollection.prototype.list = function (tx, callback) {
155
+ var args = argspec.getArgs(arguments, [
156
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
157
+ { name: 'callback', optional: false, check: argspec.isCallback() }
158
+ ]);
159
+ tx = args.tx;
160
+ callback = args.callback;
161
+
162
+ var coll = makeLocalClone(this);
163
+ coll.list(null, callback);
164
+ };
165
+
166
+ /**
167
+ * Asynchronous call to remove all the items in the collection.
168
+ * Note: does not only remove the items from the collection, but
169
+ * the items themselves.
170
+ * @param tx transaction to use
171
+ * @param callback function to be called when clearing has completed
172
+ */
173
+ persistence.DbQueryCollection.prototype.destroyAll = function (tx, callback) {
174
+ var args = argspec.getArgs(arguments, [
175
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
176
+ { name: 'callback', optional: true, check: argspec.isCallback(), defaultValue: function(){} }
177
+ ]);
178
+ tx = args.tx;
179
+ callback = args.callback;
180
+
181
+ var coll = makeLocalClone(this);
182
+ coll.destroyAll(null, callback);
183
+ };
184
+
185
+ /**
186
+ * Asynchronous call to count the number of items in the collection.
187
+ * @param tx transaction to use
188
+ * @param callback function to be called when clearing has completed
189
+ */
190
+ persistence.DbQueryCollection.prototype.count = function (tx, callback) {
191
+ var args = argspec.getArgs(arguments, [
192
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
193
+ { name: 'callback', optional: false, check: argspec.isCallback() }
194
+ ]);
195
+ tx = args.tx;
196
+ callback = args.callback;
197
+
198
+ var coll = makeLocalClone(this);
199
+ coll.count(null, callback);
200
+ };
201
+
202
+ persistence.ManyToManyDbQueryCollection = function(session, entityName) {
203
+ this.init(session, entityName, persistence.ManyToManyDbQueryCollection);
204
+ this._items = [];
205
+ };
206
+
207
+ persistence.ManyToManyDbQueryCollection.prototype = new persistence.LocalQueryCollection();
208
+
209
+ persistence.ManyToManyDbQueryCollection.prototype.initManyToMany = function(obj, coll) {
210
+ this._obj = obj;
211
+ this._coll = coll; // column name
212
+ };
213
+
214
+ persistence.ManyToManyDbQueryCollection.prototype.add = function(item, recursing) {
215
+ persistence.LocalQueryCollection.prototype.add.call(this, item);
216
+ if(!recursing) { // prevent recursively adding to one another
217
+ // Let's find the inverse collection
218
+ var meta = persistence.getMeta(this._obj._type);
219
+ var inverseProperty = meta.hasMany[this._coll].inverseProperty;
220
+ persistence.get(item, inverseProperty).add(this._obj, true);
221
+ }
222
+ };
223
+
224
+ persistence.ManyToManyDbQueryCollection.prototype.remove = function(item, recursing) {
225
+ persistence.LocalQueryCollection.prototype.remove.call(this, item);
226
+ if(!recursing) { // prevent recursively adding to one another
227
+ // Let's find the inverse collection
228
+ var meta = persistence.getMeta(this._obj._type);
229
+ var inverseProperty = meta.hasMany[this._coll].inverseProperty;
230
+ persistence.get(item, inverseProperty).remove(this._obj, true);
231
+ }
232
+ };
233
+ };
234
+
235
+ try {
236
+ exports.config = persistence.store.memory.config;
237
+ exports.getSession = function() { return persistence; };
238
+ } catch(e) {}
239
+
@@ -0,0 +1,127 @@
1
+ //= require persistence.core
2
+
3
+ /**
4
+ * This back-end depends on the node.js asynchronous MySQL driver as found on:
5
+ * http://github.com/felixge/node-mysql/
6
+ * Easy install using npm:
7
+ * npm install mysql
8
+ */
9
+ var sys = require('sys');
10
+ var sql = require('./persistence.store.sql');
11
+ var mysql = require('mysql');
12
+
13
+ var db, username, password;
14
+
15
+ function log(o) {
16
+ sys.print(sys.inspect(o) + "\n");
17
+ }
18
+
19
+
20
+ exports.config = function(persistence, hostname, port, db, username, password) {
21
+ exports.getSession = function(cb) {
22
+ var that = {};
23
+ var client = mysql.createClient({
24
+ host: hostname,
25
+ port: port,
26
+ database: db,
27
+ user: username,
28
+ password: password
29
+ });
30
+
31
+ var session = new persistence.Session(that);
32
+ session.transaction = function (explicitCommit, fn) {
33
+ if (typeof arguments[0] === "function") {
34
+ fn = arguments[0];
35
+ explicitCommit = false;
36
+ }
37
+ var tx = transaction(client);
38
+ if (explicitCommit) {
39
+ tx.executeSql("START TRANSACTION", null, function(){
40
+ fn(tx)
41
+ });
42
+ }
43
+ else
44
+ fn(tx);
45
+ };
46
+
47
+ session.close = function() {
48
+ client.end();
49
+ //conn._connection.destroy();
50
+ };
51
+ session.client = client;
52
+ return session;
53
+ };
54
+
55
+ function transaction(conn){
56
+ var that = {};
57
+ if(conn.ending) {
58
+ throw new Error("Connection has been closed, cannot execute query.");
59
+ }
60
+ that.executeSql = function(query, args, successFn, errorFn){
61
+ function cb(err, result){
62
+ if (err) {
63
+ log(err.message);
64
+ that.errorHandler && that.errorHandler(err);
65
+ errorFn && errorFn(null, err);
66
+ return;
67
+ }
68
+ if (successFn) {
69
+ successFn(result);
70
+ }
71
+ }
72
+ if (persistence.debug) {
73
+ sys.print(query + "\n");
74
+ args && args.length > 0 && sys.print(args.join(",") + "\n")
75
+ }
76
+ if (!args) {
77
+ conn.query(query, cb);
78
+ }
79
+ else {
80
+ conn.query(query, args, cb);
81
+ }
82
+ }
83
+
84
+ that.commit = function(session, callback){
85
+ session.flush(that, function(){
86
+ that.executeSql("COMMIT", null, callback);
87
+ })
88
+ }
89
+
90
+ that.rollback = function(session, callback){
91
+ that.executeSql("ROLLBACK", null, function() {
92
+ session.clean();
93
+ callback && callback();
94
+ });
95
+ }
96
+ return that;
97
+ }
98
+
99
+ exports.mysqlDialect = {
100
+ // columns is an array of arrays, e.g.
101
+ // [["id", "VARCHAR(32)", "PRIMARY KEY"], ["name", "TEXT"]]
102
+ createTable: function(tableName, columns) {
103
+ var tm = persistence.typeMapper;
104
+ var sql = "CREATE TABLE IF NOT EXISTS `" + tableName + "` (";
105
+ var defs = [];
106
+ for(var i = 0; i < columns.length; i++) {
107
+ var column = columns[i];
108
+ defs.push("`" + column[0] + "` " + tm.columnType(column[1]) + (column[2] ? " " + column[2] : ""));
109
+ }
110
+ sql += defs.join(", ");
111
+ sql += ') ENGINE=InnoDB DEFAULT CHARSET=utf8';
112
+ return sql;
113
+ },
114
+
115
+ // columns is array of column names, e.g.
116
+ // ["id"]
117
+ createIndex: function(tableName, columns, options) {
118
+ options = options || {};
119
+ return "CREATE "+(options.unique?"UNIQUE ":"")+"INDEX `" + tableName + "__" + columns.join("_") +
120
+ "` ON `" + tableName + "` (" +
121
+ columns.map(function(col) { return "`" + col + "`"; }).join(", ") + ")";
122
+ }
123
+ };
124
+
125
+ sql.config(persistence, exports.mysqlDialect);
126
+ };
127
+
@@ -0,0 +1,900 @@
1
+ //= require persistence.core
2
+
3
+ /**
4
+ * Default type mapper. Override to support more types or type options.
5
+ */
6
+ var defaultTypeMapper = {
7
+ /**
8
+ * SQL type for ids
9
+ */
10
+ idType: "VARCHAR(32)",
11
+
12
+ /**
13
+ * SQL type for class names (used by mixins)
14
+ */
15
+ classNameType: "TEXT",
16
+
17
+ /**
18
+ * Returns SQL type for column definition
19
+ */
20
+ columnType: function(type){
21
+ switch(type) {
22
+ case 'JSON': return 'TEXT';
23
+ case 'BOOL': return 'INT';
24
+ case 'DATE': return 'INT';
25
+ default: return type;
26
+ }
27
+ },
28
+
29
+ inVar: function(str, type){
30
+ return str;
31
+ },
32
+ outVar: function(str, type){
33
+ return str;
34
+ },
35
+ outId: function(str){
36
+ return "'" + str + "'";
37
+ },
38
+ /**
39
+ * Converts a value from the database to a value suitable for the entity
40
+ * (also does type conversions, if necessary)
41
+ */
42
+ dbValToEntityVal: function(val, type){
43
+ if (val === null || val === undefined) {
44
+ return val;
45
+ }
46
+ switch (type) {
47
+ case 'DATE':
48
+ // SQL is in seconds and JS in miliseconds
49
+ if (val > 1000000000000) {
50
+ // usually in seconds, but sometimes it's milliseconds
51
+ return new Date(parseInt(val, 10));
52
+ } else {
53
+ return new Date(parseInt(val, 10) * 1000);
54
+ }
55
+ case 'BOOL':
56
+ return val === 1 || val === '1';
57
+ break;
58
+ case 'INT':
59
+ return +val;
60
+ break;
61
+ case 'BIGINT':
62
+ return +val;
63
+ break;
64
+ case 'JSON':
65
+ if (val) {
66
+ return JSON.parse(val);
67
+ }
68
+ else {
69
+ return val;
70
+ }
71
+ break;
72
+ default:
73
+ return val;
74
+ }
75
+ },
76
+
77
+ /**
78
+ * Converts an entity value to a database value, inverse of
79
+ * dbValToEntityVal
80
+ */
81
+ entityValToDbVal: function(val, type){
82
+ if (val === undefined || val === null) {
83
+ return null;
84
+ }
85
+ else if (type === 'JSON' && val) {
86
+ return JSON.stringify(val);
87
+ }
88
+ else if (val.id) {
89
+ return val.id;
90
+ }
91
+ else if (type === 'BOOL') {
92
+ return (val === 'false') ? 0 : (val ? 1 : 0);
93
+ }
94
+ else if (type === 'DATE' || val.getTime) {
95
+ // In order to make SQLite Date/Time functions work we should store
96
+ // values in seconds and not as miliseconds as JS Date.getTime()
97
+ val = new Date(val);
98
+ return Math.round(val.getTime() / 1000);
99
+ }
100
+ else {
101
+ return val;
102
+ }
103
+ },
104
+ /**
105
+ * Shortcut for inVar when type is id -- no need to override
106
+ */
107
+ inIdVar: function(str){
108
+ return this.inVar(str, this.idType);
109
+ },
110
+ /**
111
+ * Shortcut for outVar when type is id -- no need to override
112
+ */
113
+ outIdVar: function(str){
114
+ return this.outVar(str, this.idType);
115
+ },
116
+ /**
117
+ * Shortcut for entityValToDbVal when type is id -- no need to override
118
+ */
119
+ entityIdToDbId: function(id){
120
+ return this.entityValToDbVal(id, this.idType);
121
+ }
122
+ }
123
+
124
+ function config(persistence, dialect) {
125
+ var argspec = persistence.argspec;
126
+
127
+ persistence.typeMapper = dialect.typeMapper || defaultTypeMapper;
128
+
129
+ persistence.generatedTables = {}; // set
130
+
131
+ /**
132
+ * Synchronize the data model with the database, creates table that had not
133
+ * been defined before
134
+ *
135
+ * @param tx
136
+ * transaction object to use (optional)
137
+ * @param callback
138
+ * function to be called when synchronization has completed,
139
+ * takes started transaction as argument
140
+ */
141
+ persistence.schemaSync = function (tx, callback, emulate) {
142
+ var args = argspec.getArgs(arguments, [
143
+ { name: "tx", optional: true, check: persistence.isTransaction, defaultValue: null },
144
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: function(){} },
145
+ { name: "emulate", optional: true, check: argspec.hasType('boolean') }
146
+ ]);
147
+ tx = args.tx;
148
+ callback = args.callback;
149
+ emulate = args.emulate;
150
+
151
+ if(!tx) {
152
+ var session = this;
153
+ this.transaction(function(tx) { session.schemaSync(tx, callback, emulate); });
154
+ return;
155
+ }
156
+ var queries = [], meta, colDefs, otherMeta, tableName;
157
+
158
+ var tm = persistence.typeMapper;
159
+ var entityMeta = persistence.getEntityMeta();
160
+ for (var entityName in entityMeta) {
161
+ if (entityMeta.hasOwnProperty(entityName)) {
162
+ meta = entityMeta[entityName];
163
+ if (!meta.isMixin) {
164
+ colDefs = [];
165
+ for (var prop in meta.fields) {
166
+ if (meta.fields.hasOwnProperty(prop)) {
167
+ colDefs.push([prop, meta.fields[prop]]);
168
+ }
169
+ }
170
+ for (var rel in meta.hasOne) {
171
+ if (meta.hasOne.hasOwnProperty(rel)) {
172
+ otherMeta = meta.hasOne[rel].type.meta;
173
+ colDefs.push([rel, tm.idType]);
174
+ queries.push([dialect.createIndex(meta.name, [rel]), null]);
175
+ }
176
+ }
177
+ for (var i = 0; i < meta.indexes.length; i++) {
178
+ queries.push([dialect.createIndex(meta.name, meta.indexes[i].columns, meta.indexes[i]), null]);
179
+ }
180
+ }
181
+ for (var rel in meta.hasMany) {
182
+ if (meta.hasMany.hasOwnProperty(rel) && meta.hasMany[rel].manyToMany) {
183
+ tableName = meta.hasMany[rel].tableName;
184
+ if (!persistence.generatedTables[tableName]) {
185
+ var otherMeta = meta.hasMany[rel].type.meta;
186
+ var inv = meta.hasMany[rel].inverseProperty;
187
+ // following test ensures that mixin mtm tables get created with the mixin itself
188
+ // it seems superfluous because mixin will be processed before entitites that use it
189
+ // but better be safe than sorry.
190
+ if (otherMeta.hasMany[inv].type.meta != meta)
191
+ continue;
192
+ var p1 = meta.name + "_" + rel;
193
+ var p2 = otherMeta.name + "_" + inv;
194
+ queries.push([dialect.createIndex(tableName, [p1]), null]);
195
+ queries.push([dialect.createIndex(tableName, [p2]), null]);
196
+ var columns = [[p1, tm.idType], [p2, tm.idType]];
197
+ if (meta.isMixin)
198
+ columns.push([p1 + "_class", tm.classNameType])
199
+ if (otherMeta.isMixin)
200
+ columns.push([p2 + "_class", tm.classNameType])
201
+ queries.push([dialect.createTable(tableName, columns), null]);
202
+ persistence.generatedTables[tableName] = true;
203
+ }
204
+ }
205
+ }
206
+ if (!meta.isMixin) {
207
+ colDefs.push(["id", tm.idType, "PRIMARY KEY"]);
208
+ persistence.generatedTables[meta.name] = true;
209
+ queries.push([dialect.createTable(meta.name, colDefs), null]);
210
+ }
211
+ }
212
+ }
213
+ var fns = persistence.schemaSyncHooks;
214
+ for(var i = 0; i < fns.length; i++) {
215
+ fns[i](tx);
216
+ }
217
+ if(emulate) {
218
+ // Done
219
+ callback(tx);
220
+ } else {
221
+ executeQueriesSeq(tx, queries, function(_, err) {
222
+ callback(tx, err);
223
+ });
224
+ }
225
+ };
226
+
227
+ /**
228
+ * Persists all changes to the database transaction
229
+ *
230
+ * @param tx
231
+ * transaction to use
232
+ * @param callback
233
+ * function to be called when done
234
+ */
235
+ persistence.flush = function (tx, callback) {
236
+ var args = argspec.getArgs(arguments, [
237
+ { name: "tx", optional: true, check: persistence.isTransaction },
238
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: null }
239
+ ]);
240
+ tx = args.tx;
241
+ callback = args.callback;
242
+
243
+ var session = this;
244
+ if(!tx) {
245
+ this.transaction(function(tx) { session.flush(tx, callback); });
246
+ return;
247
+ }
248
+ var fns = persistence.flushHooks;
249
+ persistence.asyncForEach(fns, function(fn, callback) {
250
+ fn(session, tx, callback);
251
+ }, function() {
252
+ // After applying the hooks
253
+ var persistObjArray = [];
254
+ for (var id in session.trackedObjects) {
255
+ if (session.trackedObjects.hasOwnProperty(id)) {
256
+ persistObjArray.push(session.trackedObjects[id]);
257
+ }
258
+ }
259
+ var removeObjArray = [];
260
+ for (var id in session.objectsToRemove) {
261
+ if (session.objectsToRemove.hasOwnProperty(id)) {
262
+ removeObjArray.push(session.objectsToRemove[id]);
263
+ delete session.trackedObjects[id]; // Stop tracking
264
+ }
265
+ }
266
+ session.objectsToRemove = {};
267
+ if(callback) {
268
+ persistence.asyncParForEach(removeObjArray, function(obj, callback) {
269
+ remove(obj, tx, callback);
270
+ }, function(result, err) {
271
+ if (err) return callback(result, err);
272
+ persistence.asyncParForEach(persistObjArray, function(obj, callback) {
273
+ save(obj, tx, callback);
274
+ }, callback);
275
+ });
276
+ } else { // More efficient
277
+ for(var i = 0; i < persistObjArray.length; i++) {
278
+ save(persistObjArray[i], tx);
279
+ }
280
+ for(var i = 0; i < removeObjArray.length; i++) {
281
+ remove(removeObjArray[i], tx);
282
+ }
283
+ }
284
+ });
285
+ };
286
+
287
+ /**
288
+ * Remove all tables in the database (as defined by the model)
289
+ */
290
+ persistence.reset = function (tx, callback) {
291
+ var args = argspec.getArgs(arguments, [
292
+ { name: "tx", optional: true, check: persistence.isTransaction, defaultValue: null },
293
+ { name: "callback", optional: true, check: argspec.isCallback(), defaultValue: function(){} }
294
+ ]);
295
+ tx = args.tx;
296
+ callback = args.callback;
297
+
298
+ var session = this;
299
+ if(!tx) {
300
+ session.transaction(function(tx) { session.reset(tx, callback); });
301
+ return;
302
+ }
303
+ // First emulate syncing the schema (to know which tables were created)
304
+ this.schemaSync(tx, function() {
305
+ var tableArray = [];
306
+ for (var p in persistence.generatedTables) {
307
+ if (persistence.generatedTables.hasOwnProperty(p)) {
308
+ tableArray.push(p);
309
+ }
310
+ }
311
+ function dropOneTable () {
312
+ var tableName = tableArray.pop();
313
+ tx.executeSql("DROP TABLE IF EXISTS `" + tableName + "`", null, function () {
314
+ if (tableArray.length > 0) {
315
+ dropOneTable();
316
+ } else {
317
+ cb();
318
+ }
319
+ }, cb);
320
+ }
321
+ if(tableArray.length > 0) {
322
+ dropOneTable();
323
+ } else {
324
+ cb();
325
+ }
326
+
327
+ function cb(result, err) {
328
+ session.clean();
329
+ persistence.generatedTables = {};
330
+ if (callback) callback(result, err);
331
+ }
332
+ }, true);
333
+ };
334
+
335
+ /**
336
+ * Converts a database row into an entity object
337
+ */
338
+ function rowToEntity(session, entityName, row, prefix) {
339
+ prefix = prefix || '';
340
+ if (session.trackedObjects[row[prefix + "id"]]) { // Cached version
341
+ return session.trackedObjects[row[prefix + "id"]];
342
+ }
343
+ var tm = persistence.typeMapper;
344
+ var rowMeta = persistence.getMeta(entityName);
345
+ var ent = persistence.define(entityName); // Get entity
346
+ if(!row[prefix+'id']) { // null value, no entity found
347
+ return null;
348
+ }
349
+ var o = new ent(session, undefined, true);
350
+ o.id = tm.dbValToEntityVal(row[prefix + 'id'], tm.idType);
351
+ o._new = false;
352
+ for ( var p in row) {
353
+ if (row.hasOwnProperty(p)) {
354
+ if (p.substring(0, prefix.length) === prefix) {
355
+ var prop = p.substring(prefix.length);
356
+ if (prop != 'id') {
357
+ o._data[prop] = tm.dbValToEntityVal(row[p], rowMeta.fields[prop] || tm.idType);
358
+ }
359
+ }
360
+ }
361
+ }
362
+ return o;
363
+ }
364
+
365
+ /**
366
+ * Internal function to persist an object to the database
367
+ * this function is invoked by persistence.flush()
368
+ */
369
+ function save(obj, tx, callback) {
370
+ var meta = persistence.getMeta(obj._type);
371
+ var tm = persistence.typeMapper;
372
+ var properties = [];
373
+ var values = [];
374
+ var qs = [];
375
+ var propertyPairs = [];
376
+ if(obj._new) { // Mark all properties dirty
377
+ for (var p in meta.fields) {
378
+ if(meta.fields.hasOwnProperty(p)) {
379
+ obj._dirtyProperties[p] = true;
380
+ }
381
+ }
382
+ }
383
+ for ( var p in obj._dirtyProperties) {
384
+ if (obj._dirtyProperties.hasOwnProperty(p)) {
385
+ properties.push("`" + p + "`");
386
+ var type = meta.fields[p] || tm.idType;
387
+ values.push(tm.entityValToDbVal(obj._data[p], type));
388
+ qs.push(tm.outVar("?", type));
389
+ propertyPairs.push("`" + p + "` = " + tm.outVar("?", type));
390
+ }
391
+ }
392
+ var additionalQueries = [];
393
+ for(var p in meta.hasMany) {
394
+ if(meta.hasMany.hasOwnProperty(p)) {
395
+ additionalQueries = additionalQueries.concat(persistence.get(obj, p).persistQueries());
396
+ }
397
+ }
398
+ executeQueriesSeq(tx, additionalQueries, function() {
399
+ if (!obj._new && properties.length === 0) { // Nothing changed and not new
400
+ if(callback) callback();
401
+ return;
402
+ }
403
+ obj._dirtyProperties = {};
404
+ if (obj._new) {
405
+ properties.push('id');
406
+ values.push(tm.entityIdToDbId(obj.id));
407
+ qs.push(tm.outIdVar('?'));
408
+ var sql = "INSERT INTO `" + obj._type + "` (" + properties.join(", ") + ") VALUES (" + qs.join(', ') + ")";
409
+ obj._new = false;
410
+ tx.executeSql(sql, values, callback, callback);
411
+ } else {
412
+ var sql = "UPDATE `" + obj._type + "` SET " + propertyPairs.join(',') + " WHERE id = " + tm.outId(obj.id);
413
+ tx.executeSql(sql, values, callback, callback);
414
+ }
415
+ });
416
+ }
417
+
418
+ persistence.save = save;
419
+
420
+ function remove (obj, tx, callback) {
421
+ var meta = persistence.getMeta(obj._type);
422
+ var tm = persistence.typeMapper;
423
+ var queries = [["DELETE FROM `" + obj._type + "` WHERE id = " + tm.outId(obj.id), null]];
424
+ for (var rel in meta.hasMany) {
425
+ if (meta.hasMany.hasOwnProperty(rel) && meta.hasMany[rel].manyToMany) {
426
+ var tableName = meta.hasMany[rel].tableName;
427
+ //var inverseProperty = meta.hasMany[rel].inverseProperty;
428
+ queries.push(["DELETE FROM `" + tableName + "` WHERE `" + meta.name + '_' + rel + "` = " + tm.outId(obj.id), null]);
429
+ }
430
+ }
431
+ executeQueriesSeq(tx, queries, callback);
432
+ }
433
+
434
+ /**
435
+ * Utility function to execute a series of queries in an asynchronous way
436
+ * @param tx the transaction to execute the queries on
437
+ * @param queries an array of [query, args] tuples
438
+ * @param callback the function to call when all queries have been executed
439
+ */
440
+ function executeQueriesSeq (tx, queries, callback) {
441
+ // queries.reverse();
442
+ var callbackArgs = [];
443
+ for ( var i = 3; i < arguments.length; i++) {
444
+ callbackArgs.push(arguments[i]);
445
+ }
446
+ persistence.asyncForEach(queries, function(queryTuple, callback) {
447
+ tx.executeSql(queryTuple[0], queryTuple[1], callback, function(_, err) {
448
+ console.log(err.message);
449
+ callback(_, err);
450
+ });
451
+ }, function(result, err) {
452
+ if (err && callback) {
453
+ callback(result, err);
454
+ return;
455
+ }
456
+ if(callback) callback.apply(null, callbackArgs);
457
+ });
458
+ }
459
+
460
+ persistence.executeQueriesSeq = executeQueriesSeq;
461
+
462
+ /////////////////////////// QueryCollection patches to work in SQL environment
463
+
464
+ /**
465
+ * Function called when session is flushed, returns list of SQL queries to execute
466
+ * (as [query, arg] tuples)
467
+ */
468
+ persistence.QueryCollection.prototype.persistQueries = function() { return []; };
469
+
470
+ var oldQCClone = persistence.QueryCollection.prototype.clone;
471
+
472
+ persistence.QueryCollection.prototype.clone = function (cloneSubscribers) {
473
+ var c = oldQCClone.call(this, cloneSubscribers);
474
+ c._additionalJoinSqls = this._additionalJoinSqls.slice(0);
475
+ c._additionalWhereSqls = this._additionalWhereSqls.slice(0);
476
+ c._additionalGroupSqls = this._additionalGroupSqls.slice(0);
477
+ c._manyToManyFetch = this._manyToManyFetch;
478
+ return c;
479
+ };
480
+
481
+ var oldQCInit = persistence.QueryCollection.prototype.init;
482
+
483
+ persistence.QueryCollection.prototype.init = function(session, entityName, constructor) {
484
+ oldQCInit.call(this, session, entityName, constructor);
485
+ this._manyToManyFetch = null;
486
+ this._additionalJoinSqls = [];
487
+ this._additionalWhereSqls = [];
488
+ this._additionalGroupSqls = [];
489
+ };
490
+
491
+ var oldQCToUniqueString = persistence.QueryCollection.prototype.toUniqueString;
492
+
493
+ persistence.QueryCollection.prototype.toUniqueString = function() {
494
+ var s = oldQCToUniqueString.call(this);
495
+ s += '|JoinSQLs:';
496
+ for(var i = 0; i < this._additionalJoinSqls.length; i++) {
497
+ s += this._additionalJoinSqls[i];
498
+ }
499
+ s += '|WhereSQLs:';
500
+ for(var i = 0; i < this._additionalWhereSqls.length; i++) {
501
+ s += this._additionalWhereSqls[i];
502
+ }
503
+ s += '|GroupSQLs:';
504
+ for(var i = 0; i < this._additionalGroupSqls.length; i++) {
505
+ s += this._additionalGroupSqls[i];
506
+ }
507
+ if(this._manyToManyFetch) {
508
+ s += '|ManyToManyFetch:';
509
+ s += JSON.stringify(this._manyToManyFetch); // TODO: Do something more efficient
510
+ }
511
+ return s;
512
+ };
513
+
514
+ persistence.NullFilter.prototype.sql = function (meta, alias, values) {
515
+ return "1=1";
516
+ };
517
+
518
+ persistence.AndFilter.prototype.sql = function (meta, alias, values) {
519
+ return "(" + this.left.sql(meta, alias, values) + " AND "
520
+ + this.right.sql(meta, alias, values) + ")";
521
+ };
522
+
523
+ persistence.OrFilter.prototype.sql = function (meta, alias, values) {
524
+ return "(" + this.left.sql(meta, alias, values) + " OR "
525
+ + this.right.sql(meta, alias, values) + ")";
526
+ };
527
+
528
+ persistence.PropertyFilter.prototype.sql = function (meta, alias, values) {
529
+ var tm = persistence.typeMapper;
530
+ var aliasPrefix = alias ? "`" + alias + "`." : "";
531
+ var sqlType = meta.fields[this.property] || tm.idType;
532
+ if (this.operator === '=' && this.value === null) {
533
+ return aliasPrefix + '`' + this.property + "` IS NULL";
534
+ } else if (this.operator === '!=' && this.value === null) {
535
+ return aliasPrefix + '`' + this.property + "` IS NOT NULL";
536
+ } else if (this.operator === 'in') {
537
+ var vals = this.value;
538
+ var qs = [];
539
+ for(var i = 0; i < vals.length; i++) {
540
+ qs.push('?');
541
+ values.push(tm.entityValToDbVal(vals[i], sqlType));
542
+ }
543
+ if(vals.length === 0) {
544
+ // Optimize this a little
545
+ return "1 = 0";
546
+ } else {
547
+ return aliasPrefix + '`' + this.property + "` IN (" + qs.join(', ') + ")";
548
+ }
549
+ } else if (this.operator === 'not in') {
550
+ var vals = this.value;
551
+ var qs = [];
552
+ for(var i = 0; i < vals.length; i++) {
553
+ qs.push('?');
554
+ values.push(tm.entityValToDbVal(vals[i], sqlType));
555
+ }
556
+
557
+ if(vals.length === 0) {
558
+ // Optimize this a little
559
+ return "1 = 1";
560
+ } else {
561
+ return aliasPrefix + '`' + this.property + "` NOT IN (" + qs.join(', ') + ")";
562
+ }
563
+ } else {
564
+ var value = this.value;
565
+ if(value === true || value === false) {
566
+ value = value ? 1 : 0;
567
+ }
568
+ values.push(tm.entityValToDbVal(value, sqlType));
569
+ return aliasPrefix + '`' + this.property + "` " + this.operator + " " + tm.outVar("?", sqlType);
570
+ }
571
+ };
572
+
573
+ // QueryColleciton's list
574
+
575
+ /**
576
+ * Asynchronous call to actually fetch the items in the collection
577
+ * @param tx transaction to use
578
+ * @param callback function to be called taking an array with
579
+ * result objects as argument
580
+ */
581
+ persistence.DbQueryCollection.prototype.list = function (tx, callback) {
582
+ var args = argspec.getArgs(arguments, [
583
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
584
+ { name: 'callback', optional: false, check: argspec.isCallback() }
585
+ ]);
586
+ tx = args.tx;
587
+ callback = args.callback;
588
+
589
+ var that = this;
590
+ var session = this._session;
591
+ if(!tx) { // no transaction supplied
592
+ session.transaction(function(tx) {
593
+ that.list(tx, callback);
594
+ });
595
+ return;
596
+ }
597
+ var entityName = this._entityName;
598
+ var meta = persistence.getMeta(entityName);
599
+ var tm = persistence.typeMapper;
600
+
601
+ // handles mixin case -- this logic is generic and could be in persistence.
602
+ if (meta.isMixin) {
603
+ var result = [];
604
+ persistence.asyncForEach(meta.mixedIns, function(realMeta, next) {
605
+ var query = that.clone();
606
+ query._entityName = realMeta.name;
607
+ query.list(tx, function(array) {
608
+ result = result.concat(array);
609
+ next();
610
+ });
611
+ }, function() {
612
+ var query = new persistence.LocalQueryCollection(result);
613
+ query._orderColumns = that._orderColumns;
614
+ query._reverse = that._reverse;
615
+ // TODO: handle skip and limit -- do we really want to do it?
616
+ query.list(null, callback);
617
+ });
618
+ return;
619
+ }
620
+
621
+ function selectAll (meta, tableAlias, prefix) {
622
+ var selectFields = [ tm.inIdVar("`" + tableAlias + "`.id") + " AS " + prefix + "id" ];
623
+ for ( var p in meta.fields) {
624
+ if (meta.fields.hasOwnProperty(p)) {
625
+ selectFields.push(tm.inVar("`" + tableAlias + "`.`" + p + "`", meta.fields[p]) + " AS `"
626
+ + prefix + p + "`");
627
+ }
628
+ }
629
+ for ( var p in meta.hasOne) {
630
+ if (meta.hasOne.hasOwnProperty(p)) {
631
+ selectFields.push(tm.inIdVar("`" + tableAlias + "`.`" + p + "`") + " AS `"
632
+ + prefix + p + "`");
633
+ }
634
+ }
635
+ return selectFields;
636
+ }
637
+ var args = [];
638
+ var mainPrefix = entityName + "_";
639
+
640
+ var mainAlias = 'root';
641
+ var selectFields = selectAll(meta, mainAlias, mainPrefix);
642
+
643
+ var joinSql = '';
644
+ var additionalWhereSqls = this._additionalWhereSqls.slice(0);
645
+ var mtm = this._manyToManyFetch;
646
+ if(mtm) {
647
+ joinSql += "LEFT JOIN `" + mtm.table + "` AS mtm ON mtm.`" + mtm.inverseProp + "` = `root`.`id` ";
648
+ additionalWhereSqls.push("mtm.`" + mtm.prop + "` = " + tm.outId(mtm.id));
649
+ }
650
+
651
+ joinSql += this._additionalJoinSqls.join(' ');
652
+
653
+ for ( var i = 0; i < this._prefetchFields.length; i++) {
654
+ var prefetchField = this._prefetchFields[i];
655
+ var thisMeta = meta.hasOne[prefetchField].type.meta;
656
+ if (thisMeta.isMixin)
657
+ throw new Error("cannot prefetch a mixin");
658
+ var tableAlias = thisMeta.name + '_' + prefetchField + "_tbl";
659
+ selectFields = selectFields.concat(selectAll(thisMeta, tableAlias,
660
+ prefetchField + "_"));
661
+ joinSql += "LEFT JOIN `" + thisMeta.name + "` AS `" + tableAlias
662
+ + "` ON `" + tableAlias + "`.`id` = `" + mainAlias + '`.`' + prefetchField + "` ";
663
+
664
+ }
665
+
666
+ var whereSql = "WHERE "
667
+ + [ this._filter.sql(meta, mainAlias, args) ].concat(additionalWhereSqls).join(' AND ');
668
+
669
+ var sql = "SELECT " + selectFields.join(", ") + " FROM `" + entityName
670
+ + "` AS `" + mainAlias + "` " + joinSql + " " + whereSql;
671
+
672
+ if(this._additionalGroupSqls.length > 0) {
673
+ sql += this._additionalGroupSqls.join(' ');
674
+ }
675
+
676
+ if(this._orderColumns.length > 0) {
677
+ sql += " ORDER BY "
678
+ + this._orderColumns.map(
679
+ function (c) {
680
+ return (c[2] ? "`" : "LOWER(`") + mainPrefix + c[0] + (c[2] ? "` " : "`) ")
681
+ + (c[1] ? "ASC" : "DESC");
682
+ }).join(", ");
683
+ }
684
+ if(this._limit >= 0) {
685
+ sql += " LIMIT " + this._limit;
686
+ }
687
+ if(this._skip > 0) {
688
+ sql += " OFFSET " + this._skip;
689
+ }
690
+ session.flush(tx, function () {
691
+ tx.executeSql(sql, args, function (rows) {
692
+ var results = [];
693
+ if(that._reverse) {
694
+ rows.reverse();
695
+ }
696
+ for ( var i = 0; i < rows.length; i++) {
697
+ var r = rows[i];
698
+ var e = rowToEntity(session, entityName, r, mainPrefix);
699
+ for ( var j = 0; j < that._prefetchFields.length; j++) {
700
+ var prefetchField = that._prefetchFields[j];
701
+ var thisMeta = meta.hasOne[prefetchField].type.meta;
702
+ e._data_obj[prefetchField] = rowToEntity(session, thisMeta.name, r, prefetchField + '_');
703
+ session.add(e._data_obj[prefetchField]);
704
+ }
705
+ results.push(e);
706
+ session.add(e);
707
+ }
708
+ callback(results);
709
+ that.triggerEvent('list', that, results);
710
+ });
711
+ });
712
+ };
713
+
714
+ /**
715
+ * Asynchronous call to remove all the items in the collection.
716
+ * Note: does not only remove the items from the collection, but
717
+ * the items themselves.
718
+ * @param tx transaction to use
719
+ * @param callback function to be called when clearing has completed
720
+ */
721
+ persistence.DbQueryCollection.prototype.destroyAll = function (tx, callback) {
722
+ var args = argspec.getArgs(arguments, [
723
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
724
+ { name: 'callback', optional: true, check: argspec.isCallback(), defaultValue: function(){} }
725
+ ]);
726
+ tx = args.tx;
727
+ callback = args.callback;
728
+
729
+ var that = this;
730
+ var session = this._session;
731
+ if(!tx) { // no transaction supplied
732
+ session.transaction(function(tx) {
733
+ that.destroyAll(tx, callback);
734
+ });
735
+ return;
736
+ }
737
+ var entityName = this._entityName;
738
+ var meta = persistence.getMeta(entityName);
739
+ var tm = persistence.typeMapper;
740
+
741
+ // handles mixin case -- this logic is generic and could be in persistence.
742
+ if (meta.isMixin) {
743
+ persistence.asyncForEach(meta.mixedIns, function(realMeta, next) {
744
+ var query = that.clone();
745
+ query._entityName = realMeta.name;
746
+ query.destroyAll(tx, callback);
747
+ }, callback);
748
+ return;
749
+ }
750
+
751
+ var joinSql = '';
752
+ var additionalWhereSqls = this._additionalWhereSqls.slice(0);
753
+ var mtm = this._manyToManyFetch;
754
+ if(mtm) {
755
+ joinSql += "LEFT JOIN `" + mtm.table + "` AS mtm ON mtm.`" + mtm.inverseProp + "` = `root`.`id` ";
756
+ additionalWhereSqls.push("mtm.`" + mtm.prop + "` = " + tm.outId(mtm.id));
757
+ }
758
+
759
+ joinSql += this._additionalJoinSqls.join(' ');
760
+
761
+ var args = [];
762
+ var whereSql = "WHERE "
763
+ + [ this._filter.sql(meta, null, args) ].concat(additionalWhereSqls).join(' AND ');
764
+
765
+ var selectSql = "SELECT id FROM `" + entityName + "` " + joinSql + ' ' + whereSql;
766
+ var deleteSql = "DELETE FROM `" + entityName + "` " + joinSql + ' ' + whereSql;
767
+ var args2 = args.slice(0);
768
+
769
+ session.flush(tx, function () {
770
+ tx.executeSql(selectSql, args, function(results) {
771
+ for(var i = 0; i < results.length; i++) {
772
+ delete session.trackedObjects[results[i].id];
773
+ session.objectsRemoved.push({id: results[i].id, entity: entityName});
774
+ }
775
+ that.triggerEvent('change', that);
776
+ tx.executeSql(deleteSql, args2, callback, callback);
777
+ }, callback);
778
+ });
779
+ };
780
+
781
+ /**
782
+ * Asynchronous call to count the number of items in the collection.
783
+ * @param tx transaction to use
784
+ * @param callback function to be called when clearing has completed
785
+ */
786
+ persistence.DbQueryCollection.prototype.count = function (tx, callback) {
787
+ var args = argspec.getArgs(arguments, [
788
+ { name: 'tx', optional: true, check: persistence.isTransaction, defaultValue: null },
789
+ { name: 'callback', optional: false, check: argspec.isCallback() }
790
+ ]);
791
+ tx = args.tx;
792
+ callback = args.callback;
793
+
794
+ var that = this;
795
+ var session = this._session;
796
+ if(tx && !tx.executeSql) { // provided callback as first argument
797
+ callback = tx;
798
+ tx = null;
799
+ }
800
+ if(!tx) { // no transaction supplied
801
+ session.transaction(function(tx) {
802
+ that.count(tx, callback);
803
+ });
804
+ return;
805
+ }
806
+ var entityName = this._entityName;
807
+ var meta = persistence.getMeta(entityName);
808
+ var tm = persistence.typeMapper;
809
+
810
+ // handles mixin case -- this logic is generic and could be in persistence.
811
+ if (meta.isMixin) {
812
+ var result = 0;
813
+ persistence.asyncForEach(meta.mixedIns, function(realMeta, next) {
814
+ var query = that.clone();
815
+ query._entityName = realMeta.name;
816
+ query.count(tx, function(count) {
817
+ result += count;
818
+ next();
819
+ });
820
+ }, function() {
821
+ callback(result);
822
+ });
823
+ return;
824
+ }
825
+
826
+ var joinSql = '';
827
+ var additionalWhereSqls = this._additionalWhereSqls.slice(0);
828
+ var mtm = this._manyToManyFetch;
829
+ if(mtm) {
830
+ joinSql += "LEFT JOIN `" + mtm.table + "` AS mtm ON mtm.`" + mtm.inverseProp + "` = `root`.`id` ";
831
+ additionalWhereSqls.push("mtm.`" + mtm.prop + "` = " + tm.outId(mtm.id));
832
+ }
833
+
834
+ joinSql += this._additionalJoinSqls.join(' ');
835
+ var args = [];
836
+ var whereSql = "WHERE " + [ this._filter.sql(meta, "root", args) ].concat(additionalWhereSqls).join(' AND ');
837
+
838
+ var sql = "SELECT COUNT(*) AS cnt FROM `" + entityName + "` AS `root` " + joinSql + " " + whereSql;
839
+
840
+ session.flush(tx, function () {
841
+ tx.executeSql(sql, args, function(results) {
842
+ callback(parseInt(results[0].cnt, 10));
843
+ });
844
+ });
845
+ };
846
+
847
+ persistence.ManyToManyDbQueryCollection.prototype.persistQueries = function() {
848
+ var queries = [];
849
+ var meta = persistence.getMeta(this._obj._type);
850
+ var inverseMeta = meta.hasMany[this._coll].type.meta;
851
+ var tm = persistence.typeMapper;
852
+ var rel = meta.hasMany[this._coll];
853
+ var inv = inverseMeta.hasMany[rel.inverseProperty];
854
+ var direct = rel.mixin ? rel.mixin.meta.name : meta.name;
855
+ var inverse = inv.mixin ? inv.mixin.meta.name : inverseMeta.name;
856
+
857
+ // Added
858
+ for(var i = 0; i < this._localAdded.length; i++) {
859
+ var columns = [direct + "_" + this._coll, inverse + '_' + rel.inverseProperty];
860
+ var vars = [tm.outIdVar("?"), tm.outIdVar("?")];
861
+ var args = [tm.entityIdToDbId(this._obj.id), tm.entityIdToDbId(this._localAdded[i].id)];
862
+ if (rel.mixin) {
863
+ columns.push(direct + "_" + this._coll + "_class");
864
+ vars.push("?");
865
+ args.push(meta.name);
866
+ }
867
+ if (inv.mixin) {
868
+ columns.push(inverse + "_" + rel.inverseProperty + "_class");
869
+ vars.push("?");
870
+ args.push(inverseMeta.name);
871
+ }
872
+ queries.push(["INSERT INTO " + rel.tableName +
873
+ " (`" + columns.join("`, `") + "`) VALUES (" + vars.join(",") + ")", args]);
874
+ }
875
+ this._localAdded = [];
876
+ // Removed
877
+ for(var i = 0; i < this._localRemoved.length; i++) {
878
+ queries.push(["DELETE FROM " + rel.tableName +
879
+ " WHERE `" + direct + "_" + this._coll + "` = " + tm.outIdVar("?") + " AND `" +
880
+ inverse + '_' + rel.inverseProperty +
881
+ "` = " + tm.outIdVar("?"), [tm.entityIdToDbId(this._obj.id), tm.entityIdToDbId(this._localRemoved[i].id)]]);
882
+ }
883
+ this._localRemoved = [];
884
+ return queries;
885
+ };
886
+ };
887
+
888
+ if (typeof exports !== 'undefined') {
889
+ exports.defaultTypeMapper = defaultTypeMapper;
890
+ exports.config = config;
891
+ }
892
+ else {
893
+ window = window || {};
894
+ window.persistence = window.persistence || {};
895
+ window.persistence.store = window.persistence.store || {};
896
+ window.persistence.store.sql = {
897
+ defaultTypeMapper: defaultTypeMapper,
898
+ config: config
899
+ };
900
+ }