2016-04-01 22:25:16 +00:00
|
|
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
|
|
|
// Node module: loopback-datasource-juggler
|
|
|
|
// This file is licensed under the MIT License.
|
|
|
|
// License text available at https://opensource.org/licenses/MIT
|
2016-08-22 19:55:22 +00:00
|
|
|
'use strict';
|
2016-04-01 22:25:16 +00:00
|
|
|
|
2018-12-07 16:13:48 +00:00
|
|
|
const assert = require('assert');
|
|
|
|
const util = require('util');
|
|
|
|
const EventEmitter = require('events').EventEmitter;
|
|
|
|
const traverse = require('traverse');
|
|
|
|
const ModelBaseClass = require('./model');
|
|
|
|
const ModelBuilder = require('./model-builder');
|
2013-08-24 16:38:12 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Model definition
|
|
|
|
*/
|
|
|
|
module.exports = ModelDefinition;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor for ModelDefinition
|
2013-10-02 05:14:21 +00:00
|
|
|
* @param {ModelBuilder} modelBuilder A model builder instance
|
2013-10-02 22:18:50 +00:00
|
|
|
* @param {String|Object} name The model name or the schema object
|
2013-10-02 05:14:21 +00:00
|
|
|
* @param {Object} properties The model properties, optional
|
|
|
|
* @param {Object} settings The model settings, optional
|
2013-08-24 16:38:12 +00:00
|
|
|
* @returns {ModelDefinition}
|
|
|
|
* @constructor
|
|
|
|
*
|
|
|
|
*/
|
2013-10-02 05:14:21 +00:00
|
|
|
function ModelDefinition(modelBuilder, name, properties, settings) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (!(this instanceof ModelDefinition)) {
|
|
|
|
// Allow to call ModelDefinition without new
|
|
|
|
return new ModelDefinition(modelBuilder, name, properties, settings);
|
|
|
|
}
|
|
|
|
this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance;
|
|
|
|
assert(name, 'name is missing');
|
2013-08-24 16:38:12 +00:00
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
if (arguments.length === 2 && typeof name === 'object') {
|
2018-12-07 16:13:48 +00:00
|
|
|
const schema = name;
|
2014-01-24 17:09:53 +00:00
|
|
|
this.name = schema.name;
|
|
|
|
this.rawProperties = schema.properties || {}; // Keep the raw property definitions
|
|
|
|
this.settings = schema.settings || {};
|
|
|
|
} else {
|
|
|
|
assert(typeof name === 'string', 'name must be a string');
|
|
|
|
this.name = name;
|
|
|
|
this.rawProperties = properties || {}; // Keep the raw property definitions
|
|
|
|
this.settings = settings || {};
|
|
|
|
}
|
|
|
|
this.relations = [];
|
|
|
|
this.properties = null;
|
|
|
|
this.build();
|
2013-08-24 16:38:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
util.inherits(ModelDefinition, EventEmitter);
|
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
// Set up types
|
|
|
|
require('./types')(ModelDefinition);
|
2013-08-24 16:38:12 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return table name for specified `modelName`
|
|
|
|
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.tableName = function(connectorType) {
|
2018-12-07 16:13:48 +00:00
|
|
|
const settings = this.settings;
|
2014-01-24 17:09:53 +00:00
|
|
|
if (settings[connectorType]) {
|
|
|
|
return settings[connectorType].table || settings[connectorType].tableName || this.name;
|
|
|
|
} else {
|
|
|
|
return this.name;
|
|
|
|
}
|
2013-08-24 16:38:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return column name for specified modelName and propertyName
|
|
|
|
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
|
|
|
|
* @param propertyName The property name
|
|
|
|
* @returns {String} columnName
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.columnName = function(connectorType, propertyName) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (!propertyName) {
|
|
|
|
return propertyName;
|
|
|
|
}
|
|
|
|
this.build();
|
2018-12-07 16:13:48 +00:00
|
|
|
const property = this.properties[propertyName];
|
2014-01-24 17:09:53 +00:00
|
|
|
if (property && property[connectorType]) {
|
|
|
|
return property[connectorType].column || property[connectorType].columnName || propertyName;
|
|
|
|
} else {
|
|
|
|
return propertyName;
|
|
|
|
}
|
2013-08-24 16:38:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return column metadata for specified modelName and propertyName
|
|
|
|
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
|
|
|
|
* @param propertyName The property name
|
|
|
|
* @returns {Object} column metadata
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.columnMetadata = function(connectorType, propertyName) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (!propertyName) {
|
|
|
|
return propertyName;
|
|
|
|
}
|
|
|
|
this.build();
|
2018-12-07 16:13:48 +00:00
|
|
|
const property = this.properties[propertyName];
|
2014-01-24 17:09:53 +00:00
|
|
|
if (property && property[connectorType]) {
|
|
|
|
return property[connectorType];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2013-08-24 16:38:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return column names for specified modelName
|
|
|
|
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
|
|
|
|
* @returns {String[]} column names
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.columnNames = function(connectorType) {
|
2014-01-24 17:09:53 +00:00
|
|
|
this.build();
|
2018-12-07 16:13:48 +00:00
|
|
|
const props = this.properties;
|
|
|
|
const cols = [];
|
|
|
|
for (const p in props) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (props[p][connectorType]) {
|
2017-04-05 19:12:27 +00:00
|
|
|
cols.push(props[p][connectorType].column || props[p][connectorType].columnName || p);
|
2014-01-24 17:09:53 +00:00
|
|
|
} else {
|
|
|
|
cols.push(p);
|
2013-08-24 16:38:12 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
|
|
|
return cols;
|
2013-08-24 16:38:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the ID properties sorted by the index
|
|
|
|
* @returns {Object[]} property name/index for IDs
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.ids = function() {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (this._ids) {
|
|
|
|
return this._ids;
|
|
|
|
}
|
2018-12-07 16:13:48 +00:00
|
|
|
const ids = [];
|
2014-01-24 17:09:53 +00:00
|
|
|
this.build();
|
2018-12-07 16:13:48 +00:00
|
|
|
const props = this.properties;
|
|
|
|
for (const key in props) {
|
|
|
|
let id = props[key].id;
|
2014-01-24 17:09:53 +00:00
|
|
|
if (!id) {
|
|
|
|
continue;
|
2013-08-24 16:38:12 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
if (typeof id !== 'number') {
|
|
|
|
id = 1;
|
2013-08-24 16:38:12 +00:00
|
|
|
}
|
2016-08-19 17:46:59 +00:00
|
|
|
ids.push({name: key, id: id, property: props[key]});
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
2016-04-01 11:48:17 +00:00
|
|
|
ids.sort(function(a, b) {
|
2015-01-21 12:16:34 +00:00
|
|
|
return a.id - b.id;
|
2014-01-24 17:09:53 +00:00
|
|
|
});
|
|
|
|
this._ids = ids;
|
|
|
|
return ids;
|
2013-08-24 16:38:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the ID column name
|
|
|
|
* @param {String} modelName The model name
|
|
|
|
* @returns {String} columnName for ID
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.idColumnName = function(connectorType) {
|
2014-01-24 17:09:53 +00:00
|
|
|
return this.columnName(connectorType, this.idName());
|
2013-08-24 16:38:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the ID property name
|
|
|
|
* @returns {String} property name for ID
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.idName = function() {
|
2018-12-07 16:13:48 +00:00
|
|
|
const id = this.ids()[0];
|
2014-01-24 17:09:53 +00:00
|
|
|
if (this.properties.id && this.properties.id.id) {
|
|
|
|
return 'id';
|
|
|
|
} else {
|
2014-03-24 21:56:52 +00:00
|
|
|
return id && id.name;
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
2013-08-24 16:38:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the ID property names sorted by the index
|
|
|
|
* @returns {String[]} property names for IDs
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.idNames = function() {
|
2018-12-07 16:13:48 +00:00
|
|
|
const ids = this.ids();
|
|
|
|
const names = ids.map(function(id) {
|
2014-01-24 17:09:53 +00:00
|
|
|
return id.name;
|
|
|
|
});
|
|
|
|
return names;
|
2013-08-24 16:38:12 +00:00
|
|
|
};
|
2013-10-02 05:14:21 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @returns {{}}
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.indexes = function() {
|
2014-01-24 17:09:53 +00:00
|
|
|
this.build();
|
2018-12-07 16:13:48 +00:00
|
|
|
const indexes = {};
|
2014-01-24 17:09:53 +00:00
|
|
|
if (this.settings.indexes) {
|
2018-12-07 16:13:48 +00:00
|
|
|
for (const i in this.settings.indexes) {
|
2014-01-24 17:09:53 +00:00
|
|
|
indexes[i] = this.settings.indexes[i];
|
2013-10-02 05:14:21 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
2018-12-07 16:13:48 +00:00
|
|
|
for (const p in this.properties) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (this.properties[p].index) {
|
|
|
|
indexes[p + '_index'] = this.properties[p].index;
|
2013-10-02 05:14:21 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
|
|
|
return indexes;
|
2013-10-02 05:14:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build a model definition
|
2013-10-02 22:18:50 +00:00
|
|
|
* @param {Boolean} force Forcing rebuild
|
2013-10-02 05:14:21 +00:00
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.build = function(forceRebuild) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (forceRebuild) {
|
|
|
|
this.properties = null;
|
|
|
|
this.relations = [];
|
|
|
|
this._ids = null;
|
2014-08-31 10:17:53 +00:00
|
|
|
this.json = null;
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
|
|
|
if (this.properties) {
|
|
|
|
return this.properties;
|
|
|
|
}
|
|
|
|
this.properties = {};
|
2018-12-07 16:13:48 +00:00
|
|
|
for (const p in this.rawProperties) {
|
|
|
|
const prop = this.rawProperties[p];
|
|
|
|
const type = this.modelBuilder.resolveType(prop);
|
2014-01-24 17:09:53 +00:00
|
|
|
if (typeof type === 'string') {
|
|
|
|
this.relations.push({
|
|
|
|
source: this.name,
|
|
|
|
target: type,
|
|
|
|
type: Array.isArray(prop) ? 'hasMany' : 'belongsTo',
|
2016-04-01 11:48:17 +00:00
|
|
|
as: p,
|
2014-01-24 17:09:53 +00:00
|
|
|
});
|
|
|
|
} else {
|
2018-12-07 16:13:48 +00:00
|
|
|
const typeDef = {
|
2016-04-01 11:48:17 +00:00
|
|
|
type: type,
|
2014-01-24 17:09:53 +00:00
|
|
|
};
|
|
|
|
if (typeof prop === 'object' && prop !== null) {
|
2018-12-07 16:13:48 +00:00
|
|
|
for (const a in prop) {
|
2014-01-24 17:09:53 +00:00
|
|
|
// Skip the type property but don't delete it Model.extend() shares same instances of the properties from the base class
|
|
|
|
if (a !== 'type') {
|
|
|
|
typeDef[a] = prop[a];
|
|
|
|
}
|
2013-10-02 05:14:21 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
|
|
|
this.properties[p] = typeDef;
|
2013-10-02 05:14:21 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
|
|
|
return this.properties;
|
2013-10-02 22:18:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Define a property
|
|
|
|
* @param {String} propertyName The property name
|
|
|
|
* @param {Object} propertyDefinition The property definition
|
|
|
|
*/
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.defineProperty = function(propertyName, propertyDefinition) {
|
2014-01-24 17:09:53 +00:00
|
|
|
this.rawProperties[propertyName] = propertyDefinition;
|
|
|
|
this.build(true);
|
2013-10-02 05:14:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
function isModelClass(cls) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (!cls) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return cls.prototype instanceof ModelBaseClass;
|
2013-10-02 05:14:21 +00:00
|
|
|
}
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
ModelDefinition.prototype.toJSON = function(forceRebuild) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (forceRebuild) {
|
|
|
|
this.json = null;
|
|
|
|
}
|
|
|
|
if (this.json) {
|
2014-08-17 16:01:52 +00:00
|
|
|
return this.json;
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
2018-12-07 16:13:48 +00:00
|
|
|
const json = {
|
2014-01-24 17:09:53 +00:00
|
|
|
name: this.name,
|
|
|
|
properties: {},
|
2016-04-01 11:48:17 +00:00
|
|
|
settings: this.settings,
|
2014-01-24 17:09:53 +00:00
|
|
|
};
|
|
|
|
this.build(forceRebuild);
|
|
|
|
|
2018-12-07 16:13:48 +00:00
|
|
|
const mapper = function(val) {
|
2014-01-24 17:09:53 +00:00
|
|
|
if (val === undefined || val === null) {
|
|
|
|
return val;
|
2013-10-02 22:18:50 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
if ('function' === typeof val.toJSON) {
|
|
|
|
// The value has its own toJSON() object
|
|
|
|
return val.toJSON();
|
2013-10-02 22:18:50 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
if ('function' === typeof val) {
|
|
|
|
if (isModelClass(val)) {
|
|
|
|
if (val.settings && val.settings.anonymous) {
|
|
|
|
return val.definition && val.definition.toJSON().properties;
|
2013-10-02 05:14:21 +00:00
|
|
|
} else {
|
2014-01-24 17:09:53 +00:00
|
|
|
return val.modelName;
|
2013-10-02 05:14:21 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
}
|
|
|
|
return val.name;
|
|
|
|
} else {
|
|
|
|
return val;
|
2013-10-02 05:14:21 +00:00
|
|
|
}
|
2014-01-24 17:09:53 +00:00
|
|
|
};
|
2018-12-07 16:13:48 +00:00
|
|
|
for (const p in this.properties) {
|
2014-01-24 17:09:53 +00:00
|
|
|
json.properties[p] = traverse(this.properties[p]).map(mapper);
|
|
|
|
}
|
|
|
|
this.json = json;
|
|
|
|
return json;
|
2013-10-02 05:14:21 +00:00
|
|
|
};
|
2015-08-12 06:02:29 +00:00
|
|
|
|
2015-08-26 22:23:35 +00:00
|
|
|
ModelDefinition.prototype.hasPK = function() {
|
|
|
|
return this.ids().length > 0;
|
2015-08-12 06:02:29 +00:00
|
|
|
};
|