From 1b4409a39abc54751e1dfd614bdb0ccd91526df8 Mon Sep 17 00:00:00 2001 From: Loay Date: Mon, 13 Feb 2017 15:28:59 -0500 Subject: [PATCH] Refactor SQL discovery methods --- lib/sql.js | 297 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) diff --git a/lib/sql.js b/lib/sql.js index 1f157ef..2132a57 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -1508,3 +1508,300 @@ SQLConnector.prototype.getInsertedId = function(model, info) { SQLConnector.prototype.executeSQL = function(sql, params, options, callback) { throw new Error(g.f('{{executeSQL()}} must be implemented by the connector')); }; + +// Refactored Discovery methods + +/** + * Build sql for listing schemas + * @param {Object} options Options for discoverDatabaseSchemas + */ +SQLConnector.prototype.buildQuerySchemas = function(options) { + var sql = 'SELECT catalog_name as "catalog",' + + ' schema_name as "schema"' + + ' FROM information_schema.schemata'; + return this.paginateSQL(sql, 'schema_name', options); +}; + +/** + * Paginate the results returned from database + * @param {String} sql The sql to execute + * @param {Object} orderBy The property name by which results are ordered + * @param {Object} options Options for discoverDatabaseSchemas + */ +SQLConnector.prototype.paginateSQL = function(sql, orderBy, options) { + throw new Error(g.f('{{paginateSQL}} must be implemented by the connector')); +}; +/** + * Discover database schemas + * + // * @param {Object} options Options for discovery + * @param {Function} [cb] The callback function + */ +SQLConnector.prototype.discoverDatabaseSchemas = function(options, cb) { + if (!cb && typeof options === 'function') { + cb = options; + options = {}; + } + options = options || {}; + var self = this; + this.execute(self.buildQuerySchemas(options), cb); +}; + +/*! + * Build sql for listing tables + * @param options {all: for all owners, owner: for a given owner} + * @returns {string} The sql statement + */ + // Due to the different implementation structure of information_schema across + // connectors, each connector will have to generate its own query +SQLConnector.prototype.buildQueryTables = function(options) { + throw new Error(g.f('{{buildQueryTables}} must be implemented by the connector')); +}; + +/*! + * Build sql for listing views + * @param options {all: for all owners, owner: for a given owner} + * @returns {string} The sql statement + */ + // Due to the different implementation structure of information_schema across + // connectors, each connector will have to generate its own query +SQLConnector.prototype.buildQueryViews = function(options) { + throw new Error(g.f('{{buildQueryViews}} must be implemented by the connector')); +}; + +/** + * Discover model definitions + * + * @param {Object} options Options for discovery + * @param {Function} [cb] The callback function + */ +SQLConnector.prototype.discoverModelDefinitions = function(options, cb) { + if (!cb && typeof options === 'function') { + cb = options; + options = {}; + } + options = options || {}; + + var self = this; + var calls = [function(callback) { + self.execute(self.buildQueryTables(options), callback); + }]; + + if (options.views) { + calls.push(function(callback) { + self.execute(self.buildQueryViews(options), callback); + }); + } + async.parallel(calls, function(err, data) { + if (err) { + cb(err, data); + } else { + var merged = []; + merged = merged.concat(data.shift()); + if (data.length) { + merged = merged.concat(data.shift()); + } + cb(err, merged); + } + }); +}; + +/** + * Build sql for listing columns + * @param {String} schema The schema name + * @param {String} table The table name + */ +// Due to the different implementation structure of information_schema across +// connectors, each connector will have to generate its own query +SQLConnector.prototype.buildQueryColumns = function(schema, table) { + throw new Error(g.f('{{buildQueryColumns}} must be implemented by the connector')); +}; + +/** + * Map the property type from database to loopback + * @param {Object} columnDefinition The columnDefinition of the table/schema + * @param {Object} options The options for the connector + */ +SQLConnector.prototype.buildPropertyType = function(columnDefinition, options) { + throw new Error(g.f('{{buildPropertyType}} must be implemented by the connector')); +}; + +/*! + * Normalize the arguments + * @param table string, required + * @param options object, optional + * @param cb function, optional + */ +SQLConnector.prototype.getArgs = function(table, options, cb) { + throw new Error(g.f('{{getArgs}} must be implemented by the connector')); +}; + +/** + * 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 + */ +SQLConnector.prototype.discoverModelProperties = function(table, options, cb) { + var self = this; + var args = self.getArgs(table, options, cb); + var schema = args.schema; + + table = args.table; + options = args.options; + + if (!schema) { + schema = self.getDefaultSchema(); + } + + self.setDefaultOptions(options); + cb = args.cb; + + var sql = self.buildQueryColumns(schema, table); + var callback = function(err, results) { + if (err) { + cb(err, results); + } else { + results.map(function(r) { + r.type = self.buildPropertyType(r, options); + self.setNullableProperty(r); + }); + cb(err, results); + } + }; + this.execute(sql, callback); +}; + +/*! + * Build the sql statement for querying primary keys of a given table + * @param schema + * @param table + * @returns {string} + */ +// http://docs.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html +// #getPrimaryKeys(java.lang.String, java.lang.String, java.lang.String) +// Due to the different implementation structure of information_schema across +// connectors, each connector will have to generate its own query +SQLConnector.prototype.buildQueryPrimaryKeys = function(schema, table) { + throw new Error(g.f('{{buildQueryPrimaryKeys}} must be implemented by the connector')); +}; + +/** + * 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 + */ +SQLConnector.prototype.discoverPrimaryKeys = function(table, options, cb) { + var self = this; + var args = self.getArgs(table, options, cb); + var schema = args.schema; + + if (typeof(self.getDefaultSchema) === 'function' && !schema) { + schema = self.getDefaultSchema(); + } + table = args.table; + options = args.options; + cb = args.cb; + + var sql = self.buildQueryPrimaryKeys(schema, table); + this.execute(sql, cb); +}; + +/*! + * Build the sql statement for querying foreign keys of a given table + * @param schema + * @param table + * @returns {string} + */ + // Due to the different implementation structure of information_schema across + // connectors, each connector will have to generate its own query +SQLConnector.prototype.buildQueryForeignKeys = function(schema, table) { + throw new Error(g.f('{{buildQueryForeignKeys}} must be implemented by the connector')); +}; + +/** + * 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 + */ +SQLConnector.prototype.discoverForeignKeys = function(table, options, cb) { + var self = this; + var args = self.getArgs(table, options, cb); + var schema = args.schema; + + if (typeof(self.getDefaultSchema) === 'function' && !schema) { + schema = self.getDefaultSchema(); + } + table = args.table; + options = args.options; + cb = args.cb; + + var sql = self.buildQueryForeignKeys(schema, table); + this.execute(sql, cb); +}; + +/*! + * 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 schema + * @param table + * @returns {string} + */ + // Due to the different implementation structure of information_schema across + // connectors, each connector will have to generate its own query +SQLConnector.prototype.buildQueryExportedForeignKeys = function(schema, table) { + throw new Error(g.f('{{buildQueryExportedForeignKeys}} must be implemented by' + + 'the connector')); +}; + +/** + * 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 + */ +SQLConnector.prototype.discoverExportedForeignKeys = function(table, options, cb) { + var self = this; + var args = self.getArgs(table, options, cb); + var schema = args.schema; + + if (typeof(self.getDefaultSchema) === 'function' && !schema) { + schema = self.getDefaultSchema(); + } + table = args.table; + options = args.options; + cb = args.cb; + + var sql = self.buildQueryExportedForeignKeys(schema, table); + this.execute(sql, cb); +}; + +/** + * Discover default schema of a database + * @param {Object} options The options for discovery + */ +SQLConnector.prototype.getDefaultSchema = function(options) { + throw new Error(g.f('{{getDefaultSchema}} must be implemented by' + + 'the connector')); +}; + +/** + * Set default options for the connector + * @param {Object} options The options for discovery + */ +SQLConnector.prototype.setDefaultOptions = function(options) { + throw new Error(g.f('{{setDefaultOptions}} must be implemented by' + + 'the connector')); +}; + +/** + * Set the nullable value for the property + * @param {Object} property The property to set nullable + */ +SQLConnector.prototype.setNullableProperty = function(property) { + throw new Error(g.f('{{setNullableProperty}} must be implemented by' + + 'the connector')); +};