From f3011216b53e2eced95d5e37e46e83fab5c95e31 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 25 Oct 2013 16:18:02 -0700 Subject: [PATCH] Support datasource/connector configuration using URL string --- lib/datasource.js | 47 ++++++++++++++++++++---------- lib/utils.js | 31 ++++++++++++++++++++ package.json | 11 +++++-- test/loopback-dl.test.js | 22 ++++++++++++++ test/util.test.js | 63 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 18 deletions(-) diff --git a/lib/datasource.js b/lib/datasource.js index d59e2d65..b225dabc 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -4,6 +4,7 @@ var ModelBuilder = require('./model-builder.js').ModelBuilder; var ModelDefinition = require('./model-definition.js'); var jutil = require('./jutil'); +var utils = require('./utils'); var ModelBaseClass = require('./model.js'); var DataAccessObject = require('./dao.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 * 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 * establish connection (of course it depends on specific connector) * @@ -55,7 +56,24 @@ function DataSource(name, settings) { if (!(this instanceof DataSource)) { 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 // 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 this.DataAccessObject = (connector && connector.DataAccessObject) ? connector.DataAccessObject : this.constructor.DataAccessObject; - this.DataAccessObject.call(this, arguments); + this.DataAccessObject.apply(this, arguments); // define DataAccessObject methods @@ -164,7 +182,7 @@ DataSource.prototype.setup = function(name, settings) { settings = name; name = undefined; } - + if(typeof settings === 'object') { if(settings.initialize) { connector = settings; @@ -176,7 +194,6 @@ DataSource.prototype.setup = function(name, settings) { } // just save everything we get - this.name = name; this.settings = settings || {}; // Check the debug env settings @@ -190,14 +207,16 @@ DataSource.prototype.setup = function(name, settings) { this.connected = false; this.connecting = false; + if(typeof connector === 'string') { + name = connector; + connector = undefined; + } + name = name || (connector && connector.name); + this.name = name; + if (name && !connector) { - // and initialize dataSource using connector - // this is only one initialization entry point of connector - // this module should define `connector` member of `this` (dataSource) - if (typeof name === 'object') { - connector = name; - this.name = connector.name; - } else if (name.match(/^\//)) { + // The connector has not been resolved + if (name.match(/^\//)) { // try absolute path connector = require(name); } 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) { var postInit = function postInit(err, result) { diff --git a/lib/utils.js b/lib/utils.js index 3b844c17..53dc5c1f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -2,6 +2,7 @@ exports.safeRequire = safeRequire; exports.fieldsToArray = fieldsToArray; exports.selectFields = selectFields; exports.removeUndefined = removeUndefined; +exports.parseSettings = parseSettings; var traverse = require('traverse'); @@ -87,3 +88,33 @@ function removeUndefined(query) { 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]; // : + 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; +} diff --git a/package.json b/package.json index 81c3b74b..7c575cfe 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,13 @@ "name": "loopback-datasource-juggler", "version": "1.0.0", "description": "LoopBack DataSoure Juggler", - "keywords": [ "StrongLoop", "LoopBack", "DataSource", "Juggler", "ORM" ], + "keywords": [ + "StrongLoop", + "LoopBack", + "DataSource", + "Juggler", + "ORM" + ], "repository": { "type": "git", "url": "https://github.com/strongloop/loopback-datasource-juggler" @@ -26,7 +32,8 @@ "dependencies": { "async": "~0.2.9", "inflection": "~1.2.6", - "traverse": "~0.6.5" + "traverse": "~0.6.5", + "qs": "~0.6.5" }, "license": "MIT" } diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index c884e9d3..1d3459b4 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -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'); + }); +}); + diff --git a/test/util.test.js b/test/util.test.js index d4943d9e..02dfa1d5 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -47,4 +47,67 @@ describe('util.removeUndefined', function(){ should.equal(removeUndefined('x'), 'x'); }); +}); + +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'); + + }); + }); \ No newline at end of file