2013-07-21 17:36:26 +00:00
module . exports = mixinDiscovery ;
function mixinDiscovery ( MySQL ) {
var async = require ( 'async' ) ;
2013-07-22 05:57:19 +00:00
function paginateSQL ( sql , orderBy , options ) {
2013-07-21 17:36:26 +00:00
options = options || { } ;
var limit = '' ;
if ( options . offset || options . skip || options . limit ) {
2013-07-22 05:57:19 +00:00
limit = ' LIMIT ' + ( options . offset || options . skip || 0 ) ; // Offset starts from 0
2013-07-21 17:36:26 +00:00
if ( options . limit ) {
2013-07-22 05:57:19 +00:00
limit = limit + ',' + options . limit ;
2013-07-21 17:36:26 +00:00
}
2013-07-21 06:38:40 +00:00
}
2013-07-22 05:57:19 +00:00
if ( ! orderBy ) {
sql += ' ORDER BY ' + orderBy ;
}
2013-07-21 17:36:26 +00:00
return sql + limit ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Build sql for listing tables
* @ param options { all : for all owners , owner : for a given owner }
* @ returns { string } The sql statement
* /
function queryTables ( options ) {
var sqlTables = null ;
2013-07-21 06:38:40 +00:00
var owner = options . owner || options . schema ;
if ( options . all && ! owner ) {
2013-07-21 17:36:26 +00:00
sqlTables = paginateSQL ( 'SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"'
+ ' FROM information_schema.tables' , 'table_schema, table_name' , options ) ;
2013-07-21 06:38:40 +00:00
} else if ( owner ) {
2013-07-21 17:36:26 +00:00
sqlTables = paginateSQL ( 'SELECT \'table\' AS "type", table_name AS "name", table_schema AS "owner"'
+ ' FROM information_schema.tables WHERE table_schema=\'' + owner + '\'' , 'table_schema, table_name' , options ) ;
2013-07-21 06:38:40 +00:00
} else {
2013-07-21 17:36:26 +00:00
sqlTables = paginateSQL ( 'SELECT \'table\' AS "type", table_name AS "name",'
+ ' SUBSTRING_INDEX(USER(), \'@\', 1) AS "owner" FROM information_schema.tables' ,
2013-07-21 06:38:40 +00:00
'table_name' , options ) ;
}
2013-07-21 17:36:26 +00:00
return sqlTables ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Build sql for listing views
* @ param options { all : for all owners , owner : for a given owner }
* @ returns { string } The sql statement
* /
function queryViews ( options ) {
var sqlViews = null ;
if ( options . views ) {
var owner = options . owner || options . schema ;
if ( options . all && ! owner ) {
sqlViews = paginateSQL ( 'SELECT \'view\' AS "type", table_name AS "name",'
+ ' table_schema AS "owner" FROM information_schema.views' ,
'table_schema, table_name' , options ) ;
} else if ( owner ) {
sqlViews = paginateSQL ( 'SELECT \'view\' AS "type", table_name AS "name",'
+ ' table_schema AS "owner" FROM information_schema.views WHERE table_schema=\'' + owner + '\'' ,
2013-07-22 05:57:19 +00:00
'table_schema, table_name' , options ) ;
2013-07-21 17:36:26 +00:00
} else {
sqlViews = paginateSQL ( 'SELECT \'view\' AS "type", table_name AS "name",'
+ ' SUBSTRING_INDEX(USER(), \'@\', 1) AS "owner" FROM information_schema.views' ,
'table_name' , options ) ;
}
}
return sqlViews ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Discover the models
* /
MySQL . prototype . discoverModelDefinitions = function ( options , cb ) {
if ( ! cb && typeof options === 'function' ) {
cb = options ;
options = { } ;
}
options = options || { } ;
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
var self = this ;
var calls = [ function ( callback ) {
self . query ( queryTables ( options ) , callback ) ;
} ] ;
if ( options . views ) {
calls . push ( function ( callback ) {
self . query ( queryViews ( options ) , callback ) ;
} ) ;
}
async . parallel ( calls , function ( err , data ) {
if ( err ) {
cb ( err , data ) ;
} else {
var merged = [ ] ;
2013-07-21 06:38:40 +00:00
merged = merged . concat ( data . shift ( ) ) ;
2013-07-21 17:36:26 +00:00
if ( data . length ) {
merged = merged . concat ( data . shift ( ) ) ;
}
cb ( err , merged ) ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
} ) ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Discover the tables / views synchronously
* /
MySQL . prototype . discoverModelDefinitionsSync = function ( options ) {
options = options || { } ;
var sqlTables = queryTables ( options ) ;
var tables = this . querySync ( sqlTables ) ;
var sqlViews = queryViews ( options ) ;
if ( sqlViews ) {
var views = this . querySync ( sqlViews ) ;
tables = tables . concat ( views ) ;
}
return tables ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Normalize the arguments
* @ param table string , required
* @ param options object , optional
* @ param cb function , optional
* /
function getArgs ( table , options , cb ) {
if ( 'string' !== typeof table || ! table ) {
throw new Error ( 'table is a required string argument: ' + table ) ;
}
options = options || { } ;
if ( ! cb && 'function' === typeof options ) {
cb = options ;
options = { } ;
}
if ( typeof options !== 'object' ) {
throw new Error ( 'options must be an object: ' + options ) ;
}
return {
owner : options . owner || options . schema ,
table : table ,
options : options ,
cb : cb
} ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Build the sql statement to query columns for a given table
* @ param owner
* @ param table
* @ returns { String } The sql statement
* /
function queryColumns ( owner , table ) {
var sql = null ;
if ( owner ) {
sql = paginateSQL ( 'SELECT table_schema AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",'
2013-07-25 22:03:03 +00:00
+ ' character_octet_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"'
2013-07-21 17:36:26 +00:00
+ ' FROM information_schema.columns'
+ ' WHERE table_schema=\'' + owner + '\''
+ ( table ? ' AND table_name=\'' + table + '\'' : '' ) ,
'table_name, ordinal_position' , { } ) ;
2013-07-21 06:38:40 +00:00
} else {
2013-07-21 17:36:26 +00:00
sql = paginateSQL ( 'SELECT SUBSTRING_INDEX(USER(), \'@\', 1) AS "owner", table_name AS "tableName", column_name AS "columnName", data_type AS "dataType",'
2013-07-25 22:03:03 +00:00
+ ' character_octet_length AS "dataLength", numeric_precision AS "dataPrecision", numeric_scale AS "dataScale", is_nullable AS "nullable"'
2013-07-21 17:36:26 +00:00
+ ' FROM information_schema.columns'
+ ( table ? ' WHERE table_name=\'' + table + '\'' : '' ) ,
'table_name, ordinal_position' , { } ) ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
return sql ;
}
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
MySQL . prototype . discoverModelProperties = function ( table , options , cb ) {
var args = getArgs ( table , options , cb ) ;
var owner = args . owner ;
table = args . table ;
options = args . options ;
cb = args . cb ;
var sql = queryColumns ( owner , table ) ;
var callback = function ( err , results ) {
if ( err ) {
cb ( err , results ) ;
} else {
results . map ( function ( r ) {
r . type = mysqlDataTypeToJSONType ( r . dataType , r . dataLength ) ;
} ) ;
cb ( err , results ) ;
}
} ;
this . query ( sql , callback ) ;
}
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
MySQL . prototype . discoverModelPropertiesSync = function ( table , options ) {
var args = getArgs ( table , options ) ;
var owner = args . owner ;
table = args . table ;
options = args . options ;
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
var sql = queryColumns ( owner , table ) ;
var results = this . querySync ( sql ) ;
results . map ( function ( r ) {
r . type = mysqlDataTypeToJSONType ( r . dataType , r . dataLength ) ;
} ) ;
return results ;
}
/ * *
* Build the sql statement for querying primary keys of a given table
* @ param owner
* @ param table
* @ returns { string }
* /
2013-07-21 06:38:40 +00:00
// http://docs.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html#getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String)
2013-07-21 17:36:26 +00:00
function queryForPrimaryKeys ( owner , table ) {
var sql = 'SELECT table_schema AS "owner", '
+ 'table_name AS "tableName", column_name AS "columnName", ordinal_position AS "keySeq", constraint_name AS "pkName" FROM'
+ ' information_schema.key_column_usage'
+ ' WHERE constraint_name=\'PRIMARY\'' ;
if ( owner ) {
sql += ' AND table_schema=\'' + owner + '\'' ;
}
if ( table ) {
sql += ' AND table_name=\'' + table + '\'' ;
}
sql += ' ORDER BY table_schema, constraint_name, table_name, ordinal_position' ;
return sql ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Discover primary keys for a given table
* @ param table
* @ param options
* @ param cb
* /
MySQL . prototype . discoverPrimaryKeys = function ( table , options , cb ) {
var args = getArgs ( table , options , cb ) ;
var owner = args . owner ;
table = args . table ;
options = args . options ;
cb = args . cb ;
var sql = queryForPrimaryKeys ( owner , table ) ;
this . query ( sql , cb ) ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Discover primary keys synchronously for a given table
* @ param table
* @ param options
* @ returns { * }
* /
MySQL . prototype . discoverPrimaryKeysSync = function ( table , options ) {
var args = getArgs ( table , options ) ;
var owner = args . owner ;
table = args . table ;
options = args . options ;
var sql = queryForPrimaryKeys ( owner , table ) ;
return this . querySync ( sql ) ;
}
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
/ * *
* Build the sql statement for querying foreign keys of a given table
* @ param owner
* @ param table
* @ returns { string }
* /
function queryForeignKeys ( owner , table ) {
var sql =
'SELECT table_schema AS "fkOwner", constraint_name AS "fkName", table_name AS "fkTableName",'
+ ' column_name AS "fkColumnName", ordinal_position AS "keySeq",'
+ ' referenced_table_schema AS "pkOwner", \'PRIMARY\' AS "pkName", '
+ ' referenced_table_name AS "pkTableName", referenced_column_name AS "pkColumnName"'
+ ' FROM information_schema.key_column_usage'
+ ' WHERE'
+ ' constraint_name!=\'PRIMARY\' and POSITION_IN_UNIQUE_CONSTRAINT IS NOT NULL' ;
if ( owner ) {
sql += ' AND table_schema=\'' + owner + '\'' ;
}
if ( table ) {
sql += ' AND table_name=\'' + table + '\'' ;
}
return sql ;
}
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
/ * *
* Discover foreign kets for a given table
* @ param table
* @ param options
* @ param cb
* /
MySQL . prototype . discoverForeignKeys = function ( table , options , cb ) {
var args = getArgs ( table , options , cb ) ;
var owner = args . owner ;
table = args . table ;
options = args . options ;
cb = args . cb ;
var sql = queryForeignKeys ( owner , table ) ;
this . query ( sql , cb ) ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Discover foreign keys synchronously for a given table
* @ param owner
* @ param options
* @ returns { * }
* /
MySQL . prototype . discoverForeignKeysSync = function ( table , options ) {
var args = getArgs ( table , options ) ;
var owner = args . owner ;
table = args . table ;
options = args . options ;
var sql = queryForeignKeys ( owner , table ) ;
return this . querySync ( sql ) ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +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 .
* @ param owner
* @ param table
* @ returns { string }
* /
function queryExportedForeignKeys ( owner , table ) {
var sql = 'SELECT a.constraint_name AS "fkName", a.owner AS "fkOwner", a.table_name AS "fkTableName",'
+ ' a.column_name AS "fkColumnName", a.position AS "keySeq",'
+ ' jcol.constraint_name AS "pkName", jcol.owner AS "pkOwner",'
+ ' jcol.table_name AS "pkTableName", jcol.column_name AS "pkColumnName"'
+ ' FROM'
+ ' (SELECT'
+ ' uc1.table_name, uc1.constraint_name, uc1.r_constraint_name, col.column_name, col.position, col.owner'
+ ' FROM'
+ ' information_schema.key_column_usage'
+ ' WHERE'
+ ' uc.constraint_type=\'P\' and uc1.r_constraint_name = uc.constraint_name and uc1.constraint_type = \'R\''
+ ' and uc1.constraint_name=col.constraint_name' ;
if ( owner ) {
sql += ' and col.owner=\'' + owner + '\'' ;
}
if ( table ) {
sql += ' and uc.table_Name=\'' + table + '\'' ;
}
sql += ' ) a'
+ ' INNER JOIN'
+ ' USER_CONS_COLUMNS jcol'
+ ' ON'
+ ' a.r_constraint_name=jcol.constraint_name'
+ ' order by a.owner, a.table_name, a.position' ;
return sql ;
}
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
MySQL . prototype . discoverExportedForeignKeys = function ( table , options , cb ) {
var args = getArgs ( table , options , cb ) ;
var owner = args . owner ;
table = args . table ;
options = args . options ;
cb = args . cb ;
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
var sql = queryExportedForeignKeys ( owner , table ) ;
this . query ( sql , cb ) ;
2013-07-21 06:38:40 +00:00
}
2013-07-21 17:36:26 +00:00
/ * *
* Discover foreign keys synchronously for a given table
* @ param owner
* @ param table
* @ returns { * }
* /
MySQL . prototype . discoverExportedForeignKeysSync = function ( table , options ) {
var args = getArgs ( table , options ) ;
var owner = args . owner ;
table = args . table ;
options = args . options ;
var sql = queryExportedForeignKeys ( owner , table ) ;
return this . querySync ( sql ) ;
}
2013-07-21 06:38:40 +00:00
2013-07-21 17:36:26 +00:00
function mysqlDataTypeToJSONType ( mysqlType , dataLength ) {
var type = mysqlType . toUpperCase ( ) ;
switch ( type ) {
case 'CHAR' :
if ( dataLength === 1 ) {
// Treat char(1) as boolean
return 'Boolean' ;
} else {
return 'String' ;
}
case 'VARCHAR' :
case 'TINYTEXT' :
case 'MEDIUMTEXT' :
case 'LONGTEXT' :
case 'TEXT' :
case 'ENUM' :
case 'SET' :
return 'String' ;
case 'TINYBLOB' :
case 'MEDIUMBLOB' :
case 'LONGBLOB' :
case 'BLOB' :
case 'BINARY' :
case 'VARBINARY' :
case 'BIT' :
return 'Binary' ;
case 'TINYINT' :
case 'SMALLINT' :
case 'INT' :
case 'MEDIUMINT' :
case 'YEAR' :
case 'FLOAT' :
case 'DOUBLE' :
return 'Number' ;
case 'DATE' :
case 'TIMESTAMP' :
case 'DATETIME' :
return 'Date' ;
default :
return 'String' ;
}
}
2013-07-21 06:38:40 +00:00
}