UNPKG

11.1 kBJavaScriptView Raw
1// Copyright IBM Corp. 2014,2019. All Rights Reserved.
2// Node module: loopback-connector
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6'use strict';
7const SG = require('strong-globalize');
8const g = SG();
9const debug = require('debug')('loopback:connector');
10
11module.exports = Connector;
12
13/**
14 * Base class for LoopBack connector. This is more a collection of useful
15 * methods for connectors than a super class
16 * @class
17 */
18function Connector(name, settings) {
19 this._models = {};
20 this.name = name;
21 this.settings = settings || {};
22}
23
24/**
25 * @private
26 * Set the relational property to indicate the backend is a relational DB
27 * @type {boolean}
28 */
29Connector.prototype.relational = false;
30
31/**
32 * Check if the connector is for a relational DB
33 * @returns {Boolean} true for relational DB
34 */
35Connector.prototype.isRelational = function() {
36 return this.isRelational ||
37 (this.getTypes().indexOf('rdbms') !== -1);
38};
39
40/**
41 * Get types associated with the connector
42 * @returns {String[]} The types for the connector
43 */
44Connector.prototype.getTypes = function() {
45 return ['db', 'nosql'];
46};
47
48/**
49 * Get the default data type for ID
50 * @param {Object} prop Property definition
51 * @returns {Function} The default type for ID
52 */
53Connector.prototype.getDefaultIdType = function(prop) {
54 /* jshint unused:false */
55 return String;
56};
57
58/**
59 * Generate random id. Each data source model must override this method.
60 * @param {String} modelName Model name
61 * @returns {*} Data type varies from model to model,
62 */
63
64Connector.prototype.generateUniqueId = function(modelName) {
65 const idType = this.getDefaultIdType && this.getDefaultIdType();
66 const isTypeFunction = (typeof idType === 'function');
67 const id = this.generateValueByColumnType ? this.generateValueByColumnType(idType) :
68 (typeof idType === 'function' ? idType() : null);
69 return id;
70};
71
72/**
73 * Get the metadata for the connector
74 * @returns {Object} The metadata object
75 * @property {String} type The type for the backend
76 * @property {Function} defaultIdType The default id type
77 * @property {Boolean} [isRelational] If the connector represents a relational
78 * database
79 * @property {Object} schemaForSettings The schema for settings object
80 */
81Connector.prototype.getMetadata = function() {
82 if (!this._metadata) {
83 this._metadata = {
84 types: this.getTypes(),
85 defaultIdType: this.getDefaultIdType(),
86 isRelational: this.isRelational(),
87 schemaForSettings: {},
88 };
89 }
90 return this._metadata;
91};
92
93/**
94 * Execute a command with given parameters
95 * @param {String|Object} command The command such as SQL
96 * @param {Array} [params] An array of parameter values
97 * @param {Object} [options] Options object
98 * @param {Function} [callback] The callback function
99 */
100Connector.prototype.execute = function(command, params, options, callback) {
101 throw new Error(g.f('execute() must be implemented by the connector'));
102};
103
104/**
105 * Get the model definition by name
106 * @param {String} modelName The model name
107 * @returns {ModelDefinition} The model definition
108 */
109Connector.prototype.getModelDefinition = function(modelName) {
110 return this._models[modelName];
111};
112
113/**
114 * Get connector specific settings for a given model, for example,
115 * ```
116 * {
117 * "postgresql": {
118 * "schema": "xyz"
119 * }
120 * }
121 * ```
122 *
123 * @param {String} modelName Model name
124 * @returns {Object} The connector specific settings
125 */
126Connector.prototype.getConnectorSpecificSettings = function(modelName) {
127 const settings = this.getModelDefinition(modelName).settings || {};
128 return settings[this.name];
129};
130
131/**
132 * Get model property definition
133 * @param {String} modelName Model name
134 * @param {String} propName Property name
135 * @returns {Object} Property definition
136 */
137Connector.prototype.getPropertyDefinition = function(modelName, propName) {
138 const model = this.getModelDefinition(modelName);
139 return Connector.getNestedPropertyDefinition(
140 model.model.definition,
141 propName.split('.'),
142 );
143};
144
145/**
146 * Helper function to get nested property definition
147 * @param {Object} definition Model name
148 * @param {Array} propPath
149 * @returns {Object} Property definition
150 */
151Connector.getNestedPropertyDefinition = function(definition, propPath) {
152 const properties = definition.properties || {};
153 const prop = properties[propPath[0]];
154 const isPropUndefined = typeof prop === 'undefined';
155 const isArray = !isPropUndefined && Array.isArray(prop.type);
156 const isFunction = !isPropUndefined && !isArray && typeof prop.type === 'function';
157
158 if (propPath.length === 1) return prop;
159
160 if (isPropUndefined || (propPath.length > 1 && (isArray && prop.type.length === 0))) {
161 return undefined;
162 }
163
164 const nextDefinition =
165 (isArray && prop.type[0].definition) ||
166 (isFunction && prop.type.definition);
167
168 if (nextDefinition === undefined) {
169 return undefined;
170 } else {
171 return Connector.getNestedPropertyDefinition(
172 nextDefinition,
173 propPath.slice(1),
174 );
175 }
176};
177
178/**
179 * Look up the data source by model name
180 * @param {String} model The model name
181 * @returns {DataSource} The data source
182 */
183Connector.prototype.getDataSource = function(model) {
184 const m = this.getModelDefinition(model);
185 if (!m) {
186 debug('Model not found: ' + model);
187 }
188 return m && m.model.dataSource;
189};
190
191/**
192 * Get the id property name
193 * @param {String} model The model name
194 * @returns {String} The id property name
195 */
196Connector.prototype.idName = function(model) {
197 return this.getDataSource(model).idName(model);
198};
199
200/**
201 * Get the id property names
202 * @param {String} model The model name
203 * @returns {String[]} The id property names
204 */
205Connector.prototype.idNames = function(model) {
206 return this.getDataSource(model).idNames(model);
207};
208
209/**
210 * Get the id index (sequence number, starting from 1)
211 * @param {String} model The model name
212 * @param {String} prop The property name
213 * @returns {Number} The id index, undefined if the property is not part
214 * of the primary key
215 */
216Connector.prototype.id = function(model, prop) {
217 const p = this.getModelDefinition(model).properties[prop];
218 return p && p.id;
219};
220
221/**
222 * Return the database name of the property of the model if it exists.
223 * Otherwise return the property name.
224 * Some connectors allow the column/field name to be customized
225 * at the model property definition level as `column`,
226 * `columnName`, or `field`. For example,
227 *
228 * ```json
229 * "name": {
230 * "type": "string",
231 * "mysql": {
232 * "column": "NAME"
233 * }
234 * }
235 * ```
236 * @param {String} model The target model name
237 * @param {String} prop The property name
238 *
239 * @returns {String} The database mapping name of the property of the model if it exists
240 */
241Connector.prototype.getPropertyDbName = Connector.prototype.column =
242function(model, property) {
243 const prop = this.getPropertyDefinition(model, property);
244 let mappingName;
245 if (prop && prop[this.name]) {
246 mappingName = prop[this.name].column || prop[this.name].columnName ||
247 prop[this.name].field || prop[this.name].fieldName;
248 if (mappingName) {
249 // Explicit column name, return as-is
250 return mappingName;
251 }
252 }
253
254 // Check if name attribute provided for column name
255 if (prop && prop.name) {
256 return prop.name;
257 }
258 mappingName = property;
259 if (typeof this.dbName === 'function') {
260 mappingName = this.dbName(mappingName);
261 }
262 return mappingName;
263};
264
265/**
266 * Return the database name of the id property of the model if it exists.
267 * Otherwise return the name of the id property.
268 * @param {String} model The target model name
269 * @param {String} prop The property name
270 * @returns {String} the database mapping name of the id property of the model if it exists.
271 */
272Connector.prototype.getIdDbName = Connector.prototype.idColumn = function(model) {
273 const idName = this.getDataSource(model).getModelDefinition(model).idName();
274 return this.getPropertyDbName(model, idName);
275};
276
277/**
278 * Hook to be called by DataSource for defining a model
279 * @param {Object} modelDefinition The model definition
280 */
281Connector.prototype.define = function(modelDefinition) {
282 modelDefinition.settings = modelDefinition.settings || {};
283 this._models[modelDefinition.model.modelName] = modelDefinition;
284};
285
286/**
287 * Hook to be called by DataSource for defining a model property
288 * @param {String} model The model name
289 * @param {String} propertyName The property name
290 * @param {Object} propertyDefinition The object for property definition
291 */
292Connector.prototype.defineProperty = function(model, propertyName, propertyDefinition) {
293 const modelDef = this.getModelDefinition(model);
294 modelDef.properties[propertyName] = propertyDefinition;
295};
296
297/**
298 * Disconnect from the connector
299 * @param {Function} [cb] Callback function
300 */
301Connector.prototype.disconnect = function disconnect(cb) {
302 // NO-OP
303 if (cb) {
304 process.nextTick(cb);
305 }
306};
307
308/**
309 * Get the id value for the given model
310 * @param {String} model The model name
311 * @param {Object} data The model instance data
312 * @returns {*} The id value
313 *
314 */
315Connector.prototype.getIdValue = function(model, data) {
316 return data && data[this.idName(model)];
317};
318
319/**
320 * Set the id value for the given model
321 * @param {String} model The model name
322 * @param {Object} data The model instance data
323 * @param {*} value The id value
324 *
325 */
326Connector.prototype.setIdValue = function(model, data, value) {
327 if (data) {
328 data[this.idName(model)] = value;
329 }
330};
331
332/**
333 * Test if a property is nullable
334 * @param {Object} prop The property definition
335 * @returns {boolean} true if nullable
336 */
337Connector.prototype.isNullable = function(prop) {
338 if (prop.required || prop.id) {
339 return false;
340 }
341 if (prop.nullable || prop['null'] || prop.allowNull) {
342 return true;
343 }
344 if (prop.nullable === false || prop['null'] === false ||
345 prop.allowNull === false) {
346 return false;
347 }
348 return true;
349};
350
351/**
352 * Return the DataAccessObject interface implemented by the connector
353 * @returns {Object} An object containing all methods implemented by the
354 * connector that can be mixed into the model class. It should be considered as
355 * the interface.
356 */
357Connector.prototype.getDataAccessObject = function() {
358 return this.DataAccessObject;
359};
360
361/*!
362 * Define aliases to a prototype method/property
363 * @param {Function} cls The class that owns the method/property
364 * @param {String} methodOrPropertyName The official property method/property name
365 * @param {String|String[]} aliases Aliases to the official property/method
366 */
367Connector.defineAliases = function(cls, methodOrPropertyName, aliases) {
368 if (typeof aliases === 'string') {
369 aliases = [aliases];
370 }
371 if (Array.isArray(aliases)) {
372 aliases.forEach(function(alias) {
373 if (typeof alias === 'string') {
374 Object.defineProperty(cls, alias, {
375 get: function() {
376 return this[methodOrPropertyName];
377 },
378 });
379 }
380 });
381 }
382};
383
384/**
385 * `command()` and `query()` are aliases to `execute()`
386 */
387Connector.defineAliases(Connector.prototype, 'execute', ['command', 'query']);