From 44ea464414583f6ecec67bacb7a3bb5ccdd8fc78 Mon Sep 17 00:00:00 2001 From: DullReferenceException Date: Fri, 30 May 2014 18:05:28 -0700 Subject: [PATCH 1/2] Enabling third-party drivers Added a method and some documentation for plugging in a third-party driver for `orm2`. Conflicts: .gitignore --- Drivers.md | 161 +++++++++++++++++++++++++++++++++++++++++ Readme.md | 11 +++ lib/Drivers/drivers.js | 21 ++++++ lib/ORM.js | 7 +- 4 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 Drivers.md create mode 100644 lib/Drivers/drivers.js diff --git a/Drivers.md b/Drivers.md new file mode 100644 index 00000000..b82df269 --- /dev/null +++ b/Drivers.md @@ -0,0 +1,161 @@ +# Creating drivers for orm2 + +To add a driver to `orm2`, call its `addDriver` method: + +```js +require('orm2').addDriver('cassandra', CassandraDriver); +``` + +The first argument is the alias to register for connection URLs. For example, the above will allow you to do this: + +```js +var orm = require('orm2'); +orm.connect('cassandra://username:password@localhost/test', function (err, db) { }); +``` + +The second argument is the constructor for your driver object. + +## Defining driver objects + +Your driver should provide the following members. + +### Constructor(config, connection, opts) + +The driver object constructor should have three parameters: + +* config - optional configuration for the database connection. It contains the following properties: + * timezone - optional timezone + * href - URL to use for connecting to the database if the connection argument is null + * host - The hostname of `href` + * pathname - The `path` of `href` + * ssl - Boolean indicating whether the driver should use SSL when connecting to the database + * query - Optional configuration for how the driver should perform queries + * ssl - Boolean indicating whether queries should be sent using SSL + * strdates - Boolean indicating whether strings should be used for dates +* connection - optionally passed if reusing an existing connection +* opts - optional options configuring the driver's behavior. It contains the following properties: + * pool - A boolean indicating whether the driver should use connection pooling + * debug - If true, whether the driver should operate in debug mode + * settings - A key/value object store. Use `get(key)` and `set(key, value)` methods to manipulate the settings. The + following settings are defined: + * properties.primary_key - The column/field name to use for object primary keys + * properties.association_key - A function taking a `name` and `field` parameter that returns the name of the + column that establishes the association + +### isSql property + +This should be set to `true` if your database is a SQL database. + +### customTypes property + +Your driver should have a `customTypes` object, with the property names being the names of the custom types, and each +value being the options relating to the type. + +### connect(cb) method (required) + +Establishes your database connection. + +### reconnect(cb, connection) method (optional) + +Establishes/re-establishes a connection. The optional prior connection is passed in the `connection` parameter. + +### ping(cb) method (required) + +Tests whether your connection is still alive. + +### close(cb) method (required) + +Closes your database connection. + +### propertyToValue(value, property) method (required) + +Maps an object property to the correlated value to use for the database. + +### valueToProperty(value, property) method (required) + +Maps a database value to the property value to use for the mapped object. + +### find(fields, table, conditions, opts, cb) method (required) + +Implement this to select and return data stored in the database. +See the [documentation for Model.find](./README.md#modelfind-conditions---options---limit---order---cb-). + +### insert(table, data, id_prop, cb) method (required) + +Inserts an object into a database table. + +### update(table, changes, conditions, cb) method (required) + +Updates an object in the appropriate database row. + +### remove(table, conditions, cb) method (required) + +Implement this to support the removal of an object from a table. + +### count(table, conditions, opts, cb) method (required) + +Implement this to support [Model.count](./README.md#modelcount-conditions--cb). + +### clear(table, cb) method (required) + +Implement this to support `Model.clear`, which deletes all objects from a given table. + +### eagerQuery(association, opts, ids, cb) method (required) + +Implement this to support eager loading of associated objects. + +### query property (optional) + +For SQL databases, the `Query` object from the `sql-query` package used to generate ad-hoc queries. + +### getQuery() method (optional) + +For SQL databases, returns a `Query` object from the `sql-query` package. + +### execQuery(query, cb) method (optional) + +For SQL databases, this executes a `Query` object from the `sql-query` package. + +### aggregate_functions[] property (optional) + +If your driver supports SQL aggregate functions, this should be an array of supported function names. + +### hasMany(Model, association) method (optional) + +If your driver maintains associations in a unique (non-SQL-like) manner, return an object from this method to implement +a one-to-many association. The return value should have the following methods: + +* has(Instance, Associations, conditions, cb) - tests if the associations have any objects matching the conditions +* get(Instance, conditions, options, createInstance, cb) - retrieves associated objects +* add(Instance, Association, data, cb) - inserts an associated object +* del(Instance, Associations, cb) - deletes an object from a set of associations + +### sync(opts, cb) method (optional) + +If your driver supports creating a table from a model, implement this method. The following options are passed: + +* extension +* id +* table +* properties +* allProperties +* indexes +* customTypes +* one_associations +* many_associations +* extend_associations + +### drop(opts, cb) method (optional) + +If your driver supports dropping a table, implement this method. The following options are passed to this method: + +* table - The name of the table +* properties +* one_associations +* many_associations + +### on(event, cb) method (required) + +Your driver should be an `EventEmitter`, and should emit the following types of events, when applicable: + +* error diff --git a/Readme.md b/Readme.md index facd4392..d84e1e5b 100755 --- a/Readme.md +++ b/Readme.md @@ -840,3 +840,14 @@ Person.hasMany("pets", Pet, { Person(1).getPets(...); Pet(2).getOwners(...); ``` + +## Adding external drivers + +To add an external driver to `orm2`, call the `addDriver` method, passing in the alias to use for connecting with this +driver, along with the constructor for the driver object: + +```js +require('orm2').addDriver('cassandra', CassandraDriver); +``` + +See [the documentation for creating drivers](./Drivers.md) for more details. diff --git a/lib/Drivers/drivers.js b/lib/Drivers/drivers.js new file mode 100644 index 00000000..64d47f16 --- /dev/null +++ b/lib/Drivers/drivers.js @@ -0,0 +1,21 @@ +var aliases = require('./aliases'); + +module.exports.add = addDriver; +module.exports.get = getDriver; + + +var drivers = {}; + +function addDriver(name, constructor) { + drivers[name] = constructor; +} + +function getDriver(name) { + if (name in aliases) { + return getDriver(aliases[name]); + } else if (!(name in drivers)) { + drivers[name] = require("./DML/" + name).Driver; + } + + return drivers[name]; +} diff --git a/lib/ORM.js b/lib/ORM.js index c6153614..02a93810 100644 --- a/lib/ORM.js +++ b/lib/ORM.js @@ -8,6 +8,7 @@ var _ = require("lodash"); var Model = require("./Model").Model; var DriverAliases = require("./Drivers/aliases"); +var drivers = require("./Drivers/drivers"); var Settings = require("./Settings"); var Singleton = require("./Singleton"); var ORMError = require("./Error"); @@ -48,7 +49,7 @@ exports.use = function (connection, proto, opts, cb) { } try { - var Driver = require("./Drivers/DML/" + proto).Driver; + var Driver = drivers.get(proto); var settings = new Settings.Container(exports.settings.get('*')); var driver = new Driver(null, connection, { debug : (opts.query && opts.query.debug === 'true'), @@ -109,7 +110,7 @@ exports.connect = function (opts, cb) { } try { - var Driver = require("./Drivers/DML/" + proto).Driver; + var Driver = drivers.get(proto); var settings = new Settings.Container(exports.settings.get('*')); var debug = extractOption(opts, "debug"); var pool = extractOption(opts, "pool"); @@ -142,6 +143,8 @@ exports.connect = function (opts, cb) { return db; }; +exports.addDriver = drivers.add; + function ORM(driver_name, driver, settings) { this.validators = exports.validators; this.enforce = exports.enforce; From 33a09e9b7ac31082242ddc59b0d17422752dfc21 Mon Sep 17 00:00:00 2001 From: DullReferenceException Date: Tue, 3 Jun 2014 18:16:21 -0700 Subject: [PATCH 2/2] Terminology change from Driver to Adapter Updated terminology, where third-party database adapters are now called Adapters instead of Drivers. --- Drivers.md => Adapters.md | 38 ++++++++++++++++++-------------------- Readme.md | 10 +++++----- lib/Adapters.js | 21 +++++++++++++++++++++ lib/Drivers/drivers.js | 21 --------------------- lib/ORM.js | 8 ++++---- 5 files changed, 48 insertions(+), 50 deletions(-) rename Drivers.md => Adapters.md (74%) create mode 100644 lib/Adapters.js delete mode 100644 lib/Drivers/drivers.js diff --git a/Drivers.md b/Adapters.md similarity index 74% rename from Drivers.md rename to Adapters.md index b82df269..866a19fb 100644 --- a/Drivers.md +++ b/Adapters.md @@ -1,9 +1,9 @@ -# Creating drivers for orm2 +# Creating database adapters for orm2 -To add a driver to `orm2`, call its `addDriver` method: +To add a database adapter to `orm`, call its `addAdapter` method: ```js -require('orm2').addDriver('cassandra', CassandraDriver); +require('orm2').addAdapter('cassandra', CassandraAdapter); ``` The first argument is the alias to register for connection URLs. For example, the above will allow you to do this: @@ -13,29 +13,29 @@ var orm = require('orm2'); orm.connect('cassandra://username:password@localhost/test', function (err, db) { }); ``` -The second argument is the constructor for your driver object. +The second argument is the constructor for your adapter object. -## Defining driver objects +## Defining adapters -Your driver should provide the following members. +Your adapter should provide the following members. ### Constructor(config, connection, opts) -The driver object constructor should have three parameters: +The adapter object constructor should have three parameters: * config - optional configuration for the database connection. It contains the following properties: * timezone - optional timezone * href - URL to use for connecting to the database if the connection argument is null * host - The hostname of `href` * pathname - The `path` of `href` - * ssl - Boolean indicating whether the driver should use SSL when connecting to the database - * query - Optional configuration for how the driver should perform queries + * ssl - Boolean indicating whether the adapter should use SSL when connecting to the database + * query - Optional configuration for how the adapter should perform queries * ssl - Boolean indicating whether queries should be sent using SSL * strdates - Boolean indicating whether strings should be used for dates * connection - optionally passed if reusing an existing connection -* opts - optional options configuring the driver's behavior. It contains the following properties: - * pool - A boolean indicating whether the driver should use connection pooling - * debug - If true, whether the driver should operate in debug mode +* opts - optional options configuring the adapter's behavior. It contains the following properties: + * pool - A boolean indicating whether the adapter should use connection pooling + * debug - If true, whether the adapter should operate in debug mode * settings - A key/value object store. Use `get(key)` and `set(key, value)` methods to manipulate the settings. The following settings are defined: * properties.primary_key - The column/field name to use for object primary keys @@ -48,7 +48,7 @@ This should be set to `true` if your database is a SQL database. ### customTypes property -Your driver should have a `customTypes` object, with the property names being the names of the custom types, and each +Your adapter should have a `customTypes` object, with the property names being the names of the custom types, and each value being the options relating to the type. ### connect(cb) method (required) @@ -118,11 +118,11 @@ For SQL databases, this executes a `Query` object from the `sql-query` package. ### aggregate_functions[] property (optional) -If your driver supports SQL aggregate functions, this should be an array of supported function names. +If your adapter supports SQL aggregate functions, this should be an array of supported function names. ### hasMany(Model, association) method (optional) -If your driver maintains associations in a unique (non-SQL-like) manner, return an object from this method to implement +If your adapter maintains associations in a unique (non-SQL-like) manner, return an object from this method to implement a one-to-many association. The return value should have the following methods: * has(Instance, Associations, conditions, cb) - tests if the associations have any objects matching the conditions @@ -132,7 +132,7 @@ a one-to-many association. The return value should have the following methods: ### sync(opts, cb) method (optional) -If your driver supports creating a table from a model, implement this method. The following options are passed: +If your adapter supports creating a table from a model, implement this method. The following options are passed: * extension * id @@ -147,7 +147,7 @@ If your driver supports creating a table from a model, implement this method. Th ### drop(opts, cb) method (optional) -If your driver supports dropping a table, implement this method. The following options are passed to this method: +If your adapter supports dropping a table, implement this method. The following options are passed to this method: * table - The name of the table * properties @@ -156,6 +156,4 @@ If your driver supports dropping a table, implement this method. The following o ### on(event, cb) method (required) -Your driver should be an `EventEmitter`, and should emit the following types of events, when applicable: - -* error +Your adapter should be an `EventEmitter`, and should emit the `error` event when applicable. diff --git a/Readme.md b/Readme.md index d84e1e5b..0e6b1c19 100755 --- a/Readme.md +++ b/Readme.md @@ -841,13 +841,13 @@ Person(1).getPets(...); Pet(2).getOwners(...); ``` -## Adding external drivers +## Adding external database adapters -To add an external driver to `orm2`, call the `addDriver` method, passing in the alias to use for connecting with this -driver, along with the constructor for the driver object: +To add an external database adapter to `orm`, call the `addAdapter` method, passing in the alias to use for connecting +with this adapter, along with the constructor for the adapter: ```js -require('orm2').addDriver('cassandra', CassandraDriver); +require('orm').addAdapter('cassandra', CassandraAdapter); ``` -See [the documentation for creating drivers](./Drivers.md) for more details. +See [the documentation for creating adapters](./Adapters.md) for more details. diff --git a/lib/Adapters.js b/lib/Adapters.js new file mode 100644 index 00000000..c37cda3a --- /dev/null +++ b/lib/Adapters.js @@ -0,0 +1,21 @@ +var aliases = require('./Drivers/aliases'); + +module.exports.add = addAdapter; +module.exports.get = getAdapter; + + +var adapters = {}; + +function addAdapter(name, constructor) { + adapters[name] = constructor; +} + +function getAdapter(name) { + if (name in aliases) { + return getAdapter(aliases[name]); + } else if (!(name in adapters)) { + adapters[name] = require("./Drivers/DML/" + name).Driver; + } + + return adapters[name]; +} diff --git a/lib/Drivers/drivers.js b/lib/Drivers/drivers.js deleted file mode 100644 index 64d47f16..00000000 --- a/lib/Drivers/drivers.js +++ /dev/null @@ -1,21 +0,0 @@ -var aliases = require('./aliases'); - -module.exports.add = addDriver; -module.exports.get = getDriver; - - -var drivers = {}; - -function addDriver(name, constructor) { - drivers[name] = constructor; -} - -function getDriver(name) { - if (name in aliases) { - return getDriver(aliases[name]); - } else if (!(name in drivers)) { - drivers[name] = require("./DML/" + name).Driver; - } - - return drivers[name]; -} diff --git a/lib/ORM.js b/lib/ORM.js index 02a93810..bf6a483a 100644 --- a/lib/ORM.js +++ b/lib/ORM.js @@ -8,7 +8,7 @@ var _ = require("lodash"); var Model = require("./Model").Model; var DriverAliases = require("./Drivers/aliases"); -var drivers = require("./Drivers/drivers"); +var adapters = require("./Adapters"); var Settings = require("./Settings"); var Singleton = require("./Singleton"); var ORMError = require("./Error"); @@ -49,7 +49,7 @@ exports.use = function (connection, proto, opts, cb) { } try { - var Driver = drivers.get(proto); + var Driver = adapters.get(proto); var settings = new Settings.Container(exports.settings.get('*')); var driver = new Driver(null, connection, { debug : (opts.query && opts.query.debug === 'true'), @@ -110,7 +110,7 @@ exports.connect = function (opts, cb) { } try { - var Driver = drivers.get(proto); + var Driver = adapters.get(proto); var settings = new Settings.Container(exports.settings.get('*')); var debug = extractOption(opts, "debug"); var pool = extractOption(opts, "pool"); @@ -143,7 +143,7 @@ exports.connect = function (opts, cb) { return db; }; -exports.addDriver = drivers.add; +exports.addAdapter = adapters.add; function ORM(driver_name, driver, settings) { this.validators = exports.validators;