· 8 years ago · Dec 19, 2017, 10:16 AM
1'use strict';
2
3angular.module('shared')
4 .factory('$database', ['$q', '$migrations', 'DEBUG_DB',
5 function ($q, $migrations, DEBUG_DB) {
6
7 var Factory = function (config) {
8 this.data = {};
9 this.data.name = 'db';
10 this.data.description = Factory.name;
11 this.data.version = '1.0';
12 this.data.size = 10 * 1024 * 1024;
13 this.data.conn = null;
14 this.data.ready = false;
15 this.data.queue = [];
16
17 if (config) {
18 if (config.name) this.data.name = config.name;
19 if (config.description) this.data.description = config.description;
20 if (config.version) this.data.version = config.version;
21 if (config.size) this.data.size = config.size * 1024 * 1024;
22 }
23
24 return this;
25 };
26
27 //----------------------------------------------------------------------------------------------------------
28 // PROPERTIES
29 //----------------------------------------------------------------------------------------------------------
30
31 Factory.prototype.name = function () {
32 return this.data.name;
33 };
34
35 Factory.prototype.description = function () {
36 return this.data.description;
37 };
38
39 Factory.prototype.version = function () {
40 return this.data.version;
41 };
42
43 Factory.prototype.size = function () {
44 return this.data.size;
45 };
46
47 Factory.prototype.conn = function () {
48 return this.data.conn;
49 };
50
51 Factory.prototype.opened = function () {
52 return !!this.data.conn;
53 };
54
55 Factory.prototype.ready = function () {
56 return this.data.ready;
57 };
58
59 //----------------------------------------------------------------------------------------------------------
60 // HELPERS
61 //----------------------------------------------------------------------------------------------------------
62
63 Factory.prototype.prepareVar = function (v, toStr, clon) {
64 if (v instanceof Date) {
65 v = v.toISOString();
66 } else if (v === true) {
67 v = 1;
68 } else if (v === false) {
69 v = 0;
70 }
71 if (toStr) {
72 if (v === undefined) {
73 v = '';
74 } else if (v === null) {
75 return 'NULL';
76 } else {
77 v = v.toString().replace(/'/g, "\\'");
78 }
79 if (clon) {
80 v = "'" + v + "'";
81 }
82 }
83 return v;
84 };
85
86 Factory.prototype.prepareParams = function (params) {
87 if (!params || (params.length === 0)) {
88 return params;
89 }
90 var p2 = [];
91 for (var i = 0; i < params.length; i++) {
92 p2.push(this.prepareVar(params[i]));
93 }
94 return p2;
95 };
96
97 //----------------------------------------------------------------------------------------------------------
98
99 Factory.prototype.open = function () {
100 var me = this;
101 var deferred = $q.defer();
102
103 me.data.conn = typeof window.sqlitePlugin !== 'undefined'
104 ? window.sqlitePlugin.openDatabase({name: me.name(), version: me.version()})
105 : window.openDatabase(me.name(), me.version(), me.description(), me.size());
106
107 if (DEBUG_DB)
108 console.info('$db(' + me.name() + '): opened');
109
110 if (me.data.conn) {
111 $q.all(me.migrate()).then(function () {
112 me.data.ready = true;
113 if (DEBUG_DB)
114 console.info('$db(' + me.name() + '): ready');
115 me.queue().then(function () {
116 if (DEBUG_DB)
117 console.info('$db(' + me.name() + '): queue finished');
118 deferred.resolve();
119 });
120 });
121 } else {
122 deferred.reject(new Error('database "' + me.name() + '" is not accessible.'));
123 }
124
125 return deferred.promise
126 };
127
128 Factory.prototype.migrate = function () {
129
130 var me = this;
131 var files = $migrations.files;
132 var commands = $migrations.commands;
133 var deferred = $q.defer();
134
135 html5sql.database = me.data.conn;
136 html5sql.readTransactionAvailable = typeof me.data.conn.readTransaction === 'function';
137 if (DEBUG_DB)
138 html5sql.logInfo = html5sql.logErrors = true;
139
140 if (DEBUG_DB)
141 console.info('$db(' + me.name() + '): processing migrations');
142
143 var processFilesRecursively = function () {
144 var file = files.shift();
145 if (typeof file !== 'undefined') {
146 processFile(file);
147 } else {
148 if (DEBUG_DB)
149 console.info('$db(' + me.name() + '): migration finished');
150 deferred.resolve(true);
151 }
152 };
153
154 var processFile = function (file) {
155 $.get('' + file, function (sql) {
156 html5sql.process(sql, function () {
157 processFilesRecursively();
158 }, function (error) {
159 console.error('Erro ao processar a migration:', file, error);
160 processFilesRecursively();
161 });
162 }).fail(function (error, failingQuery) {
163 console.error('Erro ao recuperar o arquivo da migration:', file, error, failingQuery);
164 processFilesRecursively();
165 });
166 };
167
168 var processCommandsRecursively = function () {
169 var command = commands.shift();
170 if (typeof command !== 'undefined') {
171 processCommands(command);
172 } else {
173 processFilesRecursively();
174 }
175 };
176
177 var processCommands = function (command) {
178 me.run(command, {}, true).then(function () {
179 processCommandsRecursively();
180 }, function (error) {
181 console.error('Erro ao processar a migration:', command, error);
182 processCommandsRecursively();
183 });
184 };
185
186 me.run('SELECT file FROM migrations ORDER BY created_at', {}, true).then(function () {
187 if (DEBUG_DB)
188 console.info('$db(' + me.name() + '): up all migrations');
189 processCommandsRecursively();
190 }, function () {
191 if (DEBUG_DB)
192 console.info('$db(' + me.name() + '): up new migrations');
193 processCommandsRecursively();
194 });
195
196 return deferred;
197 };
198
199 Factory.prototype.queue = function () {
200 var me = this;
201 var promises = [];
202
203 if (DEBUG_DB)
204 console.info('$db(' + me.name() + '): processing queue');
205
206 //for (var i = 0; i < this.data.queue.length; i++)
207 // promises.push(this.data.queue[i].apply(this));
208 //this.data.queue = [];
209
210 while (this.data.queue.length > 0) {
211 var fn = this.data.queue[0];
212 this.data.queue.splice(0, 1);
213 promises.push(fn.apply(this));
214 }
215
216 return $q.all(promises).then(function () {
217 if (DEBUG_DB)
218 console.info('$db(' + me.name() + '): queue finished');
219 });
220 };
221
222 Factory.prototype.backup = function () {
223 if (typeof window.sqlitePlugin !== 'undefined') {
224 var now = new Date();
225 window.sqlitePlugin.backupDatabase(now.getYear() + '.' + now.getMonth() + '.' + now.getDay() + '.' + this.name() + '.db.backup');
226 }
227 };
228
229 //----------------------------------------------------------------------------------------------------------
230 // RUN
231 //----------------------------------------------------------------------------------------------------------
232
233 Factory.prototype.done = function (fn, force) {
234 if (!angular.isFunction(fn)) throw 'Error! "fn" is not a Function';
235
236 if (this.ready() || force === true) {
237 fn.apply(this);
238 } else {
239 if (DEBUG_DB)
240 console.log('$db(' + this.name() + '): push queue');
241 this.data.queue.push(fn);
242 }
243
244 return this;
245 };
246
247 Factory.prototype.run = function (sql, params, force) {
248 var me = this;
249 var deferred = $q.defer();
250
251 var successCallback = null;
252 deferred.promise.success = function (callback) {
253 successCallback = callback;
254 return deferred.promise;
255 };
256
257 var errorCallback = null;
258 deferred.promise.error = function (callback) {
259 errorCallback = callback;
260 return deferred.promise;
261 };
262
263 var completeCallback = null;
264 deferred.promise.complete = function (callback) {
265 completeCallback = callback;
266 return deferred.promise;
267 };
268
269 function onSuccess(SQLTransaction, SQLResultSet) {
270 if (successCallback) successCallback(SQLTransaction, SQLResultSet);
271 if (completeCallback) completeCallback(SQLTransaction, SQLResultSet);
272 deferred.resolve(SQLResultSet);
273 }
274
275 function onError(SQLTransaction, SQLError) {
276 if (errorCallback) errorCallback(SQLTransaction, SQLError);
277 if (completeCallback) completeCallback(SQLTransaction, null, SQLError);
278 console.error(SQLError);
279 deferred.reject(SQLError);
280 }
281
282 me.done(function () {
283 me.conn().transaction(function (SQLTransaction) {
284 var data = me.prepareParams(params);
285
286 if (DEBUG_DB)
287 console.info('$db(' + me.name() + '):', sql, data);
288
289 SQLTransaction.executeSql('' + sql, data, onSuccess, onError);
290 });
291 return deferred.promise;
292 }, force);
293
294 return deferred.promise;
295 };
296
297 //----------------------------------------------------------------------------------------------------------
298 // DML
299 //----------------------------------------------------------------------------------------------------------
300
301 Factory.prototype.exists = function (table) {
302 var promise = this.run("SELECT * FROM sqlite_master WHERE type='table' AND name=?", [table], true);
303
304 var yesCallback = null;
305 promise.yes = function (callback) {
306 yesCallback = callback;
307 return promise;
308 };
309
310 var noCallback = null;
311 promise.no = function (callback) {
312 noCallback = callback;
313 return promise;
314 };
315
316 promise.then(function (SQLResultSet) {
317 if (SQLResultSet.rows.length > 0) {
318 if (yesCallback) yesCallback(SQLResultSet);
319 } else {
320 if (noCallback) noCallback(SQLResultSet);
321 }
322 });
323
324 return promise;
325 };
326
327 Factory.prototype.create = function (table, definition, data, drop) {
328 var me = this;
329 var deferred = $q.defer();
330
331 var first = true;
332 var sql = 'CREATE TABLE IF NOT EXISTS ' + table + ' (';
333 angular.forEach(definition, function (config, field) {
334 first ? first = false : sql += ',';
335 sql += ' ' + field;
336
337 var type;
338 if (typeof config === 'string') {
339 type = config.toUpperCase();
340 config = {};
341 } else if (config) {
342 type = (config.type || '').toUpperCase();
343 } else {
344 config = {};
345 }
346
347 if (type === 'KEY') {
348 sql += ' INTEGER PRIMARY KEY AUTOINCREMENT';
349 } else {
350 // TYPE //
351 if (['BOOL', 'BOOLEAN', 'INTEGER', 'INT'].indexOf(type) >= 0) {
352 sql += ' INTEGER';
353 } else if (['REAL', 'DECIMAL', 'FLOAT'].indexOf(type) >= 0) {
354 sql += ' REAL';
355 } else if (['BLOB', 'LONGTEXT'].indexOf(type) >= 0) {
356 sql += ' BLOB';
357 } else {
358 sql += config.foreign ? ' INTEGER' : ' TEXT';
359 }
360
361 // PRIMARY //
362 if (config.primary)
363 sql += ' PRIMARY KEY';
364
365 // AUTOINCREMENT //
366 if (config.autoincrement)
367 sql += ' AUTOINCREMENT';
368
369 // UNIQUE //
370 if (config.unique)
371 sql += ' UNIQUE';
372
373 // IS NULL / NOT NULL //
374 sql += config.null === false ? ' NOT NULL' : ' NULL';
375
376 // DEFAULT //
377 if (config.default !== undefined)
378 sql += ' DEFAULT ' + this.prepareVar(config.default, true, true);
379
380 // FOREIGN //
381 if (config.foreign) {
382 if (typeof config.foreign === 'string') {
383 var parts = config.foreign.split('.');
384 config = {
385 table: parts[0],
386 key: parts[1] || 'id'
387 };
388 } else {
389 config = config.foreign;
390 if (!config.key) {
391 config.key = 'id';
392 }
393 }
394 sql += ' REFERENCES ' + config.table + '(' + config.key + ')';
395 }
396 }
397 });
398 sql += ' )';
399
400 function finish() {
401 deferred.resolve();
402 }
403
404 function create() {
405 me.run(sql, [], true).then(function () {
406 data && data.length > 0 ? me.ir(table, data, 'INSERT', true).then(finish) : finish();
407 finish();
408 });
409 }
410
411 drop ? me.drop(table).then(create) : me.exists(table).yes(finish).no(create);
412
413 return deferred.promise;
414 };
415
416 Factory.prototype.alter = function (table, definition, data) {
417 alert('$db.alter em construção...');
418 };
419
420 Factory.prototype.truncate = function (table) {
421 return this.run('DELETE FROM ' + table, [], true);
422 };
423
424 Factory.prototype.drop = function (table) {
425 return this.run('DROP TABLE IF EXISTS ' + table, [], true);
426 };
427
428 //----------------------------------------------------------------------------------------------------------
429 // DML
430 //----------------------------------------------------------------------------------------------------------
431
432 Factory.prototype.select = function (table, columns, where, order, limit) {
433 alert('$db.select em construção...');
434 };
435
436 Factory.prototype.ir = function (table, data, action, force) {
437 var promises = [];
438
439 var items = (data instanceof [].constructor) ? data : [data];
440 for (var i = 0; i < items.length; i++) {
441 var item = items[i];
442 var columns = '', values = '', params = [];
443 for (var property in item) {
444 if (item.hasOwnProperty(property)) {
445 if (columns) {
446 columns += ', ';
447 values += ', ';
448 }
449 columns += property;
450 values += '?';
451 params.push(item[property]);
452 }
453 }
454
455 promises.push(this.run(action + ' INTO ' + table + ' (' + columns + ') VALUES (' + values + ')', params, force));
456 }
457
458 var all = $q.all(promises);
459
460 var successCallback = null;
461 all.success = function (callback) {
462 successCallback = callback;
463 return deferred.promise;
464 };
465
466 var errorCallback = null;
467 all.error = function (callback) {
468 errorCallback = callback;
469 return deferred.promise;
470 };
471
472 var completeCallback = null;
473 all.complete = function (callback) {
474 completeCallback = callback;
475 return deferred.promise;
476 };
477
478 function onSuccess(SQLTransaction, SQLResultSet) {
479 if (successCallback) successCallback(SQLTransaction, SQLResultSet);
480 if (completeCallback) completeCallback(SQLTransaction, SQLResultSet);
481 }
482
483 function onError(SQLTransaction, SQLError) {
484 if (errorCallback) errorCallback(SQLTransaction, SQLError);
485 if (completeCallback) completeCallback(SQLTransaction, null, SQLError);
486 console.error(SQLError);
487 }
488
489 return all.then(onSuccess, onError);
490 };
491
492 Factory.prototype.insert = function (table, data) {
493 return this.ir(table, data, 'INSERT');
494 };
495
496 Factory.prototype.replace = function (table, data) {
497 return this.ir(table, data, 'REPLACE');
498 };
499
500 Factory.prototype.update = function (table, data, where) {
501 alert('$db.update em construção...');
502 };
503
504 Factory.prototype.delete = function (table, where) {
505 alert('$db.delete em construção...');
506 };
507
508 Factory.prototype.deleteById = function (table, id) {
509 return this.deleteBy(table, 'id', id);
510 };
511
512 Factory.prototype.deleteBy = function (table, field, value) {
513 return this.run('DELETE FROM ' + table + ' WHERE ' + field + ' = ?', [value]);
514 };
515
516 //----------------------------------------------------------------------------------------------------------
517
518 return Factory;
519
520 }
521 ]);