Support datasource/connector configuration using URL string

This commit is contained in:
Raymond Feng 2013-10-25 16:18:02 -07:00
parent f164ed6919
commit f3011216b5
5 changed files with 156 additions and 18 deletions

View File

@ -4,6 +4,7 @@
var ModelBuilder = require('./model-builder.js').ModelBuilder; var ModelBuilder = require('./model-builder.js').ModelBuilder;
var ModelDefinition = require('./model-definition.js'); var ModelDefinition = require('./model-definition.js');
var jutil = require('./jutil'); var jutil = require('./jutil');
var utils = require('./utils');
var ModelBaseClass = require('./model.js'); var ModelBaseClass = require('./model.js');
var DataAccessObject = require('./dao.js'); var DataAccessObject = require('./dao.js');
var List = require('./list.js'); var List = require('./list.js');
@ -31,7 +32,7 @@ var slice = Array.prototype.slice;
* All classes in single dataSource shares same connector type and * All classes in single dataSource shares same connector type and
* one database connection * one database connection
* *
* @param {String} name - type of dataSource connector (mysql, mongoose, sequelize, redis) * @param {String} name - type of dataSource connector (mysql, mongoose, oracle, redis)
* @param {Object} settings - any database-specific settings which we need to * @param {Object} settings - any database-specific settings which we need to
* establish connection (of course it depends on specific connector) * establish connection (of course it depends on specific connector)
* *
@ -55,7 +56,24 @@ function DataSource(name, settings) {
if (!(this instanceof DataSource)) { if (!(this instanceof DataSource)) {
return new DataSource(name, settings); return new DataSource(name, settings);
} }
ModelBuilder.call(this, arguments);
// Check if the settings object is passed as the first argument
if (typeof name === 'object' && settings === undefined) {
settings = name;
name = undefined;
}
// Check if the first argument is a URL
if(typeof name === 'string' && name.indexOf('://') !== -1 ) {
name = utils.parseSettings(name);
}
// Check if the settings is in the form of URL string
if(typeof settings === 'string' && settings.indexOf('://') !== -1 ) {
settings = utils.parseSettings(settings);
}
ModelBuilder.call(this, name, settings);
// operation metadata // operation metadata
// Initialize it before calling setup as the connector might register operations // Initialize it before calling setup as the connector might register operations
@ -70,7 +88,7 @@ function DataSource(name, settings) {
// DataAccessObject - connector defined or supply the default // DataAccessObject - connector defined or supply the default
this.DataAccessObject = (connector && connector.DataAccessObject) ? connector.DataAccessObject : this.constructor.DataAccessObject; this.DataAccessObject = (connector && connector.DataAccessObject) ? connector.DataAccessObject : this.constructor.DataAccessObject;
this.DataAccessObject.call(this, arguments); this.DataAccessObject.apply(this, arguments);
// define DataAccessObject methods // define DataAccessObject methods
@ -176,7 +194,6 @@ DataSource.prototype.setup = function(name, settings) {
} }
// just save everything we get // just save everything we get
this.name = name;
this.settings = settings || {}; this.settings = settings || {};
// Check the debug env settings // Check the debug env settings
@ -190,14 +207,16 @@ DataSource.prototype.setup = function(name, settings) {
this.connected = false; this.connected = false;
this.connecting = false; this.connecting = false;
if(typeof connector === 'string') {
name = connector;
connector = undefined;
}
name = name || (connector && connector.name);
this.name = name;
if (name && !connector) { if (name && !connector) {
// and initialize dataSource using connector // The connector has not been resolved
// this is only one initialization entry point of connector if (name.match(/^\//)) {
// this module should define `connector` member of `this` (dataSource)
if (typeof name === 'object') {
connector = name;
this.name = connector.name;
} else if (name.match(/^\//)) {
// try absolute path // try absolute path
connector = require(name); connector = require(name);
} else if (existsSync(__dirname + '/connectors/' + name + '.js')) { } else if (existsSync(__dirname + '/connectors/' + name + '.js')) {
@ -216,10 +235,6 @@ DataSource.prototype.setup = function(name, settings) {
} }
} }
if('string' === typeof connector) {
connector = require(connector);
}
if (connector) { if (connector) {
var postInit = function postInit(err, result) { var postInit = function postInit(err, result) {

View File

@ -2,6 +2,7 @@ exports.safeRequire = safeRequire;
exports.fieldsToArray = fieldsToArray; exports.fieldsToArray = fieldsToArray;
exports.selectFields = selectFields; exports.selectFields = selectFields;
exports.removeUndefined = removeUndefined; exports.removeUndefined = removeUndefined;
exports.parseSettings = parseSettings;
var traverse = require('traverse'); var traverse = require('traverse');
@ -87,3 +88,33 @@ function removeUndefined(query) {
return x; return x;
}); });
} }
var url = require('url');
var qs = require('qs');
/**
* Parse a URL into a settings object
* @param {String} urlStr The URL for connector settings
* @returns {Object} The settings object
*/
function parseSettings(urlStr) {
if(!urlStr) {
return {};
}
var uri = url.parse(urlStr, false);
var settings = {};
settings.connector = uri.protocol && uri.protocol.split(':')[0]; // Remove the trailing :
settings.host = settings.hostname = uri.hostname;
settings.port = uri.port && Number(uri.port); // port is a string
settings.user = settings.username = uri.auth && uri.auth.split(':')[0]; // <username>:<password>
settings.password = uri.auth && uri.auth.split(':')[1];
settings.database = uri.pathname && uri.pathname.split('/')[1]; // remove the leading /
settings.url = urlStr;
if(uri.query) {
var params = qs.parse(uri.query);
for(var p in params) {
settings[p] = params[p];
}
}
return settings;
}

View File

@ -2,7 +2,13 @@
"name": "loopback-datasource-juggler", "name": "loopback-datasource-juggler",
"version": "1.0.0", "version": "1.0.0",
"description": "LoopBack DataSoure Juggler", "description": "LoopBack DataSoure Juggler",
"keywords": [ "StrongLoop", "LoopBack", "DataSource", "Juggler", "ORM" ], "keywords": [
"StrongLoop",
"LoopBack",
"DataSource",
"Juggler",
"ORM"
],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/strongloop/loopback-datasource-juggler" "url": "https://github.com/strongloop/loopback-datasource-juggler"
@ -26,7 +32,8 @@
"dependencies": { "dependencies": {
"async": "~0.2.9", "async": "~0.2.9",
"inflection": "~1.2.6", "inflection": "~1.2.6",
"traverse": "~0.6.5" "traverse": "~0.6.5",
"qs": "~0.6.5"
}, },
"license": "MIT" "license": "MIT"
} }

View File

@ -466,3 +466,25 @@ describe('Load models from json', function () {
}); });
}); });
describe('DataSource constructor', function(){
it('Takes url as the settings', function() {
var ds = new DataSource('memory://localhost/mydb?x=1');
assert.equal(ds.connector.name, 'memory');
});
it('Takes connector name', function() {
var ds = new DataSource('memory');
assert.equal(ds.connector.name, 'memory');
});
it('Takes settings object', function() {
var ds = new DataSource({connector: 'memory'});
assert.equal(ds.connector.name, 'memory');
});
it('Takes settings object and name', function() {
var ds = new DataSource('x', {connector: 'memory'});
assert.equal(ds.connector.name, 'memory');
});
});

View File

@ -48,3 +48,66 @@ describe('util.removeUndefined', function(){
}); });
}); });
describe('util.parseSettings', function(){
it('Parse a full url into a settings object', function() {
var url = 'mongodb://x:y@localhost:27017/mydb?w=2';
var settings = utils.parseSettings(url);
should.equal(settings.hostname, 'localhost');
should.equal(settings.port, 27017);
should.equal(settings.host, 'localhost');
should.equal(settings.user, 'x');
should.equal(settings.password, 'y');
should.equal(settings.database, 'mydb');
should.equal(settings.connector, 'mongodb');
should.equal(settings.w, '2');
should.equal(settings.url, 'mongodb://x:y@localhost:27017/mydb?w=2');
});
it('Parse a url without auth into a settings object', function() {
var url = 'mongodb://localhost:27017/mydb/abc?w=2';
var settings = utils.parseSettings(url);
should.equal(settings.hostname, 'localhost');
should.equal(settings.port, 27017);
should.equal(settings.host, 'localhost');
should.equal(settings.user, undefined);
should.equal(settings.password, undefined);
should.equal(settings.database, 'mydb');
should.equal(settings.connector, 'mongodb');
should.equal(settings.w, '2');
should.equal(settings.url, 'mongodb://localhost:27017/mydb/abc?w=2');
});
it('Parse a url with complex query into a settings object', function() {
var url = 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB';
var settings = utils.parseSettings(url);
should.equal(settings.hostname, '127.0.0.1');
should.equal(settings.port, 3306);
should.equal(settings.host, '127.0.0.1');
should.equal(settings.user, undefined);
should.equal(settings.password, undefined);
should.equal(settings.database, 'mydb');
should.equal(settings.connector, 'mysql');
should.equal(settings.x.a, '1');
should.equal(settings.x.b, '2');
should.equal(settings.engine, 'InnoDB');
should.equal(settings.url, 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB');
});
it('Parse a url without auth into a settings object', function() {
var url = 'memory://?x=1';
var settings = utils.parseSettings(url);
should.equal(settings.hostname, '');
should.equal(settings.user, undefined);
should.equal(settings.password, undefined);
should.equal(settings.database, undefined);
should.equal(settings.connector, 'memory');
should.equal(settings.x, '1');
should.equal(settings.url, 'memory://?x=1');
});
});