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