Use connection pool for MySQL

This commit is contained in:
Raymond Feng 2013-11-26 17:40:31 -08:00
parent 348ec4eafe
commit 999825abea
2 changed files with 191 additions and 134 deletions

View File

@ -22,7 +22,7 @@ exports.initialize = function initializeDataSource(dataSource, callback) {
var s = dataSource.settings; var s = dataSource.settings;
if (s.collation) { if (s.collation) {
s.charset = s.collation.substr(0,s.collation.indexOf('_')); // Charset should be first 'chunk' of collation. s.charset = s.collation.substr(0, s.collation.indexOf('_')); // Charset should be first 'chunk' of collation.
} else { } else {
s.collation = 'utf8_general_ci'; s.collation = 'utf8_general_ci';
s.charset = 'utf8'; s.charset = 'utf8';
@ -31,12 +31,13 @@ exports.initialize = function initializeDataSource(dataSource, callback) {
s.supportBigNumbers = (s.supportBigNumbers || false); s.supportBigNumbers = (s.supportBigNumbers || false);
s.timezone = (s.timezone || 'local'); s.timezone = (s.timezone || 'local');
dataSource.client = mysql.createConnection({ dataSource.client = mysql.createPool({
host: s.host || s.hostname || 'localhost', host: s.host || s.hostname || 'localhost',
port: s.port || 3306, port: s.port || 3306,
user: s.username || s.user, user: s.username || s.user,
password: s.password, password: s.password,
timezone: s.timezone, timezone: s.timezone,
// database: s.database,
debug: s.debug, debug: s.debug,
socketPath: s.socketPath, socketPath: s.socketPath,
charset: s.collation.toUpperCase(), // Correct by docs despite seeming odd. charset: s.collation.toUpperCase(), // Correct by docs despite seeming odd.
@ -47,37 +48,57 @@ exports.initialize = function initializeDataSource(dataSource, callback) {
dataSource.emit('error', err); dataSource.emit('error', err);
}); });
if(s.debug) { if (s.debug) {
console.log('Settings: ', s); console.log('Settings: ', s);
} }
dataSource.connector = new MySQL(dataSource.client, s); dataSource.connector = new MySQL(dataSource.client, s);
dataSource.connector.dataSource = dataSource; dataSource.connector.dataSource = dataSource;
dataSource.client.query('USE `' + s.database + '`', function (err) { callback && callback();
/*
dataSource.client.getConnection(function (err, connection) {
if (err) {
callback && callback(err);
return;
}
connection.query('USE `' + s.database + '`', function (err) {
if (err) { if (err) {
if (err.message.match(/(^|: )unknown database/i)) { if (err.message.match(/(^|: )unknown database/i)) {
var dbName = s.database; var dbName = s.database;
var charset = s.charset; var charset = s.charset;
var collation = s.collation; var collation = s.collation;
var q = 'CREATE DATABASE ' + dbName + ' CHARACTER SET ' + charset + ' COLLATE ' + collation; var q = 'CREATE DATABASE ' + dbName + ' CHARACTER SET ' + charset + ' COLLATE ' + collation;
dataSource.client.query(q, function (error) { connection.query(q, function (error) {
if (!error) { if (!error) {
dataSource.client.query('USE ' + s.database, callback); connection.query('USE ' + s.database, function (err, result) {
connection.release();
callback && callback(err, result);
});
} else { } else {
throw error; connection.release();
callback && callback(err);
} }
}); });
} else throw err; } else {
} else callback(); connection.release();
callback && callback(err);
}
} else {
connection.release();
callback && callback();
}
}); });
});
*/
// MySQL specific column types // MySQL specific column types
juggler.ModelBuilder.registerType(function Point() {}); juggler.ModelBuilder.registerType(function Point() {
});
dataSource.EnumFactory = EnumFactory; // factory for Enums. Note that currently Enums can not be registered. dataSource.EnumFactory = EnumFactory; // factory for Enums. Note that currently Enums can not be registered.
}; };
exports.MySQL = MySQL; exports.MySQL = MySQL;
@ -103,6 +124,7 @@ require('util').inherits(MySQL, juggler.BaseSQL);
* @param {Function} [callback] The callback after the SQL statement is executed * @param {Function} [callback] The callback after the SQL statement is executed
*/ */
MySQL.prototype.query = function (sql, callback) { MySQL.prototype.query = function (sql, callback) {
var self = this;
if (!this.dataSource.connected) { if (!this.dataSource.connected) {
return this.dataSource.on('connected', function () { return this.dataSource.on('connected', function () {
this.query(sql, callback); this.query(sql, callback);
@ -111,36 +133,59 @@ MySQL.prototype.query = function (sql, callback) {
var client = this.client; var client = this.client;
var time = Date.now(); var time = Date.now();
var debug = this.settings.debug; var debug = this.settings.debug;
var db = this.settings.database;
var log = this.log; var log = this.log;
if (typeof callback !== 'function') throw new Error('callback should be a function'); if (typeof callback !== 'function') throw new Error('callback should be a function');
if(debug) { if (debug) {
console.log('SQL:' , sql); console.log('SQL:', sql);
}
this.client.query(sql, function (err, data) {
if(err) {
if(debug) {
console.error('Error:', err);
} }
client.getConnection(function (err, connection) {
if (err) {
callback && callback(err);
return;
} }
connection.query('USE `' + db + '`', function (err) {
if (err) {
if (err && err.message.match(/(^|: )unknown database/i)) { if (err && err.message.match(/(^|: )unknown database/i)) {
var dbName = err.message.match(/(^|: )unknown database '(.*?)'/i)[1]; var charset = self.settings.charset;
client.query('CREATE DATABASE ' + dbName, function (error) { var collation = self.settings.collation;
var q = 'CREATE DATABASE ' + db + ' CHARACTER SET ' + charset + ' COLLATE ' + collation;
connection.query(q, function (error) {
if (!error) { if (!error) {
client.query(sql, callback); connection.query('USE `' + db + '`', function (err) {
connection.query(sql, function (err, result) {
connection.release();
callback && callback(err, result);
});
});
} else { } else {
callback(err); connection.release();
callback && callback(err);
} }
}); });
return; return;
} else {
connection.release();
callback && callback(err);
return;
} }
if(debug) { }
console.log('Data:' , data); connection.query(sql, function (err, data) {
if (debug) {
if(err) {
console.error('Error:', err);
}
console.log('Data:', data);
} }
if (log) log(sql, time); if (log) log(sql, time);
callback(err, data); connection.release();
callback && callback(err, data);
});
});
}); });
}; };
/** /**
* Create the data model in MySQL * Create the data model in MySQL
* *
@ -267,7 +312,7 @@ MySQL.prototype.toDatabase = function (prop, val) {
} }
if (prop.type.name == "Boolean") return val ? 1 : 0; if (prop.type.name == "Boolean") return val ? 1 : 0;
if (prop.type.name === 'GeoPoint') { if (prop.type.name === 'GeoPoint') {
return val ? 'Point(' + val.lat + ',' + val.lng +')' : 'NULL'; return val ? 'Point(' + val.lat + ',' + val.lng + ')' : 'NULL';
} }
if (typeof prop.type === 'function') return this.client.escape(prop.type(val)); if (typeof prop.type === 'function') return this.client.escape(prop.type(val));
return this.client.escape(val.toString()); return this.client.escape(val.toString());
@ -285,14 +330,14 @@ MySQL.prototype.fromDatabase = function (model, data) {
} }
var props = this._models[model].properties; var props = this._models[model].properties;
var json = {}; var json = {};
for(var p in props) { for (var p in props) {
var key = this.column(model, p); var key = this.column(model, p);
var val = data[key]; var val = data[key];
if (typeof val === 'undefined' || val === null) { if (typeof val === 'undefined' || val === null) {
continue; continue;
} }
if (props[p]) { if (props[p]) {
switch(props[p].type.name) { switch (props[p].type.name) {
case 'Date': case 'Date':
val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
break; break;
@ -317,9 +362,9 @@ MySQL.prototype.escapeName = function (name) {
return '`' + name.replace(/\./g, '`.`') + '`'; return '`' + name.replace(/\./g, '`.`') + '`';
}; };
MySQL.prototype.getColumns = function(model, props){ MySQL.prototype.getColumns = function (model, props) {
var cols = this._models[model].properties; var cols = this._models[model].properties;
if(!cols) { if (!cols) {
return '*'; return '*';
} }
var self = this; var self = this;
@ -347,7 +392,7 @@ MySQL.prototype.getColumns = function(model, props){
}); });
} }
} }
var names = keys.map(function(c) { var names = keys.map(function (c) {
return self.columnEscaped(model, c); return self.columnEscaped(model, c);
}); });
return names.join(', '); return names.join(', ');
@ -411,7 +456,7 @@ function buildOrderBy(self, model, order) {
if (typeof order === 'string') { if (typeof order === 'string') {
order = [order]; order = [order];
} }
return 'ORDER BY ' + order.map(function(o) { return 'ORDER BY ' + order.map(function (o) {
var t = o.split(/[\s,]+/); var t = o.split(/[\s,]+/);
if (t.length === 1) { if (t.length === 1) {
return self.columnEscaped(model, o); return self.columnEscaped(model, o);
@ -435,14 +480,14 @@ MySQL.prototype.all = function all(model, filter, callback) {
var self = this; var self = this;
// Order by id if no order is specified // Order by id if no order is specified
filter = filter || {}; filter = filter || {};
if(!filter.order) { if (!filter.order) {
var idNames = this.idNames(model); var idNames = this.idNames(model);
if(idNames && idNames.length) { if (idNames && idNames.length) {
filter.order = idNames.join(' '); filter.order = idNames.join(' ');
} }
} }
var sql = 'SELECT '+ this.getColumns(model, filter.fields) + ' FROM ' + this.tableEscaped(model); var sql = 'SELECT ' + this.getColumns(model, filter.fields) + ' FROM ' + this.tableEscaped(model);
if (filter) { if (filter) {
@ -488,7 +533,7 @@ MySQL.prototype.all = function all(model, filter, callback) {
* *
*/ */
MySQL.prototype.destroyAll = function destroyAll(model, where, callback) { MySQL.prototype.destroyAll = function destroyAll(model, where, callback) {
if(!callback && 'function' === typeof where) { if (!callback && 'function' === typeof where) {
callback = where; callback = where;
where = undefined; where = undefined;
} }
@ -552,7 +597,7 @@ MySQL.prototype.createTable = function (model, cb) {
var engine = metadata && metadata.engine; var engine = metadata && metadata.engine;
var sql = 'CREATE TABLE ' + this.tableEscaped(model) + var sql = 'CREATE TABLE ' + this.tableEscaped(model) +
' (\n ' + this.propertiesSQL(model) + '\n)'; ' (\n ' + this.propertiesSQL(model) + '\n)';
if(engine) { if (engine) {
sql += 'ENGINE=' + engine + '\n'; sql += 'ENGINE=' + engine + '\n';
} }
this.query(sql, cb); this.query(sql, cb);
@ -645,7 +690,7 @@ MySQL.prototype.alterTable = function (model, actualFields, actualIndexes, done,
// remove indexes // remove indexes
aiNames.forEach(function (indexName) { aiNames.forEach(function (indexName) {
if (indexName === 'PRIMARY'|| (m.properties[indexName] && self.id(model, indexName))) return; if (indexName === 'PRIMARY' || (m.properties[indexName] && self.id(model, indexName))) return;
if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) { if (indexNames.indexOf(indexName) === -1 && !m.properties[indexName] || m.properties[indexName] && !m.properties[indexName].index) {
sql.push('DROP INDEX `' + indexName + '`'); sql.push('DROP INDEX `' + indexName + '`');
} else { } else {
@ -733,13 +778,13 @@ MySQL.prototype.alterTable = function (model, actualFields, actualIndexes, done,
function changed(newSettings, oldSettings) { function changed(newSettings, oldSettings) {
if (oldSettings.Null === 'YES') { // Used to allow null and does not now. if (oldSettings.Null === 'YES') { // Used to allow null and does not now.
if(newSettings.allowNull === false) return true; if (newSettings.allowNull === false) return true;
if(newSettings.null === false) return true; if (newSettings.null === false) return true;
} }
if (oldSettings.Null === 'NO') { // Did not allow null and now does. if (oldSettings.Null === 'NO') { // Did not allow null and now does.
if(newSettings.allowNull === true) return true; if (newSettings.allowNull === true) return true;
if(newSettings.null === true) return true; if (newSettings.null === true) return true;
if(newSettings.null === undefined && newSettings.allowNull === undefined) return true; if (newSettings.null === undefined && newSettings.allowNull === undefined) return true;
} }
if (oldSettings.Type.toUpperCase() !== datatype(newSettings).toUpperCase()) return true; if (oldSettings.Type.toUpperCase() !== datatype(newSettings).toUpperCase()) return true;
@ -787,8 +832,8 @@ MySQL.prototype.propertiesSQL = function (model) {
}); });
// Settings might not have an indexes property. // Settings might not have an indexes property.
var dxs = this._models[model].settings.indexes; var dxs = this._models[model].settings.indexes;
if(dxs){ if (dxs) {
Object.keys(this._models[model].settings.indexes).forEach(function(prop){ Object.keys(this._models[model].settings.indexes).forEach(function (prop) {
sql.push(self.indexSettingsSQL(model, prop)); sql.push(self.indexSettingsSQL(model, prop));
}); });
} }
@ -843,11 +888,11 @@ MySQL.prototype.propertySettingsSQL = function (model, prop) {
MySQL.prototype.columnDataType = function (model, property) { MySQL.prototype.columnDataType = function (model, property) {
var columnMetadata = this.columnMetadata(model, property); var columnMetadata = this.columnMetadata(model, property);
var colType = columnMetadata && columnMetadata.dataType; var colType = columnMetadata && columnMetadata.dataType;
if(colType) { if (colType) {
colType = colType.toUpperCase(); colType = colType.toUpperCase();
} }
var prop = this._models[model].properties[property]; var prop = this._models[model].properties[property];
if(!prop) { if (!prop) {
return null; return null;
} }
var colLength = columnMetadata && columnMetadata.dataLength || prop.length; var colLength = columnMetadata && columnMetadata.dataLength || prop.length;
@ -894,7 +939,7 @@ function datatype(p) {
function columnType(p, defaultType) { function columnType(p, defaultType) {
var dt = defaultType; var dt = defaultType;
if(p.dataType){ if (p.dataType) {
dt = String(p.dataType); dt = String(p.dataType);
} }
return dt; return dt;
@ -919,11 +964,11 @@ function stringOptionsByType(p, dt) {
return dt; return dt;
} }
function stringOptions(p, dt){ function stringOptions(p, dt) {
if(p.charset){ if (p.charset) {
dt += " CHARACTER SET " + p.charset; dt += " CHARACTER SET " + p.charset;
} }
if(p.collation){ if (p.collation) {
dt += " COLLATE " + p.collation; dt += " COLLATE " + p.collation;
} }
return dt; return dt;
@ -958,15 +1003,15 @@ function numericOptionsByType(p, dt) {
function floatingPointOptions(p, dt) { function floatingPointOptions(p, dt) {
var precision = 16; var precision = 16;
var scale = 8; var scale = 8;
if(p.precision){ if (p.precision) {
precision = Number(p.precision); precision = Number(p.precision);
} }
if(p.scale){ if (p.scale) {
scale = Number(p.scale); scale = Number(p.scale);
} }
if (p.precision && p.scale) { if (p.precision && p.scale) {
dt += '(' + precision + ',' + scale + ')'; dt += '(' + precision + ',' + scale + ')';
} else if(p.precision){ } else if (p.precision) {
dt += '(' + precision + ')'; dt += '(' + precision + ')';
} }
return dt; return dt;
@ -979,10 +1024,10 @@ function floatingPointOptions(p, dt) {
function fixedPointOptions(p, dt) { function fixedPointOptions(p, dt) {
var precision = 9; var precision = 9;
var scale = 2; var scale = 2;
if(p.precision){ if (p.precision) {
precision = Number(p.precision); precision = Number(p.precision);
} }
if(p.scale){ if (p.scale) {
scale = Number(p.scale); scale = Number(p.scale);
} }
dt += '(' + precision + ',' + scale + ')'; dt += '(' + precision + ',' + scale + ')';
@ -994,9 +1039,9 @@ function integerOptions(p, dt) {
if (p.display || p.limit) { if (p.display || p.limit) {
tmp = Number(p.display || p.limit); tmp = Number(p.display || p.limit);
} }
if(tmp > 0){ if (tmp > 0) {
dt += '(' + tmp + ')'; dt += '(' + tmp + ')';
} else if(p.unsigned){ } else if (p.unsigned) {
switch (dt.toLowerCase()) { switch (dt.toLowerCase()) {
default: default:
case 'int': case 'int':
@ -1038,12 +1083,24 @@ function integerOptions(p, dt) {
return dt; return dt;
} }
function unsigned(p, dt){ function unsigned(p, dt) {
if (p.unsigned) { if (p.unsigned) {
dt += ' UNSIGNED'; dt += ' UNSIGNED';
} }
return dt; return dt;
} }
/**
* Disconnect from MongoDB
*/
MySQL.prototype.disconnect = function () {
if(this.debug) {
console.log('disconnect');
}
if(this.client) {
this.client.end();
}
};
require('./discovery')(MySQL); require('./discovery')(MySQL);

View File

@ -41,7 +41,7 @@ describe('migrations', function() {
}); });
it('should drop db and disconnect all', function(done) { it('should drop db and disconnect all', function(done) {
db.adapter.query('DROP DATABASE IF EXISTS ' + db.settings.database, function(err) { db.connector.query('DROP DATABASE IF EXISTS ' + db.settings.database, function(err) {
db.client.end(function(){ db.client.end(function(){
done(); done();
}); });
@ -61,10 +61,10 @@ function charsetTest(test_set, test_collo, test_set_str, test_set_collo, done){
DummyModel = db.define('DummyModel', {string: String}); DummyModel = db.define('DummyModel', {string: String});
db.automigrate(function(){ db.automigrate(function(){
var q = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ' + db.client.escape(db.settings.database) + ' LIMIT 1'; var q = 'SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ' + db.client.escape(db.settings.database) + ' LIMIT 1';
db.client.query(q, function(err, r) { db.connector.query(q, function(err, r) {
assert.ok(!err); assert.ok(!err);
assert.ok(r[0].DEFAULT_COLLATION_NAME.match(test_collo)); assert.ok(r[0].DEFAULT_COLLATION_NAME.match(test_collo));
db.client.query('SHOW VARIABLES LIKE "character_set%"', function(err, r){ db.connector.query('SHOW VARIABLES LIKE "character_set%"', function(err, r){
assert.ok(!err); assert.ok(!err);
var hit_all = 0; var hit_all = 0;
for (var result in r) { for (var result in r) {
@ -75,7 +75,7 @@ function charsetTest(test_set, test_collo, test_set_str, test_set_collo, done){
} }
assert.equal(hit_all, 4); assert.equal(hit_all, 4);
}); });
db.client.query('SHOW VARIABLES LIKE "collation%"', function(err, r){ db.connector.query('SHOW VARIABLES LIKE "collation%"', function(err, r){
assert.ok(!err); assert.ok(!err);
var hit_all = 0; var hit_all = 0;
for (var result in r) { for (var result in r) {
@ -101,7 +101,7 @@ function matchResult(result, variable_name, match) {
} }
var query = function (sql, cb) { var query = function (sql, cb) {
odb.adapter.query(sql, cb); odb.connector.query(sql, cb);
}; };