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