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,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
+ }