2013-08-13 16:37:09 +00:00
/ * !
2013-05-17 17:54:14 +00:00
* Module dependencies
* /
2013-05-24 05:20:20 +00:00
var ModelBuilder = require ( './model-builder.js' ) . ModelBuilder ;
2013-10-03 00:20:54 +00:00
var ModelDefinition = require ( './model-definition.js' ) ;
2013-05-28 05:20:30 +00:00
var jutil = require ( './jutil' ) ;
2013-10-25 23:18:02 +00:00
var utils = require ( './utils' ) ;
2013-05-17 17:54:14 +00:00
var ModelBaseClass = require ( './model.js' ) ;
var DataAccessObject = require ( './dao.js' ) ;
var List = require ( './list.js' ) ;
var EventEmitter = require ( 'events' ) . EventEmitter ;
var util = require ( 'util' ) ;
var path = require ( 'path' ) ;
var fs = require ( 'fs' ) ;
2013-11-05 17:29:24 +00:00
var assert = require ( 'assert' ) ;
2013-05-22 17:41:08 +00:00
var async = require ( 'async' ) ;
2013-05-17 17:54:14 +00:00
var existsSync = fs . existsSync || path . existsSync ;
/ * *
* Export public API
* /
exports . DataSource = DataSource ;
2013-08-13 16:37:09 +00:00
/ * !
2013-05-17 17:54:14 +00:00
* Helpers
* /
var slice = Array . prototype . slice ;
/ * *
2013-07-23 18:16:43 +00:00
* DataSource - connector - specific classes factory .
2013-05-17 17:54:14 +00:00
*
2013-07-23 18:16:43 +00:00
* All classes in single dataSource shares same connector type and
2013-05-17 17:54:14 +00:00
* one database connection
*
2013-10-25 23:18:02 +00:00
* @ param { String } name - type of dataSource connector ( mysql , mongoose , oracle , redis )
2013-08-13 16:37:09 +00:00
* @ param { Object } settings - any database - specific settings which we need to
2013-07-23 18:16:43 +00:00
* establish connection ( of course it depends on specific connector )
2013-05-17 17:54:14 +00:00
*
* - host
* - port
* - username
* - password
* - database
* - debug { Boolean } = false
*
* @ example DataSource creation , waiting for connection callback
* ` ` `
2013-07-23 18:16:43 +00:00
* var dataSource = new DataSource ( 'mysql' , { database : 'myapp_test' } ) ;
* dataSource . define ( ... ) ;
* dataSource . on ( 'connected' , function ( ) {
2013-05-17 17:54:14 +00:00
* // work with database
* } ) ;
* ` ` `
* /
function DataSource ( name , settings ) {
if ( ! ( this instanceof DataSource ) ) {
return new DataSource ( name , settings ) ;
}
2013-10-25 23:18:02 +00:00
// 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 ) ;
2013-06-18 21:44:30 +00:00
// operation metadata
2013-07-23 18:16:43 +00:00
// Initialize it before calling setup as the connector might register operations
2013-06-18 21:44:30 +00:00
this . _operations = { } ;
2013-05-17 17:54:14 +00:00
this . setup ( name , settings ) ;
2013-07-23 19:05:08 +00:00
2013-07-23 20:19:35 +00:00
this . _setupConnector ( ) ;
2013-07-23 19:05:08 +00:00
2013-06-12 22:45:31 +00:00
// connector
2013-07-23 18:16:43 +00:00
var connector = this . connector ;
2013-06-12 22:45:31 +00:00
// DataAccessObject - connector defined or supply the default
2013-06-14 20:56:44 +00:00
this . DataAccessObject = ( connector && connector . DataAccessObject ) ? connector . DataAccessObject : this . constructor . DataAccessObject ;
2013-10-25 23:18:02 +00:00
this . DataAccessObject . apply ( this , arguments ) ;
2013-06-14 20:56:44 +00:00
2013-06-18 21:44:30 +00:00
2013-06-11 16:03:11 +00:00
// define DataAccessObject methods
Object . keys ( this . DataAccessObject ) . forEach ( function ( name ) {
var fn = this . DataAccessObject [ name ] ;
if ( typeof fn === 'function' ) {
this . defineOperation ( name , {
2013-06-18 19:07:13 +00:00
accepts : fn . accepts ,
'returns' : fn . returns ,
2013-06-11 16:03:11 +00:00
http : fn . http ,
2013-06-12 22:45:31 +00:00
remoteEnabled : fn . shared ? true : false ,
scope : this . DataAccessObject ,
fnName : name
2013-06-11 16:03:11 +00:00
} ) ;
}
} . bind ( this ) ) ;
// define DataAccessObject.prototype methods
Object . keys ( this . DataAccessObject . prototype ) . forEach ( function ( name ) {
var fn = this . DataAccessObject . prototype [ name ] ;
if ( typeof fn === 'function' ) {
2013-07-25 00:21:35 +00:00
var returns = fn . returns ;
2013-06-11 16:03:11 +00:00
this . defineOperation ( name , {
prototype : true ,
2013-06-18 19:07:13 +00:00
accepts : fn . accepts ,
'returns' : fn . returns ,
2013-06-11 16:03:11 +00:00
http : fn . http ,
2013-06-12 22:45:31 +00:00
remoteEnabled : fn . shared ? true : false ,
scope : this . DataAccessObject . prototype ,
fnName : name
2013-06-11 16:03:11 +00:00
} ) ;
}
} . bind ( this ) ) ;
2013-08-13 16:37:09 +00:00
}
2013-05-17 17:54:14 +00:00
2013-05-24 05:20:20 +00:00
util . inherits ( DataSource , ModelBuilder ) ;
2013-05-17 17:54:14 +00:00
2013-05-23 23:38:14 +00:00
// allow child classes to supply a data access object
DataSource . DataAccessObject = DataAccessObject ;
2013-05-17 17:54:14 +00:00
// Copy over statics
2013-05-24 05:20:20 +00:00
for ( var m in ModelBuilder ) {
2013-05-17 21:41:04 +00:00
if ( ! DataSource . hasOwnProperty ( m ) && 'super_' !== m ) {
2013-05-24 05:20:20 +00:00
DataSource [ m ] = ModelBuilder [ m ] ;
2013-05-17 17:54:14 +00:00
}
}
2013-07-23 20:19:35 +00:00
/ * *
* Set up the connector instance for backward compatibility with JugglingDB schema / adapter
* @ private
* /
DataSource . prototype . _setupConnector = function ( ) {
this . connector = this . connector || this . adapter ; // The legacy JugglingDB adapter will set up `adapter` property
this . adapter = this . connector ; // Keep the adapter as an alias to connector
if ( this . connector ) {
if ( ! this . connector . dataSource ) {
// Set up the dataSource if the connector doesn't do so
this . connector . dataSource = this ;
}
2013-07-25 05:57:53 +00:00
var dataSource = this ;
2013-07-23 20:19:35 +00:00
this . connector . log = function ( query , start ) {
dataSource . log ( query , start ) ;
} ;
this . connector . logger = function ( query ) {
var t1 = Date . now ( ) ;
var log = this . log ;
return function ( q ) {
log ( q || query , t1 ) ;
} ;
} ;
}
2013-08-13 16:37:09 +00:00
} ;
2013-07-23 20:19:35 +00:00
2013-08-13 16:37:09 +00:00
/ * *
* Set up the data source
* @ param { String } name The name
* @ param { Object } settings The settings
* @ returns { * }
* @ private
* /
2013-05-17 17:54:14 +00:00
DataSource . prototype . setup = function ( name , settings ) {
2013-07-23 18:16:43 +00:00
var dataSource = this ;
var connector ;
2013-05-17 17:54:14 +00:00
2013-06-14 20:56:44 +00:00
// support single settings object
2013-07-30 17:52:30 +00:00
if ( name && typeof name === 'object' && ! settings ) {
2013-06-17 14:54:51 +00:00
settings = name ;
name = undefined ;
2013-06-14 20:56:44 +00:00
}
2013-10-25 23:18:02 +00:00
2013-06-17 14:54:51 +00:00
if ( typeof settings === 'object' ) {
if ( settings . initialize ) {
2013-07-23 18:16:43 +00:00
connector = settings ;
2013-06-17 14:54:51 +00:00
} else if ( settings . connector ) {
2013-07-23 18:16:43 +00:00
connector = settings . connector ;
2013-07-30 17:52:30 +00:00
} else if ( settings . adapter ) {
connector = settings . adapter ;
2013-06-17 14:54:51 +00:00
}
2013-06-14 20:56:44 +00:00
}
2013-05-17 17:54:14 +00:00
// just save everything we get
2013-08-19 06:11:32 +00:00
this . settings = settings || { } ;
// Check the debug env settings
var debugEnv = process . env . DEBUG || process . env . NODE _DEBUG || '' ;
var debugModules = debugEnv . split ( /[\s,]+/ ) ;
if ( debugModules . indexOf ( 'loopback' ) !== - 1 ) {
this . settings . debug = true ;
}
2013-05-17 17:54:14 +00:00
// Disconnected by default
this . connected = false ;
this . connecting = false ;
2013-10-25 23:18:02 +00:00
if ( typeof connector === 'string' ) {
name = connector ;
connector = undefined ;
}
name = name || ( connector && connector . name ) ;
this . name = name ;
2013-07-23 18:16:43 +00:00
if ( name && ! connector ) {
2013-10-29 20:04:23 +00:00
if ( typeof name === 'object' ) {
// The first argument might be the connector itself
connector = name ;
this . name = connector . name ;
}
2013-10-25 23:18:02 +00:00
// The connector has not been resolved
2013-10-29 20:04:23 +00:00
else if ( name . match ( /^\// ) ) {
2013-05-17 17:54:14 +00:00
// try absolute path
2013-07-23 18:16:43 +00:00
connector = require ( name ) ;
2013-07-23 21:40:44 +00:00
} else if ( existsSync ( _ _dirname + '/connectors/' + name + '.js' ) ) {
2013-07-23 18:16:43 +00:00
// try built-in connector
2013-07-23 21:40:44 +00:00
connector = require ( './connectors/' + name ) ;
2013-05-17 17:54:14 +00:00
} else {
2013-07-23 18:16:43 +00:00
// try foreign connector
2013-05-17 17:54:14 +00:00
try {
2013-07-30 17:52:30 +00:00
if ( name . indexOf ( 'loopback-connector-' ) === - 1 ) {
name = 'loopback-connector-' + name ;
}
connector = require ( name ) ;
2013-05-17 17:54:14 +00:00
} catch ( e ) {
2013-08-20 23:14:27 +00:00
return console . log ( '\nWARNING: LoopbackData connector "' + name + '" is not installed,\nso your models would not work, to fix run:\n\n npm install ' + name , '\n' ) ;
2013-05-17 17:54:14 +00:00
}
}
}
2013-07-23 18:16:43 +00:00
if ( connector ) {
2013-09-12 20:31:21 +00:00
var postInit = function postInit ( err , result ) {
2013-05-17 17:54:14 +00:00
2013-07-23 20:19:35 +00:00
this . _setupConnector ( ) ;
2013-07-23 18:16:43 +00:00
// we have an connector now?
if ( ! this . connector ) {
throw new Error ( 'Connector is not defined correctly: it should create `connector` member of dataSource' ) ;
2013-05-17 17:54:14 +00:00
}
2013-09-12 20:31:21 +00:00
this . connected = ! err ; // Connected now
if ( this . connected ) {
this . emit ( 'connected' ) ;
2013-10-11 05:47:26 +00:00
} else {
// The connection fails, let's report it and hope it will be recovered in the next call
2013-10-11 18:48:12 +00:00
console . error ( 'Connection fails: ' , err , '\nIt will be retried for the next request.' ) ;
2013-10-11 05:47:26 +00:00
this . connecting = false ;
2013-09-12 20:31:21 +00:00
}
2013-05-17 17:54:14 +00:00
2013-07-11 16:55:26 +00:00
} . bind ( this ) ;
2013-07-23 18:16:43 +00:00
if ( 'function' === typeof connector . initialize ) {
2013-07-11 16:55:26 +00:00
// Call the async initialize method
2013-07-23 18:16:43 +00:00
connector . initialize ( this , postInit ) ;
} else if ( 'function' === typeof connector ) {
// Use the connector constructor directly
this . connector = new connector ( this . settings ) ;
2013-07-11 16:55:26 +00:00
postInit ( ) ;
}
2013-05-17 17:54:14 +00:00
}
2013-07-23 18:16:43 +00:00
dataSource . connect = function ( cb ) {
var dataSource = this ;
if ( dataSource . connected || dataSource . connecting ) {
2013-05-31 20:40:50 +00:00
process . nextTick ( function ( ) {
cb && cb ( ) ;
} ) ;
2013-05-17 23:21:12 +00:00
return ;
}
2013-07-23 18:16:43 +00:00
dataSource . connecting = true ;
if ( dataSource . connector . connect ) {
dataSource . connector . connect ( function ( err , result ) {
2013-05-17 17:54:14 +00:00
if ( ! err ) {
2013-07-23 18:16:43 +00:00
dataSource . connected = true ;
dataSource . connecting = false ;
dataSource . emit ( 'connected' ) ;
2013-10-11 05:47:26 +00:00
} else {
dataSource . connected = false ;
dataSource . connecting = false ;
dataSource . emit ( 'error' , err ) ;
2013-05-17 17:54:14 +00:00
}
2013-05-31 20:40:50 +00:00
cb && cb ( err , result ) ;
2013-05-17 17:54:14 +00:00
} ) ;
} else {
2013-05-31 20:40:50 +00:00
process . nextTick ( function ( ) {
2013-10-11 05:47:26 +00:00
dataSource . connected = true ;
dataSource . connecting = false ;
dataSource . emit ( 'connected' ) ;
2013-05-31 20:40:50 +00:00
cb && cb ( ) ;
} ) ;
2013-05-17 17:54:14 +00:00
}
} ;
2013-08-13 16:37:09 +00:00
} ;
2013-05-17 17:54:14 +00:00
2013-11-05 17:29:24 +00:00
function isModelClass ( cls ) {
if ( ! cls ) {
return false ;
}
return cls . prototype instanceof ModelBaseClass ;
}
2013-11-07 21:09:09 +00:00
/ * !
* Define relations for the model class from the relations object
* @ param modelClass
* @ param relations
* /
DataSource . prototype . defineRelations = function ( modelClass , relations ) {
// Create a function for the closure in the loop
var createListener = function ( name , relation , targetModel , throughModel ) {
if ( targetModel && targetModel . settings . unresolved ) {
targetModel . once ( 'defined' , function ( model ) {
// Check if the through model doesn't exist or resolved
if ( ! throughModel || ! throughModel . settings . unresolved ) {
// The target model is resolved
var params = {
foreignKey : relation . foreignKey ,
as : name ,
model : model
} ;
if ( throughModel ) {
params . through = throughModel ;
}
modelClass [ relation . type ] . call ( modelClass , name , params ) ;
}
} ) ;
}
if ( throughModel && throughModel . settings . unresolved ) {
// Set up a listener to the through model
throughModel . once ( 'defined' , function ( model ) {
if ( ! targetModel . settings . unresolved ) {
// The target model is resolved
var params = {
foreignKey : relation . foreignKey ,
as : name ,
model : targetModel ,
through : model
} ;
modelClass [ relation . type ] . call ( modelClass , name , params ) ;
}
} ) ;
}
} ;
// Set up the relations
if ( relations ) {
for ( var rn in relations ) {
var r = relations [ rn ] ;
assert ( [ 'belongsTo' , 'hasMany' , 'hasAndBelongsToMany' ] . indexOf ( r . type ) !== - 1 , "Invalid relation type: " + r . type ) ;
var targetModel = isModelClass ( r . model ) ? r . model : this . models [ r . model ] ;
if ( ! targetModel ) {
// The target model doesn't exist, let create a place holder for it
targetModel = this . define ( r . model , { } , { unresolved : true } ) ;
}
var throughModel = null ;
if ( r . through ) {
throughModel = isModelClass ( r . through ) ? r . through : this . models [ r . through ] ;
if ( ! throughModel ) {
// The through model doesn't exist, let create a place holder for it
throughModel = this . define ( r . through , { } , { unresolved : true } ) ;
}
}
if ( targetModel . settings . unresolved || ( throughModel && throughModel . settings . unresolved ) ) {
// Create a listener to defer the relation set up
createListener ( rn , r , targetModel , throughModel ) ;
} else {
// The target model is resolved
var params = {
foreignKey : r . foreignKey ,
as : rn ,
model : targetModel
} ;
if ( throughModel ) {
params . through = throughModel ;
}
modelClass [ r . type ] . call ( modelClass , rn , params ) ;
}
}
}
} ;
/ * !
* Set up the data access functions from the data source
* @ param modelClass
* @ param settings
* /
DataSource . prototype . setupDataAccess = function ( modelClass , settings ) {
// add data access objects
this . mixin ( modelClass ) ;
var relations = settings . relationships || settings . relations ;
this . defineRelations ( modelClass , relations ) ;
if ( this . connector && this . connector . define ) {
// pass control to connector
this . connector . define ( {
model : modelClass ,
properties : modelClass . definition . properties ,
settings : settings
} ) ;
}
} ;
2013-05-17 17:54:14 +00:00
/ * *
2013-08-13 16:37:09 +00:00
* Define a model class
2013-05-17 17:54:14 +00:00
*
* @ param { String } className
* @ param { Object } properties - hash of class properties in format
* ` {property: Type, property2: Type2, ...} `
* or
* ` {property: {type: Type}, property2: {type: Type2}, ...} `
* @ param { Object } settings - other configuration of class
* @ return newly created class
*
* @ example simple case
* ` ` `
2013-07-23 18:16:43 +00:00
* var User = dataSource . define ( 'User' , {
2013-05-17 17:54:14 +00:00
* email : String ,
* password : String ,
* birthDate : Date ,
* activated : Boolean
* } ) ;
* ` ` `
* @ example more advanced case
* ` ` `
2013-07-23 18:16:43 +00:00
* var User = dataSource . define ( 'User' , {
2013-05-17 17:54:14 +00:00
* email : { type : String , limit : 150 , index : true } ,
* password : { type : String , limit : 50 } ,
* birthDate : Date ,
* registrationDate : { type : Date , default : function ( ) { return new Date } } ,
* activated : { type : Boolean , default : false }
* } ) ;
* ` ` `
* /
2013-08-13 16:37:09 +00:00
DataSource . prototype . createModel = DataSource . prototype . define = function defineClass ( className , properties , settings ) {
2013-05-17 21:41:04 +00:00
var args = slice . call ( arguments ) ;
2013-08-13 16:37:09 +00:00
if ( ! className ) {
throw new Error ( 'Class name required' ) ;
}
if ( args . length === 1 ) {
properties = { } ;
args . push ( properties ) ;
}
if ( args . length === 2 ) {
settings = { } ;
args . push ( settings ) ;
}
2013-05-17 21:41:04 +00:00
properties = properties || { } ;
settings = settings || { } ;
2013-08-26 20:38:24 +00:00
if ( this . isRelational ( ) ) {
// Set the strict mode to be true for relational DBs by default
if ( settings . strict === undefined || settings . strict === null ) {
settings . strict = true ;
}
if ( settings . strict === false ) {
settings . strict = 'throw' ;
}
}
2013-11-07 21:09:09 +00:00
var modelClass = ModelBuilder . prototype . define . call ( this , className , properties , settings ) ;
2013-05-17 17:54:14 +00:00
2013-11-05 06:53:02 +00:00
if ( settings . unresolved ) {
2013-11-07 21:09:09 +00:00
return modelClass ;
2013-11-05 06:53:02 +00:00
}
2013-11-07 21:09:09 +00:00
this . setupDataAccess ( modelClass , settings ) ;
return modelClass ;
2013-05-17 17:54:14 +00:00
} ;
2013-06-14 20:56:44 +00:00
2013-05-23 23:38:14 +00:00
/ * *
* Mixin DataAccessObject methods .
2013-08-13 16:37:09 +00:00
*
* @ param { Function } ModelCtor The model constructor
2013-05-23 23:38:14 +00:00
* /
DataSource . prototype . mixin = function ( ModelCtor ) {
2013-06-11 16:03:11 +00:00
var ops = this . operations ( ) ;
2013-06-12 22:45:31 +00:00
var DAO = this . DataAccessObject ;
2013-05-23 23:38:14 +00:00
2013-06-12 22:45:31 +00:00
// mixin DAO
jutil . mixin ( ModelCtor , DAO ) ;
2013-05-23 23:38:14 +00:00
2013-06-12 22:45:31 +00:00
// decorate operations as alias functions
2013-06-11 16:03:11 +00:00
Object . keys ( ops ) . forEach ( function ( name ) {
var op = ops [ name ] ;
var scope ;
if ( op . enabled ) {
scope = op . prototype ? ModelCtor . prototype : ModelCtor ;
2013-06-12 22:45:31 +00:00
// var sfn = scope[name] = function () {
// op.scope[op.fnName].apply(self, arguments);
// }
Object . keys ( op )
. filter ( function ( key ) {
// filter out the following keys
return ~ [
'scope' ,
'fnName' ,
'prototype'
2013-10-02 05:14:21 +00:00
] . indexOf ( key ) ;
2013-06-12 22:45:31 +00:00
} )
. forEach ( function ( key ) {
2013-06-11 16:03:11 +00:00
if ( typeof op [ key ] !== 'undefined' ) {
2013-06-12 22:45:31 +00:00
op . scope [ op . fnName ] [ key ] = op [ key ] ;
2013-06-11 16:03:11 +00:00
}
2013-06-12 22:45:31 +00:00
} ) ;
2013-06-11 16:03:11 +00:00
}
2013-06-12 22:45:31 +00:00
} ) ;
2013-10-02 05:14:21 +00:00
} ;
2013-05-23 23:38:14 +00:00
/ * *
* Attach an existing model to a data source .
2013-08-13 16:37:09 +00:00
*
2013-11-07 21:09:09 +00:00
* @ param { Function } modelClass The model constructor
2013-05-23 23:38:14 +00:00
* /
2013-11-07 21:09:09 +00:00
DataSource . prototype . attach = function ( modelClass ) {
if ( modelClass . dataSource === this ) {
2013-10-02 05:14:21 +00:00
// Already attached to the data source
return ;
}
2013-11-07 21:09:09 +00:00
var className = modelClass . modelName ;
var properties = modelClass . dataSource . definitions [ className ] . properties ;
var settings = modelClass . dataSource . definitions [ className ] . settings ;
2013-10-11 23:35:17 +00:00
// redefine the dataSource
2013-11-07 21:09:09 +00:00
modelClass . dataSource = this ;
2013-10-11 23:35:17 +00:00
// add to def
var def = new ModelDefinition ( this , className , properties , settings ) ;
def . build ( ) ;
this . definitions [ className ] = def ;
2013-11-07 21:09:09 +00:00
this . models [ className ] = modelClass ;
2013-05-24 00:29:03 +00:00
2013-11-07 21:09:09 +00:00
this . setupDataAccess ( modelClass , settings ) ;
2013-10-11 23:35:17 +00:00
2013-05-23 23:38:14 +00:00
return this ;
2013-08-13 16:37:09 +00:00
} ;
2013-05-23 23:38:14 +00:00
2013-05-17 21:41:04 +00:00
/ * *
* Define single property named ` prop ` on ` model `
*
* @ param { String } model - name of model
* @ param { String } prop - name of propery
* @ param { Object } params - property settings
* /
DataSource . prototype . defineProperty = function ( model , prop , params ) {
2013-05-24 05:20:20 +00:00
ModelBuilder . prototype . defineProperty . call ( this , model , prop , params ) ;
2013-05-17 21:41:04 +00:00
2013-10-02 22:18:50 +00:00
var resolvedProp = this . definitions [ model ] . properties [ prop ] ;
2013-07-23 18:16:43 +00:00
if ( this . connector . defineProperty ) {
2013-10-02 22:18:50 +00:00
this . connector . defineProperty ( model , prop , resolvedProp ) ;
2013-05-17 21:41:04 +00:00
}
} ;
2013-05-17 17:54:14 +00:00
/ * *
* Drop each model table and re - create .
2013-07-23 21:40:44 +00:00
* This method make sense only for sql connectors .
2013-05-17 17:54:14 +00:00
*
2013-08-08 15:30:26 +00:00
* @ param { String } or { [ String ] } Models to be migrated , if not present , apply to all models
2013-08-13 16:37:09 +00:00
* @ param { Function } [ cb ] The callback function
2013-08-08 15:30:26 +00:00
*
2013-05-17 17:54:14 +00:00
* @ warning All data will be lost ! Use autoupdate if you need your data .
* /
2013-08-08 15:30:26 +00:00
DataSource . prototype . automigrate = function ( models , cb ) {
2013-05-17 17:54:14 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . automigrate ) {
2013-08-08 15:30:26 +00:00
this . connector . automigrate ( models , cb ) ;
} else {
if ( ( ! cb ) && ( 'function' === typeof models ) ) {
cb = models ;
models = undefined ;
}
cb && process . nextTick ( cb ) ;
2013-05-17 17:54:14 +00:00
}
} ;
/ * *
* Update existing database tables .
2013-07-23 21:40:44 +00:00
* This method make sense only for sql connectors .
2013-08-08 15:30:26 +00:00
*
* @ param { String } or { [ String ] } Models to be migrated , if not present , apply to all models
2013-08-13 16:37:09 +00:00
* @ param { Function } [ cb ] The callback function
2013-05-17 17:54:14 +00:00
* /
2013-08-08 15:30:26 +00:00
DataSource . prototype . autoupdate = function ( models , cb ) {
2013-05-17 17:54:14 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . autoupdate ) {
2013-08-08 15:30:26 +00:00
this . connector . autoupdate ( models , cb ) ;
} else {
if ( ( ! cb ) && ( 'function' === typeof models ) ) {
cb = models ;
models = undefined ;
}
cb && process . nextTick ( cb ) ;
2013-05-17 17:54:14 +00:00
}
} ;
/ * *
* Discover existing database tables .
* This method returns an array of model objects , including { type , name , onwer }
2013-08-13 16:37:09 +00:00
*
* ` options `
*
* all : true - Discovering all models , false - Discovering the models owned by the current user
* views : true - Including views , false - only tables
* limit : The page size
* offset : The starting index
*
* @ param { Object } options The options
* @ param { Function } [ cb ] The callback function
*
2013-05-17 17:54:14 +00:00
* /
2013-06-17 18:43:20 +00:00
DataSource . prototype . discoverModelDefinitions = function ( options , cb ) {
2013-05-17 17:54:14 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverModelDefinitions ) {
this . connector . discoverModelDefinitions ( options , cb ) ;
2013-05-17 17:54:14 +00:00
} else if ( cb ) {
cb ( ) ;
}
} ;
2013-06-02 06:03:25 +00:00
2013-08-13 16:37:09 +00:00
/ * *
* The synchronous version of discoverModelDefinitions
* @ param { Object } options The options
* @ returns { * }
* /
2013-06-17 18:43:20 +00:00
DataSource . prototype . discoverModelDefinitionsSync = function ( options ) {
2013-06-02 06:03:25 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverModelDefinitionsSync ) {
return this . connector . discoverModelDefinitionsSync ( options ) ;
2013-06-02 06:03:25 +00:00
}
return null ;
} ;
2013-05-17 17:54:14 +00:00
/ * *
* Discover properties for a given model .
2013-08-13 16:37:09 +00:00
*
* ` property description `
*
* owner { String } The database owner or schema
* tableName { String } The table / view name
* columnName { String } The column name
* dataType { String } The data type
* dataLength { Number } The data length
* dataPrecision { Number } The numeric data precision
* dataScale { Number } The numeric data scale
* nullable { Boolean } If the data can be null
*
* ` options `
*
* owner / schema The database owner / schema
*
* @ param { String } modelName The table / view name
* @ param { Object } options The options
* @ param { Function } [ cb ] The callback function
*
2013-05-17 17:54:14 +00:00
* /
2013-08-13 16:37:09 +00:00
DataSource . prototype . discoverModelProperties = function ( modelName , options , cb ) {
2013-05-17 17:54:14 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverModelProperties ) {
2013-08-13 16:37:09 +00:00
this . connector . discoverModelProperties ( modelName , options , cb ) ;
2013-05-17 17:54:14 +00:00
} else if ( cb ) {
cb ( ) ;
}
} ;
2013-08-13 16:37:09 +00:00
/ * *
* The synchronous version of discoverModelProperties
* @ param { String } modelName The table / view name
* @ param { Object } options The options
* @ returns { * }
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverModelPropertiesSync = function ( modelName , options ) {
2013-06-02 06:03:25 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverModelPropertiesSync ) {
return this . connector . discoverModelPropertiesSync ( modelName , options ) ;
2013-06-02 06:03:25 +00:00
}
return null ;
} ;
2013-05-17 17:54:14 +00:00
/ * *
2013-06-20 22:50:55 +00:00
* Discover primary keys for a given owner / modelName
2013-05-17 17:54:14 +00:00
*
* Each primary key column description has the following columns :
*
2013-08-13 16:37:09 +00:00
* owner { String } => table schema ( may be null )
* tableName { String } => table name
* columnName { String } => column name
* keySeq { Number } => sequence number within primary key ( a value of 1 represents the first column of the primary key , a value of 2 would represent the second column within the primary key ) .
* pkName { String } => primary key name ( may be null )
*
* The owner , default to current user
*
* ` options `
*
* owner / schema The database owner / schema
*
* @ param { String } modelName The model name
* @ param { Object } options The options
* @ param { Function } [ cb ] The callback function
2013-05-17 17:54:14 +00:00
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverPrimaryKeys = function ( modelName , options , cb ) {
2013-05-17 17:54:14 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverPrimaryKeys ) {
this . connector . discoverPrimaryKeys ( modelName , options , cb ) ;
2013-05-17 17:54:14 +00:00
} else if ( cb ) {
cb ( ) ;
}
2013-08-13 16:37:09 +00:00
} ;
2013-05-17 17:54:14 +00:00
2013-08-13 16:37:09 +00:00
/ * *
* The synchronous version of discoverPrimaryKeys
* @ param { String } modelName The model name
* @ param { Object } options The options
* @ returns { * }
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverPrimaryKeysSync = function ( modelName , options ) {
2013-06-02 06:03:25 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverPrimaryKeysSync ) {
return this . connector . discoverPrimaryKeysSync ( modelName , options ) ;
2013-06-02 06:03:25 +00:00
}
return null ;
2013-08-13 16:37:09 +00:00
} ;
2013-06-02 06:03:25 +00:00
2013-05-17 17:54:14 +00:00
/ * *
2013-06-20 22:50:55 +00:00
* Discover foreign keys for a given owner / modelName
2013-05-17 17:54:14 +00:00
*
2013-08-13 16:37:09 +00:00
* ` foreign key description `
*
* fkOwner String => foreign key table schema ( may be null )
* fkName String => foreign key name ( may be null )
* fkTableName String => foreign key table name
* fkColumnName String => foreign key column name
* keySeq Number => sequence number within a foreign key ( a value of 1 represents the first column of the foreign key , a value of 2 would represent the second column within the foreign key ) .
* pkOwner String => primary key table schema being imported ( may be null )
* pkName String => primary key name ( may be null )
* pkTableName String => primary key table name being imported
* pkColumnName String => primary key column name being imported
*
* ` options `
*
* owner / schema The database owner / schema
2013-05-17 17:54:14 +00:00
*
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
* @ param { Object } options The options
* @ param { Function } [ cb ] The callback function
2013-06-20 22:50:55 +00:00
*
2013-05-17 17:54:14 +00:00
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverForeignKeys = function ( modelName , options , cb ) {
2013-05-17 17:54:14 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverForeignKeys ) {
this . connector . discoverForeignKeys ( modelName , options , cb ) ;
2013-05-17 17:54:14 +00:00
} else if ( cb ) {
cb ( ) ;
}
2013-08-13 16:37:09 +00:00
} ;
2013-05-17 17:54:14 +00:00
2013-08-13 16:37:09 +00:00
/ * *
* The synchronous version of discoverForeignKeys
*
* @ param { String } modelName The model name
* @ param { Object } options The options
* @ returns { * }
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverForeignKeysSync = function ( modelName , options ) {
2013-06-02 06:03:25 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverForeignKeysSync ) {
return this . connector . discoverForeignKeysSync ( modelName , options ) ;
2013-06-02 06:03:25 +00:00
}
return null ;
}
2013-06-03 06:00:11 +00:00
/ * *
* Retrieves a description of the foreign key columns that reference the given table ' s primary key columns ( the foreign keys exported by a table ) .
* They are ordered by fkTableOwner , fkTableName , and keySeq .
*
2013-08-13 16:37:09 +00:00
* ` foreign key description `
*
* fkOwner { String } => foreign key table schema ( may be null )
* fkName { String } => foreign key name ( may be null )
* fkTableName { String } => foreign key table name
* fkColumnName { String } => foreign key column name
* keySeq { Number } => sequence number within a foreign key ( a value of 1 represents the first column of the foreign key , a value of 2 would represent the second column within the foreign key ) .
* pkOwner { String } => primary key table schema being imported ( may be null )
* pkName { String } => primary key name ( may be null )
* pkTableName { String } => primary key table name being imported
* pkColumnName { String } => primary key column name being imported
2013-06-03 06:00:11 +00:00
*
2013-08-13 16:37:09 +00:00
* ` options `
2013-06-20 22:50:55 +00:00
*
2013-08-13 16:37:09 +00:00
* owner / schema The database owner / schema
*
* @ param { String } modelName The model name
* @ param { Object } options The options
* @ param { Function } [ cb ] The callback function
2013-06-03 06:00:11 +00:00
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverExportedForeignKeys = function ( modelName , options , cb ) {
2013-06-03 06:00:11 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverExportedForeignKeys ) {
this . connector . discoverExportedForeignKeys ( modelName , options , cb ) ;
2013-06-03 06:00:11 +00:00
} else if ( cb ) {
cb ( ) ;
}
2013-08-13 16:37:09 +00:00
} ;
2013-06-03 06:00:11 +00:00
2013-08-13 16:37:09 +00:00
/ * *
* The synchronous version of discoverExportedForeignKeys
* @ param { String } modelName The model name
* @ param { Object } options The options
* @ returns { * }
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverExportedForeignKeysSync = function ( modelName , options ) {
2013-06-03 06:00:11 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . discoverExportedForeignKeysSync ) {
return this . connector . discoverExportedForeignKeysSync ( modelName , options ) ;
2013-06-03 06:00:11 +00:00
}
return null ;
}
2013-05-21 21:43:25 +00:00
function capitalize ( str ) {
if ( ! str ) {
return str ;
}
2013-05-21 21:54:14 +00:00
return str . charAt ( 0 ) . toUpperCase ( ) + ( ( str . length > 1 ) ? str . slice ( 1 ) . toLowerCase ( ) : '' ) ;
2013-05-21 21:43:25 +00:00
}
function fromDBName ( dbName , camelCase ) {
if ( ! dbName ) {
return dbName ;
}
var parts = dbName . split ( /-|_/ ) ;
parts [ 0 ] = camelCase ? parts [ 0 ] . toLowerCase ( ) : capitalize ( parts [ 0 ] ) ;
for ( var i = 1 ; i < parts . length ; i ++ ) {
parts [ i ] = capitalize ( parts [ i ] ) ;
}
return parts . join ( '' ) ;
}
2013-08-13 16:37:09 +00:00
/ * *
* Discover one schema from the given model without following the associations
*
* @ param { String } modelName The model name
* @ param { Object } [ options ] The options
* @ param { Function } [ cb ] The callback function
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverSchema = function ( modelName , options , cb ) {
options = options || { } ;
if ( ! cb && 'function' === typeof options ) {
cb = options ;
options = { } ;
}
options . visited = { } ;
options . associations = false ;
this . discoverSchemas ( modelName , options , function ( err , schemas ) {
2013-05-31 06:13:04 +00:00
if ( err ) {
cb && cb ( err , schemas ) ;
return ;
}
for ( var s in schemas ) {
cb && cb ( null , schemas [ s ] ) ;
return ;
}
} ) ;
}
2013-06-02 06:03:25 +00:00
2013-05-21 21:25:23 +00:00
/ * *
2013-07-23 19:16:12 +00:00
* Discover schema from a given modelName / view
2013-06-20 22:50:55 +00:00
*
2013-08-13 16:37:09 +00:00
* ` options `
*
* { String } owner / schema - The database owner / schema name
* { Boolean } associations - If relations ( primary key / foreign key ) are navigated
* { Boolean } all - If all owners are included
* { Boolean } views - If views are included
*
* @ param { String } modelName The model name
* @ param { Object } [ options ] The options
* @ param { Function } [ cb ] The callback function
2013-05-21 21:25:23 +00:00
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverSchemas = function ( modelName , options , cb ) {
options = options || { } ;
if ( ! cb && 'function' === typeof options ) {
cb = options ;
options = { } ;
}
2013-05-31 00:23:31 +00:00
var self = this ;
2013-07-23 19:16:12 +00:00
var schemaName = this . name || this . connector . name ;
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
var tasks = [
2013-06-20 22:50:55 +00:00
this . discoverModelProperties . bind ( this , modelName , options ) ,
this . discoverPrimaryKeys . bind ( this , modelName , options ) ] ;
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
if ( options . associations ) {
2013-06-20 22:50:55 +00:00
tasks . push ( this . discoverForeignKeys . bind ( this , modelName , options ) ) ;
2013-05-31 06:13:04 +00:00
}
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
async . parallel ( tasks , function ( err , results ) {
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
if ( err ) {
cb && cb ( err ) ;
return ;
}
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
var columns = results [ 0 ] ;
2013-06-20 22:50:55 +00:00
if ( ! columns || columns . length === 0 ) {
2013-05-31 06:13:04 +00:00
cb && cb ( ) ;
return ;
}
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
// Handle primary keys
var primaryKeys = results [ 1 ] ;
var pks = { } ;
primaryKeys . forEach ( function ( pk ) {
pks [ pk . columnName ] = pk . keySeq ;
} ) ;
if ( self . settings . debug ) {
console . log ( 'Primary keys: ' , pks ) ;
}
2013-05-22 19:17:14 +00:00
2013-07-23 19:16:12 +00:00
var schema = {
2013-06-20 22:50:55 +00:00
name : fromDBName ( modelName , false ) ,
2013-05-31 06:13:04 +00:00
options : {
idInjection : false // DO NOT add id property
} ,
properties : {
2013-05-21 21:25:23 +00:00
}
2013-05-31 06:13:04 +00:00
} ;
2013-07-23 19:16:12 +00:00
schema . options [ schemaName ] = {
schema : columns [ 0 ] . owner ,
2013-06-20 22:50:55 +00:00
table : modelName
2013-05-31 06:13:04 +00:00
} ;
columns . forEach ( function ( item ) {
var i = item ;
var propName = fromDBName ( item . columnName , true ) ;
2013-07-23 19:16:12 +00:00
schema . properties [ propName ] = {
2013-05-31 06:13:04 +00:00
type : item . type ,
required : ( item . nullable === 'N' ) ,
2013-07-25 22:05:49 +00:00
length : item . dataLength ,
precision : item . dataPrecision ,
scale : item . dataScale
2013-05-21 21:25:23 +00:00
} ;
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
if ( pks [ item . columnName ] ) {
2013-07-23 19:16:12 +00:00
schema . properties [ propName ] . id = pks [ item . columnName ] ;
2013-05-31 06:13:04 +00:00
}
2013-07-23 19:16:12 +00:00
schema . properties [ propName ] [ schemaName ] = {
2013-05-31 06:13:04 +00:00
columnName : i . columnName ,
dataType : i . dataType ,
dataLength : i . dataLength ,
2013-07-25 22:05:49 +00:00
dataPrecision : item . dataPrecision ,
dataScale : item . dataScale ,
2013-05-31 06:13:04 +00:00
nullable : i . nullable
2013-05-21 21:25:23 +00:00
} ;
2013-05-31 06:13:04 +00:00
} ) ;
2013-05-21 21:25:23 +00:00
2013-06-20 22:50:55 +00:00
// Add current modelName to the visited tables
2013-05-31 06:13:04 +00:00
options . visited = options . visited || { } ;
2013-06-20 22:50:55 +00:00
var schemaKey = columns [ 0 ] . owner + '.' + modelName ;
2013-05-31 06:13:04 +00:00
if ( ! options . visited . hasOwnProperty ( schemaKey ) ) {
if ( self . settings . debug ) {
2013-07-23 19:16:12 +00:00
console . log ( 'Adding schema for ' + schemaKey ) ;
2013-05-31 06:13:04 +00:00
}
2013-07-23 19:16:12 +00:00
options . visited [ schemaKey ] = schema ;
2013-05-31 06:13:04 +00:00
}
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
var otherTables = { } ;
if ( options . associations ) {
// Handle foreign keys
var fks = { } ;
var foreignKeys = results [ 2 ] ;
foreignKeys . forEach ( function ( fk ) {
var fkInfo = {
keySeq : fk . keySeq ,
owner : fk . pkOwner ,
tableName : fk . pkTableName ,
columnName : fk . pkColumnName
2013-05-22 17:41:08 +00:00
} ;
2013-05-31 06:13:04 +00:00
if ( fks [ fk . fkName ] ) {
fks [ fk . fkName ] . push ( fkInfo ) ;
} else {
fks [ fk . fkName ] = [ fkInfo ] ;
}
} ) ;
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
if ( self . settings . debug ) {
console . log ( 'Foreign keys: ' , fks ) ;
}
foreignKeys . forEach ( function ( fk ) {
2013-05-31 17:25:11 +00:00
var propName = fromDBName ( fk . pkTableName , true ) ;
2013-07-23 19:16:12 +00:00
schema . properties [ propName ] = {
2013-05-31 17:25:11 +00:00
type : fromDBName ( fk . pkTableName , false ) ,
association : {
type : 'belongsTo' ,
foreignKey : fromDBName ( fk . pkColumnName , true )
}
} ;
2013-05-31 06:13:04 +00:00
var key = fk . pkOwner + '.' + fk . pkTableName ;
if ( ! options . visited . hasOwnProperty ( key ) && ! otherTables . hasOwnProperty ( key ) ) {
otherTables [ key ] = { owner : fk . pkOwner , tableName : fk . pkTableName } ;
2013-05-22 17:41:08 +00:00
}
} ) ;
2013-05-31 06:13:04 +00:00
}
2013-05-22 17:41:08 +00:00
2013-05-31 06:13:04 +00:00
if ( Object . keys ( otherTables ) . length === 0 ) {
cb && cb ( null , options . visited ) ;
} else {
var moreTasks = [ ] ;
for ( var t in otherTables ) {
if ( self . settings . debug ) {
2013-07-23 19:16:12 +00:00
console . log ( 'Discovering related schema for ' + schemaKey ) ;
2013-05-31 06:13:04 +00:00
}
2013-06-20 22:50:55 +00:00
var newOptions = { } ;
for ( var key in options ) {
newOptions [ key ] = options [ key ] ;
}
newOptions . owner = otherTables [ t ] . owner ;
moreTasks . push ( DataSource . prototype . discoverSchemas . bind ( self , otherTables [ t ] . tableName , newOptions ) ) ;
2013-05-31 06:13:04 +00:00
}
async . parallel ( moreTasks , function ( err , results ) {
var result = results && results [ 0 ] ;
cb && cb ( err , result ) ;
} ) ;
}
} ) ;
2013-08-13 16:37:09 +00:00
} ;
2013-05-21 21:25:23 +00:00
2013-06-02 06:03:25 +00:00
/ * *
2013-08-13 16:37:09 +00:00
* Discover schema from a given table / view synchronously
*
* ` options `
*
* { String } owner / schema - The database owner / schema name
* { Boolean } associations - If relations ( primary key / foreign key ) are navigated
* { Boolean } all - If all owners are included
* { Boolean } views - If views are included
2013-06-20 22:50:55 +00:00
*
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
* @ param { Object } [ options ] The options
2013-06-02 06:03:25 +00:00
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverSchemasSync = function ( modelName , options ) {
2013-06-02 06:03:25 +00:00
var self = this ;
2013-07-23 19:16:12 +00:00
var schemaName = this . name || this . connector . name ;
2013-06-02 06:03:25 +00:00
2013-06-20 22:50:55 +00:00
var columns = this . discoverModelPropertiesSync ( modelName , options ) ;
if ( ! columns || columns . length === 0 ) {
2013-06-02 06:03:25 +00:00
return [ ] ;
}
// Handle primary keys
2013-06-20 22:50:55 +00:00
var primaryKeys = this . discoverPrimaryKeysSync ( modelName , options ) ;
2013-06-02 06:03:25 +00:00
var pks = { } ;
primaryKeys . forEach ( function ( pk ) {
pks [ pk . columnName ] = pk . keySeq ;
} ) ;
if ( self . settings . debug ) {
console . log ( 'Primary keys: ' , pks ) ;
}
2013-07-23 19:16:12 +00:00
var schema = {
2013-06-20 22:50:55 +00:00
name : fromDBName ( modelName , false ) ,
2013-06-02 06:03:25 +00:00
options : {
idInjection : false // DO NOT add id property
} ,
properties : {
}
} ;
2013-07-23 19:16:12 +00:00
schema . options [ schemaName ] = {
schema : columns . length > 0 && columns [ 0 ] . owner ,
2013-06-20 22:50:55 +00:00
table : modelName
2013-06-02 06:03:25 +00:00
} ;
columns . forEach ( function ( item ) {
var i = item ;
var propName = fromDBName ( item . columnName , true ) ;
2013-07-23 19:16:12 +00:00
schema . properties [ propName ] = {
2013-06-02 06:03:25 +00:00
type : item . type ,
required : ( item . nullable === 'N' ) ,
2013-07-25 22:20:19 +00:00
length : item . dataLength ,
precision : item . dataPrecision ,
scale : item . dataScale
2013-06-02 06:03:25 +00:00
} ;
if ( pks [ item . columnName ] ) {
2013-07-23 19:16:12 +00:00
schema . properties [ propName ] . id = pks [ item . columnName ] ;
2013-06-02 06:03:25 +00:00
}
2013-07-23 19:16:12 +00:00
schema . properties [ propName ] [ schemaName ] = {
2013-06-02 06:03:25 +00:00
columnName : i . columnName ,
dataType : i . dataType ,
dataLength : i . dataLength ,
2013-07-25 22:20:19 +00:00
dataPrecision : item . dataPrecision ,
dataScale : item . dataScale ,
2013-06-02 06:03:25 +00:00
nullable : i . nullable
} ;
} ) ;
2013-06-20 22:50:55 +00:00
// Add current modelName to the visited tables
2013-06-02 06:03:25 +00:00
options . visited = options . visited || { } ;
2013-06-20 22:50:55 +00:00
var schemaKey = columns [ 0 ] . owner + '.' + modelName ;
2013-06-02 06:03:25 +00:00
if ( ! options . visited . hasOwnProperty ( schemaKey ) ) {
if ( self . settings . debug ) {
2013-07-23 19:16:12 +00:00
console . log ( 'Adding schema for ' + schemaKey ) ;
2013-06-02 06:03:25 +00:00
}
2013-07-23 19:16:12 +00:00
options . visited [ schemaKey ] = schema ;
2013-06-02 06:03:25 +00:00
}
var otherTables = { } ;
if ( options . associations ) {
// Handle foreign keys
var fks = { } ;
2013-06-20 22:50:55 +00:00
var foreignKeys = this . discoverForeignKeysSync ( modelName , options ) ;
2013-06-02 06:03:25 +00:00
foreignKeys . forEach ( function ( fk ) {
var fkInfo = {
keySeq : fk . keySeq ,
owner : fk . pkOwner ,
tableName : fk . pkTableName ,
columnName : fk . pkColumnName
} ;
if ( fks [ fk . fkName ] ) {
fks [ fk . fkName ] . push ( fkInfo ) ;
} else {
fks [ fk . fkName ] = [ fkInfo ] ;
}
} ) ;
if ( self . settings . debug ) {
console . log ( 'Foreign keys: ' , fks ) ;
}
foreignKeys . forEach ( function ( fk ) {
var propName = fromDBName ( fk . pkTableName , true ) ;
2013-07-23 19:16:12 +00:00
schema . properties [ propName ] = {
2013-06-02 06:03:25 +00:00
type : fromDBName ( fk . pkTableName , false ) ,
association : {
type : 'belongsTo' ,
foreignKey : fromDBName ( fk . pkColumnName , true )
}
} ;
var key = fk . pkOwner + '.' + fk . pkTableName ;
if ( ! options . visited . hasOwnProperty ( key ) && ! otherTables . hasOwnProperty ( key ) ) {
otherTables [ key ] = { owner : fk . pkOwner , tableName : fk . pkTableName } ;
}
} ) ;
}
if ( Object . keys ( otherTables ) . length === 0 ) {
return options . visited ;
} else {
var moreTasks = [ ] ;
for ( var t in otherTables ) {
if ( self . settings . debug ) {
2013-07-23 19:16:12 +00:00
console . log ( 'Discovering related schema for ' + schemaKey ) ;
2013-06-02 06:03:25 +00:00
}
2013-06-20 22:50:55 +00:00
var newOptions = { } ;
for ( var key in options ) {
newOptions [ key ] = options [ key ] ;
}
newOptions . owner = otherTables [ t ] . owner ;
self . discoverSchemasSync ( otherTables [ t ] . tableName , newOptions ) ;
2013-06-02 06:03:25 +00:00
}
return options . visited ;
}
2013-08-13 16:37:09 +00:00
} ;
2013-06-02 06:03:25 +00:00
/ * *
2013-06-20 22:50:55 +00:00
* Discover and build models from the given owner / modelName
*
2013-08-13 16:37:09 +00:00
* ` options `
*
* { String } owner / schema - The database owner / schema name
* { Boolean } associations - If relations ( primary key / foreign key ) are navigated
* { Boolean } all - If all owners are included
* { Boolean } views - If views are included
*
* @ param { String } modelName The model name
* @ param { Object } [ options ] The options
* @ param { Function } [ cb ] The callback function
2013-06-02 06:03:25 +00:00
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverAndBuildModels = function ( modelName , options , cb ) {
2013-06-03 15:51:17 +00:00
var self = this ;
2013-06-20 22:50:55 +00:00
this . discoverSchemas ( modelName , options , function ( err , schemas ) {
2013-06-02 06:03:25 +00:00
if ( err ) {
cb && cb ( err , schemas ) ;
return ;
}
var schemaList = [ ] ;
for ( var s in schemas ) {
2013-07-23 19:16:12 +00:00
var schema = schemas [ s ] ;
schemaList . push ( schema ) ;
2013-06-02 06:03:25 +00:00
}
2013-06-03 15:51:17 +00:00
var models = self . buildModels ( schemaList ) ;
2013-06-02 06:03:25 +00:00
cb && cb ( err , models ) ;
} ) ;
2013-08-13 16:37:09 +00:00
} ;
2013-06-02 06:03:25 +00:00
/ * *
2013-06-20 22:50:55 +00:00
* Discover and build models from the given owner / modelName synchronously
*
2013-08-13 16:37:09 +00:00
* ` options `
*
* { String } owner / schema - The database owner / schema name
* { Boolean } associations - If relations ( primary key / foreign key ) are navigated
* { Boolean } all - If all owners are included
* { Boolean } views - If views are included
*
* @ param { String } modelName The model name
* @ param { Object } [ options ] The options
2013-06-02 06:03:25 +00:00
* /
2013-06-20 22:50:55 +00:00
DataSource . prototype . discoverAndBuildModelsSync = function ( modelName , options ) {
var schemas = this . discoverSchemasSync ( modelName , options ) ;
2013-06-02 06:03:25 +00:00
var schemaList = [ ] ;
for ( var s in schemas ) {
2013-07-23 19:16:12 +00:00
var schema = schemas [ s ] ;
schemaList . push ( schema ) ;
2013-06-02 06:03:25 +00:00
}
var models = this . buildModels ( schemaList ) ;
return models ;
2013-08-13 16:37:09 +00:00
} ;
2013-06-02 06:03:25 +00:00
2013-05-17 17:54:14 +00:00
/ * *
* Check whether migrations needed
2013-07-23 21:40:44 +00:00
* This method make sense only for sql connectors .
2013-08-13 16:37:09 +00:00
* @ param { String [ ] } [ models ] A model name or an array of model names . If not present , apply to all models
2013-05-17 17:54:14 +00:00
* /
2013-08-13 16:37:09 +00:00
DataSource . prototype . isActual = function ( models , cb ) {
2013-05-17 17:54:14 +00:00
this . freeze ( ) ;
2013-07-23 18:16:43 +00:00
if ( this . connector . isActual ) {
2013-08-13 16:37:09 +00:00
this . connector . isActual ( models , cb ) ;
} else {
if ( ( ! cb ) && ( 'function' === typeof models ) ) {
cb = models ;
models = undefined ;
}
if ( cb ) {
process . nextTick ( function ( ) {
cb ( null , true ) ;
} ) ;
}
2013-05-17 17:54:14 +00:00
}
} ;
/ * *
* Log benchmarked message . Do not redefine this method , if you need to grab
2013-07-23 18:16:43 +00:00
* chema logs , use ` dataSource.on('log', ...) ` emitter event
2013-05-17 17:54:14 +00:00
*
2013-07-23 21:40:44 +00:00
* @ private used by connectors
2013-05-17 17:54:14 +00:00
* /
DataSource . prototype . log = function ( sql , t ) {
this . emit ( 'log' , sql , t ) ;
} ;
/ * *
2013-07-23 18:16:43 +00:00
* Freeze dataSource . Behavior depends on connector
2013-05-17 17:54:14 +00:00
* /
DataSource . prototype . freeze = function freeze ( ) {
2013-07-23 19:05:08 +00:00
if ( this . connector . freezeDataSource ) {
this . connector . freezeDataSource ( ) ;
}
2013-07-23 18:16:43 +00:00
if ( this . connector . freezeSchema ) {
this . connector . freezeSchema ( ) ;
2013-05-17 17:54:14 +00:00
}
}
/ * *
* Return table name for specified ` modelName `
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
2013-05-17 17:54:14 +00:00
* /
DataSource . prototype . tableName = function ( modelName ) {
2013-10-03 00:20:54 +00:00
return this . definitions [ modelName ] . tableName ( this . connector . name ) ;
2013-05-22 17:41:08 +00:00
} ;
/ * *
* Return column name for specified modelName and propertyName
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
* @ param propertyName The property name
2013-05-22 17:41:08 +00:00
* @ returns { String } columnName
* /
DataSource . prototype . columnName = function ( modelName , propertyName ) {
2013-10-03 21:49:03 +00:00
return this . definitions [ modelName ] . columnName ( this . connector . name , propertyName ) ;
2013-05-22 17:41:08 +00:00
} ;
2013-05-29 17:03:01 +00:00
/ * *
* Return column metadata for specified modelName and propertyName
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
* @ param propertyName The property name
2013-05-29 17:03:01 +00:00
* @ returns { Object } column metadata
* /
DataSource . prototype . columnMetadata = function ( modelName , propertyName ) {
2013-10-03 00:20:54 +00:00
return this . definitions [ modelName ] . columnMetadata ( this . connector . name , propertyName ) ;
2013-05-29 17:03:01 +00:00
} ;
2013-05-22 17:41:08 +00:00
/ * *
* Return column names for specified modelName
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
* @ returns { String [ ] } column names
2013-05-22 17:41:08 +00:00
* /
DataSource . prototype . columnNames = function ( modelName ) {
2013-10-03 00:20:54 +00:00
return this . definitions [ modelName ] . columnNames ( this . connector . name ) ;
2013-05-17 17:54:14 +00:00
} ;
2013-05-22 17:41:08 +00:00
/ * *
* Find the ID column name
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
2013-05-22 17:41:08 +00:00
* @ returns { String } columnName for ID
* /
2013-05-23 01:04:05 +00:00
DataSource . prototype . idColumnName = function ( modelName ) {
2013-10-03 00:20:54 +00:00
return this . definitions [ modelName ] . idColumnName ( this . connector . name ) ;
2013-08-13 16:37:09 +00:00
} ;
2013-05-23 01:04:05 +00:00
/ * *
2013-05-30 23:06:04 +00:00
* Find the ID property name
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
* @ returns { String } property name for ID
2013-05-23 01:04:05 +00:00
* /
DataSource . prototype . idName = function ( modelName ) {
2013-10-03 00:20:54 +00:00
if ( ! this . definitions [ modelName ] . idName ) {
console . log ( this . definitions [ modelName ] ) ;
2013-05-23 17:46:01 +00:00
}
2013-10-03 00:20:54 +00:00
return this . definitions [ modelName ] . idName ( ) ;
2013-08-13 16:37:09 +00:00
} ;
2013-05-22 17:41:08 +00:00
2013-05-30 23:06:04 +00:00
/ * *
* Find the ID property names sorted by the index
2013-08-13 16:37:09 +00:00
* @ param { String } modelName The model name
* @ returns { String [ ] } property names for IDs
2013-05-30 23:06:04 +00:00
* /
DataSource . prototype . idNames = function ( modelName ) {
2013-10-03 00:20:54 +00:00
return this . definitions [ modelName ] . idNames ( ) ;
2013-08-13 16:37:09 +00:00
} ;
2013-05-30 23:06:04 +00:00
2013-05-22 17:41:08 +00:00
2013-05-17 17:54:14 +00:00
/ * *
2013-08-13 16:37:09 +00:00
* Define foreign key to another model
* @ param { String } className The model name that owns the key
2013-05-17 17:54:14 +00:00
* @ param { String } key - name of key field
2013-08-13 16:37:09 +00:00
* @ param { String } foreignClassName The foreign model name
2013-05-17 17:54:14 +00:00
* /
DataSource . prototype . defineForeignKey = function defineForeignKey ( className , key , foreignClassName ) {
// quit if key already defined
2013-10-04 22:49:13 +00:00
if ( this . definitions [ className ] . rawProperties [ key ] ) return ;
2013-05-17 17:54:14 +00:00
2013-07-23 18:16:43 +00:00
if ( this . connector . defineForeignKey ) {
2013-05-17 17:54:14 +00:00
var cb = function ( err , keyType ) {
if ( err ) throw err ;
2013-10-08 01:01:31 +00:00
// Add the foreign key property to the data source _models
this . defineProperty ( className , key , { type : keyType || Number } ) ;
2013-05-17 17:54:14 +00:00
} . bind ( this ) ;
2013-07-23 18:16:43 +00:00
switch ( this . connector . defineForeignKey . length ) {
2013-05-17 17:54:14 +00:00
case 4 :
2013-07-23 18:16:43 +00:00
this . connector . defineForeignKey ( className , key , foreignClassName , cb ) ;
2013-05-17 17:54:14 +00:00
break ;
default :
case 3 :
2013-07-23 18:16:43 +00:00
this . connector . defineForeignKey ( className , key , cb ) ;
2013-05-17 17:54:14 +00:00
break ;
}
} else {
2013-10-08 01:01:31 +00:00
// Add the foreign key property to the data source _models
this . defineProperty ( className , key , { type : Number } ) ;
2013-05-17 17:54:14 +00:00
}
2013-10-08 01:01:31 +00:00
2013-05-17 17:54:14 +00:00
} ;
/ * *
* Close database connection
2013-08-13 16:37:09 +00:00
* @ param { Fucntion } [ cb ] The callback function
2013-05-17 17:54:14 +00:00
* /
DataSource . prototype . disconnect = function disconnect ( cb ) {
2013-05-31 20:10:09 +00:00
var self = this ;
2013-07-23 18:16:43 +00:00
if ( this . connected && ( typeof this . connector . disconnect === 'function' ) ) {
this . connector . disconnect ( function ( err , result ) {
2013-05-31 20:10:09 +00:00
self . connected = false ;
cb && cb ( err , result ) ;
} ) ;
} else {
process . nextTick ( function ( ) {
cb && cb ( ) ;
} ) ;
2013-05-17 17:54:14 +00:00
}
} ;
2013-08-13 16:37:09 +00:00
/ * *
* Copy the model from Master
* @ param { Function } Master The model constructor
* @ returns { Function } The copy of the model constructor
*
* @ private
* /
2013-05-17 17:54:14 +00:00
DataSource . prototype . copyModel = function copyModel ( Master ) {
2013-07-23 18:16:43 +00:00
var dataSource = this ;
2013-05-17 17:54:14 +00:00
var className = Master . modelName ;
2013-07-23 18:16:43 +00:00
var md = Master . dataSource . definitions [ className ] ;
2013-05-17 17:54:14 +00:00
var Slave = function SlaveModel ( ) {
Master . apply ( this , [ ] . slice . call ( arguments ) ) ;
} ;
util . inherits ( Slave , Master ) ;
2013-10-02 05:14:21 +00:00
// Delegating static properties
2013-05-17 17:54:14 +00:00
Slave . _ _proto _ _ = Master ;
2013-07-23 18:16:43 +00:00
hiddenProperty ( Slave , 'dataSource' , dataSource ) ;
2013-05-17 17:54:14 +00:00
hiddenProperty ( Slave , 'modelName' , className ) ;
hiddenProperty ( Slave , 'relations' , Master . relations ) ;
2013-07-23 18:16:43 +00:00
if ( ! ( className in dataSource . models ) ) {
2013-05-17 17:54:14 +00:00
// store class in model pool
2013-07-23 18:16:43 +00:00
dataSource . models [ className ] = Slave ;
2013-10-03 00:20:54 +00:00
dataSource . definitions [ className ] = new ModelDefinition ( dataSource , md . name , md . properties , md . settings ) ;
2013-05-17 17:54:14 +00:00
2013-07-23 18:16:43 +00:00
if ( ( ! dataSource . isTransaction ) && dataSource . connector && dataSource . connector . define ) {
dataSource . connector . define ( {
2013-05-17 17:54:14 +00:00
model : Slave ,
properties : md . properties ,
settings : md . settings
} ) ;
}
}
return Slave ;
} ;
2013-08-13 16:37:09 +00:00
/ * *
*
* @ returns { EventEmitter }
* @ private
* /
2013-05-17 17:54:14 +00:00
DataSource . prototype . transaction = function ( ) {
2013-07-23 18:16:43 +00:00
var dataSource = this ;
2013-10-02 05:14:21 +00:00
var transaction = new EventEmitter ( ) ;
for ( var p in dataSource ) {
transaction [ p ] = dataSource [ p ] ;
}
2013-05-17 17:54:14 +00:00
transaction . isTransaction = true ;
2013-07-23 18:16:43 +00:00
transaction . origin = dataSource ;
transaction . name = dataSource . name ;
transaction . settings = dataSource . settings ;
2013-05-17 17:54:14 +00:00
transaction . connected = false ;
transaction . connecting = false ;
2013-07-23 18:16:43 +00:00
transaction . connector = dataSource . connector . transaction ( ) ;
2013-05-17 17:54:14 +00:00
// create blank models pool
transaction . models = { } ;
transaction . definitions = { } ;
2013-07-23 18:16:43 +00:00
for ( var i in dataSource . models ) {
dataSource . copyModel . call ( transaction , dataSource . models [ i ] ) ;
2013-05-17 17:54:14 +00:00
}
transaction . exec = function ( cb ) {
2013-07-23 18:16:43 +00:00
transaction . connector . exec ( cb ) ;
2013-05-17 17:54:14 +00:00
} ;
return transaction ;
} ;
2013-06-11 16:03:11 +00:00
/ * *
2013-08-13 16:37:09 +00:00
* Enable a data source operation to be remote .
* @ param { String } operation The operation name
2013-06-11 16:03:11 +00:00
* /
DataSource . prototype . enableRemote = function ( operation ) {
var op = this . getOperation ( operation ) ;
if ( op ) {
op . remoteEnabled = true ;
} else {
2013-06-11 18:11:10 +00:00
throw new Error ( operation + ' is not provided by the attached connector' ) ;
2013-06-11 16:03:11 +00:00
}
}
/ * *
2013-08-13 16:37:09 +00:00
* Disable a data source operation to be remote .
* @ param { String } operation The operation name
2013-06-11 16:03:11 +00:00
* /
DataSource . prototype . disableRemote = function ( operation ) {
var op = this . getOperation ( operation ) ;
if ( op ) {
op . remoteEnabled = false ;
} else {
2013-06-11 18:11:10 +00:00
throw new Error ( operation + ' is not provided by the attached connector' ) ;
2013-06-11 16:03:11 +00:00
}
}
/ * *
* Get an operation ' s metadata .
2013-08-13 16:37:09 +00:00
* @ param { String } operation The operation name
2013-06-11 16:03:11 +00:00
* /
DataSource . prototype . getOperation = function ( operation ) {
var ops = this . operations ( ) ;
2013-06-11 18:11:10 +00:00
var opKeys = Object . keys ( ops ) ;
2013-06-11 16:03:11 +00:00
2013-06-11 18:11:10 +00:00
for ( var i = 0 ; i < opKeys . length ; i ++ ) {
var op = ops [ opKeys [ i ] ] ;
2013-06-11 16:03:11 +00:00
if ( op . name === operation ) {
return op ;
}
}
}
/ * *
* Get all operations .
* /
DataSource . prototype . operations = function ( ) {
return this . _operations ;
}
/ * *
2013-08-13 16:37:09 +00:00
* Define an operation to the data source
* @ param { String } name The operation name
* @ param { Object } options The options
* @ param [ Function } fn The function
2013-06-11 16:03:11 +00:00
* /
DataSource . prototype . defineOperation = function ( name , options , fn ) {
options . fn = fn ;
2013-06-11 18:11:10 +00:00
options . name = name ;
2013-06-11 16:03:11 +00:00
this . _operations [ name ] = options ;
2013-10-02 05:14:21 +00:00
} ;
2013-06-11 16:03:11 +00:00
2013-08-13 16:37:09 +00:00
/ * *
* Check if the backend is a relational DB
* @ returns { Boolean }
* /
2013-07-15 17:38:54 +00:00
DataSource . prototype . isRelational = function ( ) {
2013-07-23 18:16:43 +00:00
return this . connector && this . connector . relational ;
2013-10-02 05:14:21 +00:00
} ;
/ * *
* Check if the data source is ready
* @ param obj
* @ param args
* @ returns { boolean }
* /
DataSource . prototype . ready = function ( obj , args ) {
2013-10-11 05:47:26 +00:00
var self = this ;
2013-10-02 05:14:21 +00:00
if ( this . connected ) {
// Connected
return false ;
}
var method = args . callee ;
// Set up a callback after the connection is established to continue the method call
2013-10-11 05:47:26 +00:00
var onConnected = null , onError = null ;
onConnected = function ( ) {
// Remove the error handler
self . removeListener ( 'error' , onError ) ;
2013-10-02 05:14:21 +00:00
method . apply ( obj , [ ] . slice . call ( args ) ) ;
2013-10-11 05:47:26 +00:00
} ;
onError = function ( err ) {
// Remove the connected listener
self . removeListener ( 'connected' , onConnected ) ;
var params = [ ] . slice . call ( args ) ;
var cb = params . pop ( ) ;
if ( typeof cb === 'function' ) {
cb ( err ) ;
}
} ;
this . once ( 'connected' , onConnected ) ;
this . once ( 'error' , onError ) ;
2013-10-02 05:14:21 +00:00
if ( ! this . connecting ) {
this . connect ( ) ;
}
return true ;
} ;
2013-07-13 02:10:42 +00:00
2013-05-17 17:54:14 +00:00
/ * *
2013-08-13 16:37:09 +00:00
* Define a hidden property
* @ param { Object } obj The property owner
* @ param { String } key The property name
* @ param { Mixed } value The default value
2013-05-17 17:54:14 +00:00
* /
2013-08-13 16:37:09 +00:00
function hiddenProperty ( obj , key , value ) {
Object . defineProperty ( obj , key , {
2013-05-17 17:54:14 +00:00
writable : false ,
enumerable : false ,
configurable : false ,
value : value
} ) ;
}
/ * *
* Define readonly property on object
*
2013-08-13 16:37:09 +00:00
* @ param { Object } obj The property owner
* @ param { String } key The property name
* @ param { Mixed } value The default value
2013-05-17 17:54:14 +00:00
* /
function defineReadonlyProp ( obj , key , value ) {
Object . defineProperty ( obj , key , {
writable : false ,
enumerable : true ,
configurable : true ,
value : value
} ) ;
}