UNPKG

31.3 kBJavaScriptView Raw
1/**
2 * Module dependencies
3 */
4var mysql = require('mysql');
5var jdb = require('jugglingdb');
6var EnumFactory = require('./enumFactory').EnumFactory;
7
8exports.initialize = function initializeSchema(schema, callback) {
9 if (!mysql) return;
10
11 var s = schema.settings;
12
13 if (s.collation) {
14 s.charset = s.collation.substr(0, s.collation.indexOf('_')); // Charset should be first 'chunk' of collation.
15 } else {
16 s.collation = 'utf8mb4_general_ci';
17 s.charset = 'utf8mb4';
18 }
19
20 s.supportBigNumbers = (s.supportBigNumbers || false);
21 s.timezone = (s.timezone || 'local');
22
23 schema.client = getConnection(s);
24
25 // MySQL specific column types
26 schema.constructor.registerType(function Point() {});
27 // Factory for Enums. Note that currently Enums can not be registered
28 schema.EnumFactory = EnumFactory;
29
30 schema.adapter = new MySQL(schema.client);
31 schema.adapter.schema = schema;
32
33 initializeConnection(schema.client, schema, callback);
34}
35
36function initializeConnection(connection, schema, callback) {
37 // Attach listeners first
38 connection.on('error', function(err) {
39 schema.log('connection error', err);
40 schema.connected = false;
41 });
42
43 if(schema.settings.pool) {
44 connection.on('connection', function(con) {
45 con.query('USE `' + schema.settings.database + '`', function(err) {
46 err && console.log(err);
47 });
48 });
49 initDatabase();
50 }
51 else {
52 connection.connect(function(err) {
53 if(err) {
54 console.log('connection.connect err', err);
55 setTimeout(schema.adapter.connect.bind(schema.adapter, callback), 6000);
56 return;
57 }
58 initDatabase()
59 });
60 }
61 function initDatabase() {
62 connection.query('USE `' + schema.settings.database + '`', function (err) {
63 if (err) {
64 if (err.message.match(/(^|: )unknown database/i)) {
65 var dbName = schema.settings.database;
66 var charset = schema.settings.charset;
67 var collation = schema.settings.collation;
68 var q = 'CREATE DATABASE ' + dbName + ' CHARACTER SET ' + charset + ' COLLATE ' + collation;
69 schema.client.query(q, function (err) {
70 if (!err) {
71 schema.client.query('USE ' + schema.settings.database, callback);
72 } else {
73 throw err;
74 }
75 });
76 } else throw err;
77 } else {
78 callback && callback();
79 }
80 });
81 }
82};
83
84/**
85 * Returns a connection or a connection pool based on the settings object
86 *
87 * @param settings {Object} adapter settings
88 * @return connection {Object} mysql connection object or connection pool
89 */
90function getConnection(settings) {
91 var connection;
92 var connectionSettings = {
93 host: settings.host || 'localhost',
94 port: settings.port || 3306,
95 user: settings.username,
96 password: settings.password,
97 timezone: settings.timezone,
98 debug: settings.debug,
99 socketPath: settings.socketPath,
100 charset: settings.collation.toUpperCase(),
101 supportBigNumbers: settings.supportBigNumbers
102 };
103
104 if (settings.pool) {
105 connectionSettings.connectionLimit = settings.connectionLimit || 10;
106 connectionSettings.queueLimit = settings.queueLimit || 0;
107 connectionSettings.waitForConnections = settings.waitForConnections || true;
108 return mysql.createPool(connectionSettings);
109 }
110 return mysql.createConnection(connectionSettings);
111}
112
113/**
114 * MySQL adapter
115 */
116
117function MySQL(client) {
118 this.name = 'mysql';
119 this._models = {};
120 this.client = client;
121}
122
123require('util').inherits(MySQL, jdb.BaseSQL);
124
125MySQL.prototype.connect = function (callback) {
126 this.client = getConnection(this.schema.settings);
127 initializeConnection(this.client, this.schema, callback);
128};
129
130MySQL.prototype.query = function (sql, callback, quiet) {
131 if (!this.schema.connected) {
132 return this.schema.on('connected', function () {
133 this.query(sql, callback, quiet);
134 }.bind(this));
135 }
136 var client = this.client;
137 var time = Date.now();
138 var log = this.log;
139 if (typeof callback !== 'function') throw new Error('callback should be a function');
140 this.client.query(sql, function (err, data) {
141 if (err && err.message.match(/(^|: )unknown database/i)) {
142 var dbName = err.message.match(/(^|: )unknown database '(.*?)'/i)[1];
143 client.query('CREATE DATABASE ' + dbName, function (error) {
144 if (!error) {
145 client.query(sql, callback);
146 } else {
147 callback(err);
148 }
149 });
150 return;
151 }
152 if (log && !quiet) {
153 log(sql, time);
154 }
155 callback(err, data);
156 });
157};
158
159/**
160 * Must invoke callback(err, id)
161 */
162MySQL.prototype.create = function (model, data, callback) {
163 var fields = this.toFields(model, data);
164 var sql = 'INSERT INTO ' + this.tableEscaped(model);
165 if (fields) {
166 sql += ' SET ' + fields;
167 } else {
168 sql += ' VALUES ()';
169 }
170 this.query(sql, function (err, info) {
171 callback(err, info && info.insertId);
172 });
173};
174
175MySQL.prototype.updateOrCreate = function (model, data, callback) {
176 var mysql = this;
177 var fieldsNames = [];
178 var fieldValues = [];
179 var combined = [];
180 var props = this._models[model].properties;
181 Object.keys(data).forEach(function (key) {
182 if (props[key] || key === 'id') {
183 var k = '`' + key + '`';
184 var v;
185 if (key !== 'id') {
186 v = mysql.toDatabase(props[key], data[key]);
187 } else {
188 v = '"' + data[key] + '"';
189 }
190 fieldsNames.push(k);
191 fieldValues.push(v);
192 if (key !== 'id') combined.push(k + ' = ' + v);
193 }
194 });
195
196 var sql = 'INSERT INTO ' + this.tableEscaped(model);
197 sql += ' (' + fieldsNames.join(', ') + ')';
198 sql += ' VALUES (' + fieldValues.join(', ') + ')';
199 sql += ' ON DUPLICATE KEY UPDATE ' + combined.join(', ');
200
201 this.query(sql, function (err, info) {
202 if (!err && info && info.insertId) {
203 data.id = info.insertId;
204 }
205 callback(err, data);
206 });
207};
208
209MySQL.prototype.toFields = function (model, data) {
210 var fields = [];
211 var props = this._models[model].properties;
212 Object.keys(data).forEach(function (key) {
213 if (props[key]) {
214 var value = this.toDatabase(props[key], data[key]);
215 if ('undefined' === typeof value) return;
216 fields.push('`' + key.replace(/\./g, '`.`') + '` = ' + value);
217 }
218 }.bind(this));
219 return fields.join(',');
220};
221
222function dateToMysql(val) {
223 return val.getUTCFullYear() + '-' +
224 fillZeros(val.getUTCMonth() + 1) + '-' +
225 fillZeros(val.getUTCDate()) + ' ' +
226 fillZeros(val.getUTCHours()) + ':' +
227 fillZeros(val.getUTCMinutes()) + ':' +
228 fillZeros(val.getUTCSeconds());
229
230 function fillZeros(v) {
231 return v < 10 ? '0' + v: v;
232 }
233}
234
235MySQL.prototype.toDatabase = function (prop, val) {
236 if (val === null) return 'NULL';
237 if (typeof val === 'undefined') return 'NULL';
238 if (prop && prop.type.name === 'JSON') {
239 return this.client.escape(JSON.stringify(val));
240 }
241 if (prop && prop.type instanceof Array) {
242 return this.client.escape(JSON.stringify(val));
243 }
244 if (val.constructor.name === 'Array') {
245 return val.map(function (v) {
246 return this.toDatabase(prop, v);
247 }.bind(this));
248 } else if (val.constructor.name === 'Object') {
249 var operator = Object.keys(val)[0]
250 val = val[operator];
251 if (operator === 'between') {
252 return this.toDatabase(prop, val[0]) +
253 ' AND ' +
254 this.toDatabase(prop, val[1]);
255 } else if (operator == 'inq' || operator == 'nin') {
256 if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array
257 for (var i = 0; i < val.length; i++) {
258 val[i] = this.client.escape(val[i]);
259 }
260 return val.join(',');
261 } else {
262 return val;
263 }
264 } else if (!operator) {
265 return 'NULL';
266 }
267 }
268 if (!prop) return val;
269 if (prop.type.name === 'Number') return Number(val);
270 if (prop.type.name === 'Date') {
271 if (!val) return 'NULL';
272 if (!val.toUTCString) {
273 val = new Date(val);
274 }
275 return '"' + dateToMysql(val) + '"';
276 }
277 if (prop.type.name == "Boolean") return val ? 1: 0;
278 if (typeof prop.type === 'function') return this.client.escape(prop.type(val));
279 return this.client.escape(val.toString());
280};
281
282MySQL.prototype.fromDatabase = function (model, data) {
283 if (!data) return null;
284 var props = this._models[model].properties;
285 Object.keys(data).forEach(function (key) {
286 var val = data[key];
287 if (typeof val === 'undefined' || val === null) {
288 return;
289 }
290 if (props[key]) {
291 switch (props[key].type.name) {
292 case 'Date':
293 val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
294 break;
295 case 'Boolean':
296 val = Boolean(val);
297 break;
298 case 'JSON':
299 val = JSON.parse(val);
300 break;
301 }
302 }
303 data[key] = val;
304 });
305 return data;
306};
307
308MySQL.prototype.escapeName = function (name) {
309 return '`' + name.replace(/\./g, '`.`') + '`';
310};
311
312MySQL.prototype.all = function all(model, filter, callback) {
313
314 var sql = 'SELECT * FROM ' + this.tableEscaped(model);
315 var self = this;
316
317 if (filter) {
318
319 if (filter.where) {
320 sql += ' WHERE ' + this._buildWhere(model, filter.where);
321 }
322
323 if (filter.order) {
324 sql += ' ' + buildOrderBy(filter.order);
325 }
326
327 if (filter.limit) {
328 sql += ' ' + buildLimit(filter.limit, filter.skip || 0);
329 }
330
331 }
332
333 this.query(sql, function (err, data) {
334 if (err) {
335 return callback(err, []);
336 }
337
338 var objs = data.map(function (obj) {
339 return self.fromDatabase(model, obj);
340 });
341 if (filter && filter.include) {
342 this._models[model].model.include(objs, filter.include, callback);
343 } else {
344 callback(null, objs);
345 }
346 }.bind(this));
347
348 return sql;
349
350 function buildOrderBy(order) {
351 if (typeof order === 'string') order = [order];
352 return 'ORDER BY ' + order.map(function (o) {
353 var t = o.split(/\s+/);
354 if (t.length === 1) return '`' + o + '`';
355 return '`' + t[0] + '` ' + t[1];
356 }).join(', ');
357 }
358
359 function buildLimit(limit, offset) {
360 return 'LIMIT ' + (offset ? (offset + ', ' + limit): limit);
361 }
362
363};
364
365MySQL.prototype._buildWhere = function (model, conds) {
366 if(!conds){
367 return "";
368 }
369 var self = this;
370 var cs = [];
371 var props = this._models[model].properties;
372 Object.keys(conds).forEach(function (key) {
373 var keyEscaped = '`' + key.replace(/\./g, '`.`') + '`'
374 var val = self.toDatabase(props[key], conds[key]);
375 if (conds[key] === null || conds[key] === undefined) {
376 cs.push(keyEscaped + ' IS NULL');
377 } else if (key.toLowerCase() === 'or' && conds[key] && conds[key].constructor.name === 'Array') {
378 var queries = [];
379 conds[key].forEach(function (cond) {
380 queries.push(self._buildWhere(model, cond));
381 });
382 cs.push('(' + queries.join(' OR ') + ')');
383 } else if (conds[key] && conds[key].constructor.name === 'Array') {
384 cs.push(keyEscaped + ' IN (' + val.join(', ') + ')');
385
386 } else if (conds[key] && conds[key].constructor.name === 'Object') {
387 var condType = Object.keys(conds[key])[0];
388 var sqlCond = keyEscaped;
389 if ((condType == 'inq' || condType == 'nin') && val.length == 0) {
390 cs.push(condType == 'inq' ? 0: 1);
391 return true;
392 }
393 switch (condType) {
394 case 'gt':
395 sqlCond += ' > ';
396 break;
397 case 'gte':
398 sqlCond += ' >= ';
399 break;
400 case 'lt':
401 sqlCond += ' < ';
402 break;
403 case 'lte':
404 sqlCond += ' <= ';
405 break;
406 case 'between':
407 sqlCond += ' BETWEEN ';
408 break;
409 case 'inq':
410 sqlCond += ' IN ';
411 break;
412 case 'nin':
413 sqlCond += ' NOT IN ';
414 break;
415 case 'neq':
416 sqlCond += ' != ';
417 break;
418 case 'like':
419 sqlCond += ' LIKE ';
420 break;
421 }
422 sqlCond += (condType == 'inq' || condType == 'nin') ? '(' + val + ')': val;
423 cs.push(sqlCond);
424 } else {
425 cs.push(keyEscaped + ' = ' + val);
426 }
427 });
428 if (cs.length === 0) {
429 return '';
430 }
431 return cs.join(' AND ');
432}
433
434MySQL.prototype.count = function count(model, callback, where) {
435 var self = this;
436 var query = 'SELECT count(*) as cnt FROM ' +
437 this.tableEscaped(model);
438 if(where){
439 query += ' where ' + this._buildWhere(model, where);
440 }
441 this.queryOne(query, function (err, res) {
442 if (err) return callback(err);
443 callback(err, res && res.cnt);
444 });
445};
446
447MySQL.prototype.autoupdate = function (cb) {
448 var self = this;
449 var wait = 0;
450 Object.keys(this._models).forEach(function (model) {
451 wait += 1;
452 self.query('SHOW FIELDS FROM ' + self.tableEscaped(model), function (err, fields) {
453 if (err) {
454 if(err.code != 'ER_NO_SUCH_TABLE') return done(err);
455 return self.createTable(model, done);
456 }
457 self.query('SHOW INDEXES FROM ' + self.tableEscaped(model), function (err, indexes) {
458 if (!err && fields.length) {
459 self.alterTable(model, fields, indexes, done);
460 } else {
461 self.createTable(model, done);
462 }
463 }, true);
464 }, true);
465 });
466
467 var hadError;
468 function done(err) {
469 if (err) {
470 hadError = err;
471 console.log(err);
472 }
473 if (--wait === 0 && cb) {
474 cb(hadError);
475 }
476 }
477};
478
479MySQL.prototype.isActual = function (cb) {
480 var ok = false;
481 var self = this;
482 var wait = 0;
483 Object.keys(this._models).forEach(function (model) {
484 wait += 1;
485 self.query('SHOW FIELDS FROM ' + self.tableEscaped(model), function (err, fields) {
486 if(err) {
487 return done(err);
488 }
489 self.query('SHOW INDEXES FROM ' + self.tableEscaped(model), function (err, indexes) {
490 if(err) {
491 return done(err);
492 }
493 self.alterTable(model, fields, indexes, done, true);
494 }, true);
495 }, true);
496 });
497
498 var hadError;
499 function done(err, needAlter) {
500 if (err) {
501 hadError = err;
502 console.log(err);
503 }
504 ok = ok || needAlter;
505 if (--wait === 0 && cb) {
506 cb(hadError, !ok);
507 }
508 }
509};
510
511MySQL.prototype.alterTable = function (model, actualFields, actualIndexes, done, checkOnly) {
512 var self = this;
513 var m = this._models[model];
514 var propNames = Object.keys(m.properties).filter(function (name) {
515 return !!m.properties[name];
516 });
517 var indexNames = m.settings.indexes ? Object.keys(m.settings.indexes).filter(function (name) {
518 return !!m.settings.indexes[name];
519 }): [];
520 var sql = [];
521 var ai = {};
522
523 if (actualIndexes) {
524 actualIndexes.forEach(function (i) {
525 var name = i.Key_name;
526 if (!ai[name]) {
527 ai[name] = {
528 info : i,
529 columns : []
530 };
531 }
532 ai[name].columns[i.Seq_in_index - 1] = i.Column_name;
533 });
534 }
535 var aiNames = Object.keys(ai);
536
537 // change/add new fields
538 propNames.forEach(function (propName) {
539 if (propName === 'id') return;
540 var found;
541 if (actualFields) {
542 actualFields.forEach(function (f) {
543 if (f.Field === propName) {
544 found = f;
545 }
546 });
547 }
548
549 if (found) {
550 actualize(propName, found);
551 } else {
552 sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName));
553 }
554 });
555
556 // drop columns
557 if (actualFields) {
558 actualFields.forEach(function (f) {
559 var notFound = !~propNames.indexOf(f.Field);
560 if (f.Field === 'id') return;
561 if (notFound || !m.properties[f.Field]) {
562 sql.push('DROP COLUMN `' + f.Field + '`');
563 }
564 });
565 }
566
567 // remove indexes
568 aiNames.forEach(function (indexName) {
569 if (indexName === 'id' || indexName === 'PRIMARY') return;
570 if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && (!m.properties[indexName].index || m.properties[indexName].type instanceof Array || m.properties[indexName].type.name === 'JSON')) {
571 sql.push('DROP INDEX `' + indexName + '`');
572 } else {
573 // first: check single (only type and kind)
574 if (m.properties[indexName] && !m.properties[indexName].index) {
575 // TODO
576 return;
577 }
578 // second: check multiple indexes
579 var orderMatched = true;
580 if (indexNames.indexOf(indexName) !== -1) {
581 if (m.settings.indexes[indexName].keys) {
582 m.settings.indexes[indexName].columns = m.settings.indexes[indexName].keys.join(',');
583 }
584 m.settings.indexes[indexName].columns.split(/,\s*/).forEach(function (columnName, i) {
585 if (ai[indexName].columns[i] !== columnName) orderMatched = false;
586 });
587 }
588 if (!orderMatched) {
589 sql.push('DROP INDEX `' + indexName + '`');
590 delete ai[indexName];
591 }
592 }
593 });
594
595 // add single-column indexes
596 propNames.forEach(function (propName) {
597 var prop = m.properties[propName];
598 var i = prop.index;
599 if (!i || prop.type && (prop.type instanceof Array || prop.type.name === 'JSON')) {
600 return;
601 }
602 var found = ai[propName] && ai[propName].info;
603 if (!found) {
604 var type = '';
605 var kind = '';
606 if (i.type) {
607 type = 'USING ' + i.type;
608 }
609 if (i.kind) {
610 // kind = i.kind;
611 }
612 if (kind && type) {
613 sql.push('ADD ' + kind + ' INDEX `' + propName + '` (`' + propName + '`) ' + type);
614 } else {
615 sql.push('ADD ' + kind + ' INDEX `' + propName + '` ' + type + ' (`' + propName + '`) ');
616 }
617 }
618 });
619
620 // add multi-column indexes
621 indexNames.forEach(function (indexName) {
622 var i = m.settings.indexes[indexName];
623 var found = ai[indexName] && ai[indexName].info;
624 if (!found) {
625 var type = '';
626 var kind = '';
627 if (i.type) {
628 type = 'USING ' + i.type;
629 }
630 if (i.kind) {
631 kind = i.kind;
632 }
633 if (i.keys && i.keys.length) {
634 i.columns = '`' + i.keys.join('`, `') + '`';
635 }
636 if (kind && type) {
637 sql.push('ADD ' + kind + ' INDEX `' + indexName + '` (' + i.columns + ') ' + type);
638 } else {
639 sql.push('ADD ' + kind + ' INDEX ' + type + ' `' + indexName + '` (' + i.columns + ')');
640 }
641 }
642 });
643
644 if (sql.length) {
645 var query = 'ALTER TABLE ' + self.tableEscaped(model) + ' ' + sql.join(',\n');
646 if (checkOnly) {
647 done(null, true, {
648 statements : sql, query : query
649 });
650 } else {
651 this.query(query, done);
652 }
653 } else {
654 done();
655 }
656
657 function actualize(propName, oldSettings) {
658 var newSettings = m.properties[propName];
659 if (newSettings && changed(newSettings, oldSettings)) {
660 sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + self.propertySettingsSQL(model, propName));
661 }
662 }
663
664 function changed(newSettings, oldSettings) {
665 if (oldSettings.Null === 'YES') { // Used to allow null and does not now.
666 if (newSettings.allowNull === false) return true;
667 if (newSettings.null === false) return true;
668 }
669 if (oldSettings.Null === 'NO') { // Did not allow null and now does.
670 if (newSettings.allowNull === true) return true;
671 if (newSettings.null === true) return true;
672 if (newSettings.null === undefined && newSettings.allowNull === undefined) return true;
673 }
674
675 if (oldSettings.Type.toUpperCase() !== datatype(newSettings).toUpperCase()) return true;
676 return false;
677 }
678};
679
680MySQL.prototype.propertiesSQL = function (model) {
681 var self = this;
682 var sql;
683 if (this.schema.settings.slave) {
684 sql = ['`id` VARCHAR(100) NOT NULL PRIMARY KEY'];
685 } else {
686 sql = ['`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'];
687 }
688 Object.keys(this._models[model].properties).forEach(function (prop) {
689 if (prop === 'id') return;
690 sql.push('`' + prop + '` ' + self.propertySettingsSQL(model, prop));
691 });
692 // Declared in model index property indexes.
693 Object.keys(this._models[model].properties).forEach(function (prop) {
694 var p = self._models[model].properties[prop];
695 var i = p.index;
696 if (i && !(p.type instanceof Array)) {
697 sql.push(self.singleIndexSettingsSQL(model, prop));
698 }
699 });
700 // Settings might not have an indexes property.
701 var dxs = this._models[model].settings.indexes;
702 if (dxs) {
703 Object.keys(this._models[model].settings.indexes).forEach(function (prop) {
704 sql.push(self.indexSettingsSQL(model, prop));
705 });
706 }
707 return sql.join(',\n ');
708};
709
710MySQL.prototype.singleIndexSettingsSQL = function (model, prop) {
711 // Recycled from alterTable single indexes above, more or less.
712 var i = this._models[model].properties[prop].index;
713 var type = '';
714 var kind = '';
715 if (i.type) {
716 type = 'USING ' + i.type;
717 }
718 if (i.kind) {
719 kind = i.kind;
720 }
721 if (kind && type) {
722 return (kind + ' INDEX `' + prop + '` (`' + prop + '`) ' + type);
723 } else {
724 return (kind + ' INDEX `' + prop + '` ' + type + ' (`' + prop + '`) ');
725 }
726};
727
728MySQL.prototype.indexSettingsSQL = function (model, prop) {
729 // Recycled from alterTable multi-column indexes above, more or less.
730 var i = this._models[model].settings.indexes[prop];
731 var p = this._models[model].properties[prop];
732 var type = '';
733 var kind = '';
734 if (i.type) {
735 type = 'USING ' + i.type;
736 }
737 if (i.kind) {
738 kind = i.kind;
739 }
740 if (i.keys && i.keys.length) {
741 i.columns = '`' + i.keys.join('`, `') + '`';
742 }
743 if (kind && type) {
744 return (kind + ' INDEX `' + prop + '` (' + i.columns + ') ' + type);
745 } else {
746 return (kind + ' INDEX ' + type + ' `' + prop + '` (' + i.columns + ')');
747 }
748};
749
750MySQL.prototype.propertySettingsSQL = function (model, prop) {
751 var p = this._models[model].properties[prop];
752 var line = datatype(p) + ' ' +
753 (p.allowNull === false || p['null'] === false ? 'NOT NULL': 'NULL');
754 return line;
755};
756
757function datatype(p) {
758 var dt = '';
759 if (p.type instanceof Array) {
760 return 'Text';
761 }
762 switch (p.type.name) {
763 default :
764 case 'String':
765 dt = columnType(p, 'VARCHAR');
766 dt = stringOptionsByType(p, dt);
767 break;
768 case 'JSON':
769 case 'Text':
770 dt = columnType(p, 'TEXT');
771 dt = stringOptionsByType(p, dt);
772 break;
773 case 'Number':
774 dt = columnType(p, 'INT');
775 dt = numericOptionsByType(p, dt);
776 break;
777 case 'Date':
778 dt = columnType(p, 'DATETIME'); // Currently doesn't need options.
779 break;
780 case 'Boolean':
781 dt = 'TINYINT(1)';
782 break;
783 case 'Point':
784 dt = 'POINT';
785 break;
786 case 'Enum':
787 dt = 'ENUM(' + p.type._string + ')';
788 dt = stringOptions(p, dt); // Enum columns can have charset/collation.
789 break;
790 }
791 return dt;
792}
793
794function columnType(p, defaultType) {
795 var dt = defaultType;
796 if (p.dataType) {
797 dt = String(p.dataType);
798 }
799 return dt;
800}
801
802function stringOptionsByType(p, dt) {
803 switch (dt.toLowerCase()) {
804 default :
805 case 'varchar':
806 case 'char':
807 dt += '(' + (p.limit || p.length || 255) + ')';
808 break;
809
810 case 'text':
811 case 'tinytext':
812 case 'mediumtext':
813 case 'longtext':
814
815 break;
816 }
817 dt = stringOptions(p, dt);
818 return dt;
819}
820
821function stringOptions(p, dt) {
822 if (p.charset) {
823 dt += " CHARACTER SET " + p.charset;
824 }
825 if (p.collation) {
826 dt += " COLLATE " + p.collation;
827 }
828 return dt;
829}
830
831function numericOptionsByType(p, dt) {
832 switch (dt.toLowerCase()) {
833 default :
834 case 'tinyint':
835 case 'smallint':
836 case 'mediumint':
837 case 'int':
838 case 'integer':
839 case 'bigint':
840 dt = integerOptions(p, dt);
841 break;
842
843 case 'decimal':
844 case 'numeric':
845 dt = fixedPointOptions(p, dt);
846 break;
847
848 case 'float':
849 case 'double':
850 dt = floatingPointOptions(p, dt);
851 break;
852 }
853 dt = unsigned(p, dt);
854 return dt;
855}
856
857function floatingPointOptions(p, dt) {
858 var precision = 16;
859 var scale = 8;
860 if (p.precision) {
861 precision = Number(p.precision);
862 }
863 if (p.scale) {
864 scale = Number(p.scale);
865 }
866 if (p.precision && p.scale) {
867 dt += '(' + precision + ',' + scale + ')';
868 } else if (p.precision) {
869 dt += '(' + precision + ')';
870 }
871 return dt;
872}
873
874/* @TODO: Change fixed point to use an arbitrary precision arithmetic library. */
875/* Currently fixed point will lose precision because it's turned to non-fixed in */
876/* JS. Also, defaulting column to (9,2) and not allowing non-specified 'DECIMAL' */
877/* declaration which would default to DECIMAL(10,0). Instead defaulting to (9,2). */
878
879function fixedPointOptions(p, dt) {
880 var precision = 9;
881 var scale = 2;
882 if (p.precision) {
883 precision = Number(p.precision);
884 }
885 if (p.scale) {
886 scale = Number(p.scale);
887 }
888 dt += '(' + precision + ',' + scale + ')';
889 return dt;
890}
891
892function integerOptions(p, dt) {
893 var tmp = 0;
894 if (p.display || p.limit) {
895 tmp = Number(p.display || p.limit);
896 }
897 if (tmp > 0) {
898 dt += '(' + tmp + ')';
899 } else if (p.unsigned) {
900 switch (dt.toLowerCase()) {
901 default :
902 case 'int':
903 dt += '(10)';
904 break;
905 case 'mediumint':
906 dt += '(8)';
907 break;
908 case 'smallint':
909 dt += '(5)';
910 break;
911 case 'tinyint':
912 dt += '(3)';
913 break;
914 case 'bigint':
915 dt += '(20)';
916 break;
917 }
918 } else {
919 switch (dt.toLowerCase()) {
920 default :
921 case 'int':
922 dt += '(11)';
923 break;
924 case 'mediumint':
925 dt += '(9)';
926 break;
927 case 'smallint':
928 dt += '(6)';
929 break;
930 case 'tinyint':
931 dt += '(4)';
932 break;
933 case 'bigint':
934 dt += '(20)';
935 break;
936 }
937 }
938 return dt;
939}
940
941function unsigned(p, dt) {
942 if (p.unsigned) {
943 dt += ' UNSIGNED';
944 }
945 return dt;
946}
947
948
949function buildWhrSet() {
950 this.buildWhrSet = function (buildtype, conds, self, props) {
951
952 var cs = [];
953 Object.keys(conds).forEach(function (key) {
954 var keyEscaped = '`' + key.replace(/\./g, '`.`') + '`'
955 var val = self.toDatabase(props[key], conds[key]);
956 if (conds[key] === null || conds[key] === undefined) {
957 cs.push(keyEscaped + ' IS NULL');
958 } else if (conds[key] && conds[key].constructor.name === 'Object') {
959 var condType = Object.keys(conds[key])[0];
960 var sqlCond = keyEscaped;
961 if ((condType == 'inq' || condType == 'nin') && val.length == 0) {
962 cs.push(condType == 'inq' ? 0: 1);
963 return true;
964 }
965 switch (condType) {
966 case 'gt':
967 sqlCond += ' > ';
968 break;
969 case 'gte':
970 sqlCond += ' >= ';
971 break;
972 case 'lt':
973 sqlCond += ' < ';
974 break;
975 case 'lte':
976 sqlCond += ' <= ';
977 break;
978 case 'between':
979 sqlCond += ' BETWEEN ';
980 break;
981 case 'inq':
982 sqlCond += ' IN ';
983 break;
984 case 'nin':
985 sqlCond += ' NOT IN ';
986 break;
987 case 'neq':
988 sqlCond += ' != ';
989 break;
990 }
991 sqlCond += (condType == 'inq' || condType == 'nin') ? '(' + val + ')': val;
992 cs.push(sqlCond);
993 } else {
994 cs.push(keyEscaped + ' = ' + val);
995 }
996 });
997
998
999 if (buildtype == 'Where') {
1000 return cs.length ? ' WHERE ' + cs.join(' AND '): '';
1001 } else {
1002 return cs.length ? ' SET ' + cs.join(' , '): '';
1003 }
1004 };
1005}
1006
1007MySQL.prototype.update = function all(model, filter, callback) {
1008
1009 if (!filter.where || !filter.update) {
1010 return callback('Where or Update fields are missing');
1011 }
1012
1013 var props = this._models[model].properties;
1014
1015 var buidquery = new buildWhrSet();
1016
1017 var sql = 'UPDATE ' + this.tableEscaped(model) + buidquery.buildWhrSet('SET', filter.update, this, props) + buidquery.buildWhrSet('Where', filter.where, this, props);
1018
1019 this.query(sql, function (err, info) {
1020 callback(err, info);
1021 });
1022};