push column/idcolumn to connector level and renam

This commit is contained in:
Agnes Lin 2020-03-22 21:22:07 -04:00
parent 49e4c37ba7
commit 9d03b8cde8
5 changed files with 229 additions and 88 deletions

View File

@ -218,6 +218,62 @@ Connector.prototype.id = function(model, 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

View File

@ -354,46 +354,6 @@ SQLConnector.prototype.table = function(model) {
return tableName;
};
/**
* Get the column name for the given model property. The column name can be
* customized at the model property definition level as `column` or
* `columnName`. For example,
*
* ```json
* "name": {
* "type": "string",
* "mysql": {
* "column": "NAME"
* }
* }
* ```
*
* @param {String} model The model name
* @param {String} property The property name
* @returns {String} The column name
*/
SQLConnector.prototype.column = function(model, property) {
const prop = this.getPropertyDefinition(model, property);
let columnName;
if (prop && prop[this.name]) {
columnName = prop[this.name].column || prop[this.name].columnName;
if (columnName) {
// Explicit column name, return as-is
return columnName;
}
}
// Check if name attribute provided for column name
if (prop && prop.name) {
return prop.name;
}
columnName = property;
if (typeof this.dbName === 'function') {
columnName = this.dbName(columnName);
}
return columnName;
};
/**
* Get the column metadata for the given model property
* @param {String} model The model name
@ -420,16 +380,6 @@ SQLConnector.prototype.propertyName = function(model, column) {
return null;
};
/**
* Get the id column name.
* @param {String} model The model name
* @returns {String} The id column name
*/
SQLConnector.prototype.idColumn = function(model) {
const idName = this.getDataSource(model).getModelDefinition(model).idName();
return this.column(model, idName);
};
/**
* Get the escaped id column name
* @param {String} model The model name

View File

@ -0,0 +1,36 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: loopback-connector
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
/*
* A mockup connector that extends NoSQL/SQL connector
* to check property name mapping.
*/
const util = require('util');
const Connector = require('../../lib/connector');
const debug = require('debug')('loopback:connector:test-connector');
exports.initialize = function initializeDataSource(dataSource, callback) {
process.nextTick(function() {
if (callback) {
const connector = new TestConnector(dataSource.settings);
connector.dataSource = dataSource;
dataSource.connector = connector;
callback(null, connector);
}
});
};
function TestConnector(settings) {
Connector.call(this, 'testdb', settings);
this._tables = {};
this.data = {};
}
util.inherits(TestConnector, Connector);
TestConnector.prototype.dbName = function(name) {
return name.toUpperCase();
};

135
test/name-mapping.test.js Normal file
View File

@ -0,0 +1,135 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: loopback-connector
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const expect = require('chai').expect;
const Connector = require('../lib/connector');
const testConnector = require('./connectors/test-connector');
const juggler = require('loopback-datasource-juggler');
const ds = new juggler.DataSource({
connector: testConnector,
debug: true,
});
/* eslint-disable one-var */
let connector;
let Customer;
let Order;
/* eslint-enable one-var */
describe('Name mapping', function() {
let connector, builder;
before(() => {
connector = ds.connector;
connector._tables = {};
connector._models = {};
Customer = ds.createModel(
'Customer',
{
name: {
id: true,
type: String,
testdb: {
column: 'FIRSTNAME',
dataType: 'VARCHAR',
dataLength: 32,
},
},
middleName: {
type: Boolean,
name: 'middle_name',
postgresql: {
column: 'MIDDLENAME',
},
},
lastName: {
type: Boolean,
testdb: {
column: 'LASTNAME',
},
},
primaryAddress: {
type: String,
name: 'primary_address',
},
address: String,
},
{testdb: {table: 'CUSTOMER'}},
);
// use field in this model to mock NoSQL DB
Order = ds.createModel(
'Order',
{
id: {
id: true,
type: Number,
testdb: {
field: 'my_id',
dataType: 'ObjectId',
},
},
des: {
type: String,
testdb: {
field: 'DESCRIPTION',
},
},
},
{testdb: {table: 'ORDER'}},
);
});
context('getIdDbName', function() {
it('should map id column name', function() {
const idCol = connector.getIdDbName('Customer');
expect(idCol).to.eql('FIRSTNAME');
});
it('alias idColumn should map id column name', function() {
const idCol = connector.idColumn('Customer');
expect(idCol).to.eql('FIRSTNAME');
});
it('should map id field name', function() {
const idCol = connector.getIdDbName('Order');
expect(idCol).to.eql('my_id');
});
});
context('getPropertyDbName', function() {
it('prefers property name if the database name is not matched', function() {
const column = connector.getPropertyDbName('Customer', 'middleName');
expect(column).to.eql('middle_name');
});
it('prefers database-specific column name over property name', function() {
const column = connector.getPropertyDbName('Customer', 'lastName');
expect(column).to.eql('LASTNAME');
});
it('alias column should map the column name', function() {
const column = connector.column('Customer', 'lastName');
expect(column).to.eql('LASTNAME');
});
it('propertyMapping should map column name from name attribute', function() {
const column = connector.getPropertyDbName('Customer', 'primaryAddress');
expect(column).to.eql('primary_address');
});
it('connector-preffered configuration (UPPERCASE) is applied if no columm/field name is provided', function() {
const column = connector.getPropertyDbName('Customer', 'address');
expect(column).to.eql('ADDRESS');
});
it('prefers database-specific field name over property name', function() {
const column = connector.getPropertyDbName('Order', 'des');
expect(column).to.eql('DESCRIPTION');
});
});
});

View File

@ -79,37 +79,13 @@ describe('sql connector', function() {
{testdb: {table: 'ORDER'}});
});
// tests for column names mapping are moved to name-mapping.test.js
it('should map table name', function() {
const table = connector.table('customer');
expect(table).to.eql('CUSTOMER');
});
it('should map column name', function() {
const column = connector.column('customer', 'name');
expect(column).to.eql('NAME');
});
it('should map column name from name attribute', function() {
const column = connector.column('customer', 'primaryAddress');
expect(column).to.eql('primary_address');
});
it('prefers database-specific column name over property name', function() {
const column = connector.column('customer', 'lastName');
expect(column).to.eql('LASTNAME');
});
it('uses database-specific column name over property name even if the column name \
does not follow the connector-specific configuration (UPPERCASE)', function() {
const column = connector.column('order', 'des');
expect(column).to.eql('description');
});
it('prefers property name when database is different', function() {
const column = connector.column('customer', 'middleName');
expect(column).to.eql('middle_name');
});
it('should find column metadata', function() {
const column = connector.columnMetadata('customer', 'name');
expect(column).to.eql({
@ -124,18 +100,6 @@ describe('sql connector', function() {
expect(prop).to.eql('name');
});
it('should map id column name', function() {
const idCol = connector.idColumn('customer');
expect(idCol).to.eql('NAME');
});
it('should map id column name even if the column name does not \
follow the connector-specific configuration (UPPERCASE)', function() {
const idCol = connector.idColumn('order');
// shouldn't be converted to ORDERID
expect(idCol).to.eql('orderId');
});
it('should find escaped id column name', function() {
const idCol = connector.idColumnEscaped('customer');
expect(idCol).to.eql('`NAME`');