loopback-connector/lib/connector.js

388 lines
11 KiB
JavaScript
Raw Normal View History

2019-04-18 21:32:38 +00:00
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
2016-05-06 04:50:59 +00:00
// Node module: loopback-connector
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
2017-03-06 23:40:47 +00:00
'use strict';
const SG = require('strong-globalize');
const g = SG();
const debug = require('debug')('loopback:connector');
2015-05-13 17:14:44 +00:00
module.exports = Connector;
/**
2015-05-13 17:14:44 +00:00
* Base class for LoopBack connector. This is more a collection of useful
* methods for connectors than a super class
* @class
*/
function Connector(name, settings) {
this._models = {};
this.name = name;
this.settings = settings || {};
}
/**
* @private
* Set the relational property to indicate the backend is a relational DB
* @type {boolean}
*/
Connector.prototype.relational = false;
2015-05-13 17:14:44 +00:00
/**
* Check if the connector is for a relational DB
* @returns {Boolean} true for relational DB
*/
Connector.prototype.isRelational = function() {
return this.isRelational ||
(this.getTypes().indexOf('rdbms') !== -1);
};
/**
* Get types associated with the connector
* @returns {String[]} The types for the connector
*/
Connector.prototype.getTypes = function() {
return ['db', 'nosql'];
};
/**
* Get the default data type for ID
* @param {Object} prop Property definition
* @returns {Function} The default type for ID
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.getDefaultIdType = function(prop) {
2017-03-06 23:40:47 +00:00
/* jshint unused:false */
return String;
};
2017-04-06 18:44:49 +00:00
/**
* Generate random id. Each data source model must override this method.
* @param {String} modelName Model name
* @returns {*} Data type varies from model to model,
2017-04-06 18:44:49 +00:00
*/
Connector.prototype.generateUniqueId = function(modelName) {
const idType = this.getDefaultIdType && this.getDefaultIdType();
const isTypeFunction = (typeof idType === 'function');
const id = this.generateValueByColumnType ? this.generateValueByColumnType(idType) :
2017-04-06 18:44:49 +00:00
(typeof idType === 'function' ? idType() : null);
return id;
};
/**
* Get the metadata for the connector
* @returns {Object} The metadata object
* @property {String} type The type for the backend
* @property {Function} defaultIdType The default id type
2015-05-13 17:14:44 +00:00
* @property {Boolean} [isRelational] If the connector represents a relational
* database
* @property {Object} schemaForSettings The schema for settings object
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.getMetadata = function() {
if (!this._metadata) {
this._metadata = {
types: this.getTypes(),
defaultIdType: this.getDefaultIdType(),
2015-05-13 17:14:44 +00:00
isRelational: this.isRelational(),
2016-04-09 18:35:52 +00:00
schemaForSettings: {},
};
}
return this._metadata;
};
/**
* Execute a command with given parameters
2015-05-13 17:14:44 +00:00
* @param {String|Object} command The command such as SQL
* @param {Array} [params] An array of parameter values
2015-05-13 17:14:44 +00:00
* @param {Object} [options] Options object
* @param {Function} [callback] The callback function
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.execute = function(command, params, options, callback) {
2016-07-25 16:45:38 +00:00
throw new Error(g.f('execute() must be implemented by the connector'));
2015-05-13 17:14:44 +00:00
};
/**
* Get the model definition by name
* @param {String} modelName The model name
* @returns {ModelDefinition} The model definition
*/
Connector.prototype.getModelDefinition = function(modelName) {
return this._models[modelName];
};
/**
* Get connector specific settings for a given model, for example,
* ```
* {
* "postgresql": {
* "schema": "xyz"
* }
* }
* ```
*
* @param {String} modelName Model name
* @returns {Object} The connector specific settings
*/
Connector.prototype.getConnectorSpecificSettings = function(modelName) {
const settings = this.getModelDefinition(modelName).settings || {};
2015-05-13 17:14:44 +00:00
return settings[this.name];
};
/**
* Get model property definition
* @param {String} modelName Model name
* @param {String} propName Property name
* @returns {Object} Property definition
*/
Connector.prototype.getPropertyDefinition = function(modelName, propName) {
const model = this.getModelDefinition(modelName);
return Connector.getNestedPropertyDefinition(
model.model.definition,
propName.split('.'),
);
};
/**
* Helper function to get nested property definition
* @param {Object} definition Model name
* @param {Array} propPath
* @returns {Object} Property definition
*/
Connector.getNestedPropertyDefinition = function(definition, propPath) {
const properties = definition.properties || {};
const prop = properties[propPath[0]];
const isPropUndefined = typeof prop === 'undefined';
const isArray = !isPropUndefined && Array.isArray(prop.type);
const isFunction = !isPropUndefined && !isArray && typeof prop.type === 'function';
if (propPath.length === 1) return prop;
if (isPropUndefined || (propPath.length > 1 && (isArray && prop.type.length === 0))) {
return undefined;
}
const nextDefinition =
(isArray && prop.type[0].definition) ||
(isFunction && prop.type.definition);
if (nextDefinition === undefined) {
return undefined;
} else {
return Connector.getNestedPropertyDefinition(
nextDefinition,
propPath.slice(1),
);
}
};
/**
* Look up the data source by model name
* @param {String} model The model name
* @returns {DataSource} The data source
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.getDataSource = function(model) {
const m = this.getModelDefinition(model);
if (!m) {
2015-05-13 17:14:44 +00:00
debug('Model not found: ' + model);
}
return m && m.model.dataSource;
};
/**
* Get the id property name
* @param {String} model The model name
* @returns {String} The id property name
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.idName = function(model) {
return this.getDataSource(model).idName(model);
};
/**
* Get the id property names
* @param {String} model The model name
* @returns {String[]} The id property names
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.idNames = function(model) {
return this.getDataSource(model).idNames(model);
};
/**
* Get the id index (sequence number, starting from 1)
* @param {String} model The model name
* @param {String} prop The property name
* @returns {Number} The id index, undefined if the property is not part
* of the primary key
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.id = function(model, prop) {
const p = this.getModelDefinition(model).properties[prop];
return p && p.id;
};
/**
* Return the database name of the property of the model if it exists.
* Otherwise return the property name.
* Some connectors allow the column/field name to be customized
* at the model property definition level as `column`,
* `columnName`, or `field`. For example,
*
* ```json
* "name": {
* "type": "string",
* "mysql": {
* "column": "NAME"
* }
* }
* ```
* @param {String} model The target model name
* @param {String} prop The property name
*
* @returns {String} The database mapping name of the property of the model if it exists
*/
Connector.prototype.getPropertyDbName = Connector.prototype.column =
function(model, property) {
const prop = this.getPropertyDefinition(model, property);
let mappingName;
if (prop && prop[this.name]) {
mappingName = prop[this.name].column || prop[this.name].columnName ||
prop[this.name].field || prop[this.name].fieldName;
if (mappingName) {
// Explicit column name, return as-is
return mappingName;
}
}
// Check if name attribute provided for column name
if (prop && prop.name) {
return prop.name;
}
mappingName = property;
if (typeof this.dbName === 'function') {
mappingName = this.dbName(mappingName);
}
return mappingName;
};
/**
* Return the database name of the id property of the model if it exists.
* Otherwise return the name of the id property.
* @param {String} model The target model name
* @param {String} prop The property name
* @returns {String} the database mapping name of the id property of the model if it exists.
*/
Connector.prototype.getIdDbName = Connector.prototype.idColumn = function(model) {
const idName = this.getDataSource(model).getModelDefinition(model).idName();
return this.getPropertyDbName(model, idName);
};
/**
* Hook to be called by DataSource for defining a model
* @param {Object} modelDefinition The model definition
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.define = function(modelDefinition) {
modelDefinition.settings = modelDefinition.settings || {};
this._models[modelDefinition.model.modelName] = modelDefinition;
};
/**
* Hook to be called by DataSource for defining a model property
* @param {String} model The model name
* @param {String} propertyName The property name
2015-05-13 17:14:44 +00:00
* @param {Object} propertyDefinition The object for property definition
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.defineProperty = function(model, propertyName, propertyDefinition) {
const modelDef = this.getModelDefinition(model);
2015-05-15 17:27:08 +00:00
modelDef.properties[propertyName] = propertyDefinition;
};
/**
* Disconnect from the connector
2015-05-13 17:14:44 +00:00
* @param {Function} [cb] Callback function
*/
Connector.prototype.disconnect = function disconnect(cb) {
// NO-OP
2015-05-13 17:14:44 +00:00
if (cb) {
process.nextTick(cb);
}
};
/**
* Get the id value for the given model
* @param {String} model The model name
* @param {Object} data The model instance data
* @returns {*} The id value
*
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.getIdValue = function(model, data) {
return data && data[this.idName(model)];
};
/**
* Set the id value for the given model
* @param {String} model The model name
* @param {Object} data The model instance data
* @param {*} value The id value
*
*/
2015-05-13 17:14:44 +00:00
Connector.prototype.setIdValue = function(model, data, value) {
if (data) {
data[this.idName(model)] = value;
}
};
2015-05-13 17:14:44 +00:00
/**
* Test if a property is nullable
* @param {Object} prop The property definition
* @returns {boolean} true if nullable
*/
Connector.prototype.isNullable = function(prop) {
if (prop.required || prop.id) {
return false;
}
if (prop.nullable || prop['null'] || prop.allowNull) {
return true;
}
if (prop.nullable === false || prop['null'] === false ||
prop.allowNull === false) {
return false;
}
return true;
};
/**
* Return the DataAccessObject interface implemented by the connector
* @returns {Object} An object containing all methods implemented by the
* connector that can be mixed into the model class. It should be considered as
* the interface.
*/
Connector.prototype.getDataAccessObject = function() {
return this.DataAccessObject;
};
/*!
* Define aliases to a prototype method/property
* @param {Function} cls The class that owns the method/property
* @param {String} methodOrPropertyName The official property method/property name
* @param {String|String[]} aliases Aliases to the official property/method
*/
Connector.defineAliases = function(cls, methodOrPropertyName, aliases) {
if (typeof aliases === 'string') {
aliases = [aliases];
}
if (Array.isArray(aliases)) {
aliases.forEach(function(alias) {
if (typeof alias === 'string') {
Object.defineProperty(cls, alias, {
get: function() {
return this[methodOrPropertyName];
2016-04-09 18:35:52 +00:00
},
2015-05-13 17:14:44 +00:00
});
}
});
}
};
2015-05-13 17:14:44 +00:00
/**
* `command()` and `query()` are aliases to `execute()`
*/
Connector.defineAliases(Connector.prototype, 'execute', ['command', 'query']);