loopback-connector-mysql/lib/discovery.js

423 lines
13 KiB
JavaScript
Raw Normal View History

2020-02-08 22:23:33 +00:00
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
2016-05-03 23:52:03 +00:00
// Node module: loopback-connector-mysql
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
2016-08-10 18:41:03 +00:00
'use strict';
const g = require('strong-globalize')();
2016-07-19 20:52:46 +00:00
2013-07-21 17:36:26 +00:00
module.exports = mixinDiscovery;
/*!
* @param {MySQL} MySQL connector class
* @param {Object} mysql mysql driver
*/
function mixinDiscovery(MySQL, mysql) {
const async = require('async');
2013-07-21 17:36:26 +00:00
2014-02-13 00:57:06 +00:00
function paginateSQL(sql, orderBy, options) {
options = options || {};
let limitClause = '';
2014-02-13 00:57:06 +00:00
if (options.offset || options.skip || options.limit) {
// Offset starts from 0
let offset = Number(options.offset || options.skip || 0);
if (isNaN(offset)) {
offset = 0;
}
limitClause = ' LIMIT ' + offset;
2014-02-13 00:57:06 +00:00
if (options.limit) {
let limit = Number(options.limit);
if (isNaN(limit)) {
limit = 0;
}
limitClause = limitClause + ',' + limit;
2014-02-13 00:57:06 +00:00
}
}
if (!orderBy) {
sql += ' ORDER BY ' + orderBy;
2013-07-21 06:38:40 +00:00
}
return sql + limitClause;
}
/*!
* Build sql for listing schemas (databases in MySQL)
* @params {Object} [options] Options object
* @returns {String} The SQL statement
*/
2017-02-13 20:45:59 +00:00
MySQL.prototype.buildQuerySchemas = function(options) {
const sql = 'SELECT catalog_name as "catalog",' +
' schema_name as "schema"' +
' FROM information_schema.schemata';
return paginateSQL(sql, 'schema_name', options);
2017-02-13 20:45:59 +00:00
};
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/*!
* Build sql for listing tables
* @param options {all: for all owners, owner: for a given owner}
* @returns {string} The sql statement
*/
2017-02-13 20:45:59 +00:00
MySQL.prototype.buildQueryTables = function(options) {
let sqlTables = null;
const schema = options.owner || options.schema;
2013-07-21 06:38:40 +00:00
if (options.all && !schema) {
sqlTables = paginateSQL('SELECT \'table\' AS "type",' +
' table_name AS "name", table_schema AS "owner"' +
' FROM information_schema.tables',
2017-07-24 16:48:26 +00:00
'table_schema, table_name', options);
} else if (schema) {
sqlTables = paginateSQL('SELECT \'table\' AS "type",' +
2018-07-12 17:42:19 +00:00
' table_name AS "name", table_schema AS "owner"' +
' FROM information_schema.tables' +
2015-05-14 15:39:36 +00:00
' WHERE table_schema=' + mysql.escape(schema),
2017-07-24 16:48:26 +00:00
'table_schema, table_name', options);
2014-02-13 00:57:06 +00:00
} else {
sqlTables = paginateSQL('SELECT \'table\' AS "type",' +
' table_name AS "name", ' +
' table_schema AS "owner" FROM information_schema.tables' +
' WHERE table_schema=SUBSTRING_INDEX(USER(),\'@\',1)',
2017-07-24 16:48:26 +00:00
'table_name', options);
2013-07-21 06:38:40 +00:00
}
2014-02-13 00:57:06 +00:00
return sqlTables;
2017-02-13 20:45:59 +00:00
};
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/*!
* Build sql for listing views
* @param options {all: for all owners, owner: for a given owner}
* @returns {string} The sql statement
*/
2017-02-13 20:45:59 +00:00
MySQL.prototype.buildQueryViews = function(options) {
let sqlViews = null;
2014-02-13 00:57:06 +00:00
if (options.views) {
const schema = options.owner || options.schema;
2013-07-21 17:36:26 +00:00
if (options.all && !schema) {
sqlViews = paginateSQL('SELECT \'view\' AS "type",' +
' table_name AS "name",' +
' table_schema AS "owner"' +
' FROM information_schema.views',
2017-07-24 16:48:26 +00:00
'table_schema, table_name', options);
} else if (schema) {
sqlViews = paginateSQL('SELECT \'view\' AS "type",' +
' table_name AS "name",' +
' table_schema AS "owner"' +
' FROM information_schema.views' +
2015-05-14 15:39:36 +00:00
' WHERE table_schema=' + mysql.escape(schema),
2017-07-24 16:48:26 +00:00
'table_schema, table_name', options);
2014-02-13 00:57:06 +00:00
} else {
sqlViews = paginateSQL('SELECT \'view\' AS "type",' +
' table_name AS "name",' +
' table_schema AS "owner"' +
' FROM information_schema.views',
2017-07-24 16:48:26 +00:00
'table_name', options);
2014-02-13 00:57:06 +00:00
}
2013-07-21 06:38:40 +00:00
}
2014-02-13 00:57:06 +00:00
return sqlViews;
};
2014-02-13 00:57:06 +00:00
/**
* Discover model definitions
*
* @param {Object} options Options for discovery
* @param {Function} [cb] The callback function
*/
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/*!
* Normalize the arguments
* @param table string, required
* @param options object, optional
* @param cb function, optional
*/
2017-02-13 20:45:59 +00:00
MySQL.prototype.getArgs = function(table, options, cb) {
2014-02-13 00:57:06 +00:00
if ('string' !== typeof table || !table) {
2016-07-19 20:52:46 +00:00
throw new Error(g.f('{{table}} is a required string argument: %s', table));
2013-07-21 06:38:40 +00:00
}
2014-02-13 00:57:06 +00:00
options = options || {};
if (!cb && 'function' === typeof options) {
cb = options;
options = {};
}
if (typeof options !== 'object') {
2016-07-19 20:52:46 +00:00
throw new Error(g.f('{{options}} must be an {{object}}: %s', options));
2014-02-13 00:57:06 +00:00
}
return {
schema: options.owner || options.schema,
2014-02-13 00:57:06 +00:00
table: table,
options: options,
2016-08-10 18:41:03 +00:00
cb: cb,
2014-02-13 00:57:06 +00:00
};
2017-02-13 20:45:59 +00:00
};
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/*!
* Build the sql statement to query columns for a given table
* @param schema
2014-02-13 00:57:06 +00:00
* @param table
* @returns {String} The sql statement
*/
MySQL.prototype.buildQueryColumns = function(schema, table, options = {}) {
let sql = null;
if (schema) {
sql = paginateSQL('SELECT table_schema AS "owner",' +
' table_name AS "tableName",' +
' column_name AS "columnName",' +
' data_type AS "dataType",' +
' character_maximum_length AS "dataLength",' +
' numeric_precision AS "dataPrecision",' +
' numeric_scale AS "dataScale",' +
' column_type AS "columnType",' +
' is_nullable = \'YES\' AS "nullable",' +
' CASE WHEN extra LIKE \'%auto_increment%\' THEN 1 ELSE 0 END AS "generated"' +
' FROM information_schema.columns' +
' WHERE table_schema=' + mysql.escape(schema) +
(table ? ' AND table_name=' + mysql.escape(table) : ''),
2017-07-24 16:48:26 +00:00
'table_name, ordinal_position', {});
2014-02-13 00:57:06 +00:00
} else {
sql = paginateSQL('SELECT table_schema AS "owner",' +
' table_name AS "tableName",' +
' column_name AS "columnName",' +
' data_type AS "dataType",' +
' character_maximum_length AS "dataLength",' +
' numeric_precision AS "dataPrecision",' +
' numeric_scale AS "dataScale",' +
' column_type AS "columnType",' +
' is_nullable = \'YES\' AS "nullable",' +
' CASE WHEN extra LIKE \'%auto_increment%\' THEN 1 ELSE 0 END AS "generated"' +
' FROM information_schema.columns' +
(table ? ' WHERE table_name=' + mysql.escape(table) : ''),
2017-07-24 16:48:26 +00:00
'table_name, ordinal_position', {});
2013-07-21 17:36:26 +00:00
}
if (options.orderBy) {
sql += ' ORDER BY ' + options.orderBy;
}
2014-02-13 00:57:06 +00:00
return sql;
2017-02-13 20:45:59 +00:00
};
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/**
* Discover model properties from a table
* @param {String} table The table name
* @param {Object} options The options for discovery
* @param {Function} [cb] The callback function
*
*/
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/*!
* Build the sql statement for querying primary keys of a given table
* @param schema
2014-02-13 00:57:06 +00:00
* @param table
* @returns {string}
*/
2017-07-24 16:48:26 +00:00
// http://docs.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html
// #getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String)
2017-02-13 20:45:59 +00:00
MySQL.prototype.buildQueryPrimaryKeys = function(schema, table) {
let 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\'';
2013-07-21 17:36:26 +00:00
if (schema) {
sql += ' AND table_schema=' + mysql.escape(schema);
2014-02-13 00:57:06 +00:00
}
if (table) {
sql += ' AND table_name=' + mysql.escape(table);
2013-07-21 06:38:40 +00:00
}
sql += ' ORDER BY' +
' table_schema, constraint_name, table_name, ordinal_position';
2014-02-13 00:57:06 +00:00
return sql;
2017-02-13 20:45:59 +00:00
};
2013-07-21 17:36:26 +00:00
2014-02-13 00:57:06 +00:00
/**
* Discover primary keys for a given table
* @param {String} table The table name
* @param {Object} options The options for discovery
* @param {Function} [cb] The callback function
*/
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/*!
* Build the sql statement for querying foreign keys of a given table
* @param schema
2014-02-13 00:57:06 +00:00
* @param table
* @returns {string}
*/
2017-02-13 20:45:59 +00:00
MySQL.prototype.buildQueryForeignKeys = function(schema, table) {
let 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 (schema) {
sql += ' AND table_schema=' + mysql.escape(schema);
2013-07-21 17:36:26 +00:00
}
2014-02-13 00:57:06 +00:00
if (table) {
sql += ' AND table_name=' + mysql.escape(table);
2014-02-13 00:57:06 +00:00
}
return sql;
2017-02-13 20:45:59 +00:00
};
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/**
* Discover foreign keys for a given table
* @param {String} table The table name
* @param {Object} options The options for discovery
* @param {Function} [cb] The callback function
*/
2013-10-08 20:44:37 +00:00
2014-02-13 00:57:06 +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).
2014-02-13 00:57:06 +00:00
* They are ordered by fkTableOwner, fkTableName, and keySeq.
* @param schema
2014-02-13 00:57:06 +00:00
* @param table
* @returns {string}
*/
2017-02-13 20:45:59 +00:00
MySQL.prototype.buildQueryExportedForeignKeys = function(schema, table) {
let sql = 'SELECT a.constraint_name AS "fkName",' +
' a.table_schema AS "fkOwner",' +
' a.table_name AS "fkTableName",' +
' a.column_name AS "fkColumnName",' +
' a.ordinal_position AS "keySeq",' +
' NULL AS "pkName",' +
' a.referenced_table_schema AS "pkOwner",' +
' a.referenced_table_name AS "pkTableName",' +
' a.referenced_column_name AS "pkColumnName"' +
' FROM information_schema.key_column_usage a' +
' WHERE a.position_in_unique_constraint IS NOT NULL';
if (schema) {
sql += ' AND a.referenced_table_schema=' + mysql.escape(schema);
2013-07-21 17:36:26 +00:00
}
2014-02-13 00:57:06 +00:00
if (table) {
sql += ' AND a.referenced_table_name=' + mysql.escape(table);
2014-02-13 00:57:06 +00:00
}
sql += ' ORDER BY a.table_schema, a.table_name, a.ordinal_position';
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
return sql;
2017-02-13 20:45:59 +00:00
};
2013-07-21 06:38:40 +00:00
2014-02-13 00:57:06 +00:00
/**
* Discover foreign keys that reference to the primary key of this table
* @param {String} table The table name
* @param {Object} options The options for discovery
* @param {Function} [cb] The callback function
*/
2013-07-21 17:36:26 +00:00
MySQL.prototype.buildPropertyType = function(columnDefinition, options) {
const mysqlType = columnDefinition.dataType;
const columnType = columnDefinition.columnType;
const dataLength = columnDefinition.dataLength;
// when options are received here the treatTINYINT1AsTinyInt comes as a string
if (typeof options.treatCHAR1AsString === 'string') {
options.treatCHAR1AsString =
options.treatCHAR1AsString === 'true';
}
// when options are received here the treatBIT1AsBit comes as a string
if (typeof options.treatBIT1AsBit === 'string') {
options.treatBIT1AsBit =
options.treatBIT1AsBit === 'true';
}
// when options are received here the treatTINYINT1AsTinyInt comes as a string
if (typeof options.treatTINYINT1AsTinyInt === 'string') {
options.treatTINYINT1AsTinyInt =
options.treatTINYINT1AsTinyInt === 'true';
}
const type = mysqlType.toUpperCase();
2014-02-13 00:57:06 +00:00
switch (type) {
case 'CHAR':
if (!options.treatCHAR1AsString && columnType === 'char(1)') {
// Treat char(1) as boolean ('Y', 'N', 'T', 'F', '0', '1')
2014-02-13 00:57:06 +00:00
return 'Boolean';
2013-07-21 17:36:26 +00:00
}
2014-02-13 00:57:06 +00:00
case 'VARCHAR':
case 'TINYTEXT':
case 'MEDIUMTEXT':
case 'LONGTEXT':
case 'TEXT':
case 'SET':
return 'String';
case 'TINYBLOB':
case 'MEDIUMBLOB':
case 'LONGBLOB':
case 'BLOB':
case 'BINARY':
case 'VARBINARY':
case 'BIT':
// treat BIT(1) as boolean as it's 1 or 0
if (!options.treatBIT1AsBit && columnType === 'bit(1)') {
return 'Boolean';
}
2014-02-13 00:57:06 +00:00
return 'Binary';
case 'TINYINT':
// treat TINYINT(1) as boolean as it is aliased as BOOL and BOOLEAN in mysql
if (!options.treatTINYINT1AsTinyInt && columnType === 'tinyint(1)') {
return 'Boolean';
}
2014-02-13 00:57:06 +00:00
case 'SMALLINT':
case 'INT':
case 'MEDIUMINT':
case 'YEAR':
case 'FLOAT':
case 'DOUBLE':
2014-05-25 16:46:55 +00:00
case 'BIGINT':
2017-06-26 19:40:29 +00:00
case 'INTEGER':
case 'DECIMAL':
case 'NUMERIC':
2014-02-13 00:57:06 +00:00
return 'Number';
case 'DATE':
case 'TIMESTAMP':
case 'DATETIME':
return 'Date';
2014-02-13 16:35:52 +00:00
case 'POINT':
2014-02-13 00:57:06 +00:00
return 'GeoPoint';
case 'BOOL':
case 'BOOLEAN':
return 'Boolean';
case 'JSON':
return 'object';
case 'ENUM':
return columnType;
2014-02-13 00:57:06 +00:00
default:
return 'String';
2013-07-21 17:36:26 +00:00
}
2016-08-10 18:41:03 +00:00
};
MySQL.prototype.getDefaultSchema = function() {
if (this.dataSource && this.dataSource.settings &&
this.dataSource.settings.database) {
return this.dataSource.settings.database;
}
return undefined;
};
2017-02-13 20:45:59 +00:00
// Recommended MySQL 5.7 Boolean scheme. See
// http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html
// Currently default is the inverse of the recommendation for backward compatibility.
MySQL.prototype.setDefaultOptions = function(options) {
const defaultOptions = {
2017-02-13 20:45:59 +00:00
treatCHAR1AsString: false,
treatBIT1AsBit: true,
treatTINYINT1AsTinyInt: true,
};
for (const opt in defaultOptions) {
2017-02-13 20:45:59 +00:00
if (defaultOptions.hasOwnProperty(opt) && !options.hasOwnProperty(opt)) {
options[opt] = defaultOptions[opt];
}
}
};
MySQL.prototype.setNullableProperty = function(r) {
r.nullable = r.nullable ? 'Y' : 'N';
};
2013-07-21 06:38:40 +00:00
}