database-jones
Version:
Lightweight database mapping
562 lines (503 loc) • 20.3 kB
JavaScript
/*
Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights
reserved.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2 of
the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA
*/
"use strict";
var jones = require("database-jones");
var unified_debug = require("unified_debug");
var udebug = unified_debug.getLogger("composition/lib.js");
var util = require("util");
function Customer(id, first, last) {
if (id !== undefined) {
this.id = id;
this.unikey = id;
this.firstName = first;
this.lastName = last;
}
}
function ShoppingCart(id, customerid) {
if (id !== undefined) {
this.id = id;
this.customerid = customerid;
}
}
function LineItem(line, shoppingcartid, quantity, itemid) {
if (line !== undefined) {
this.line = line;
this.shoppingcartid = shoppingcartid;
this.quantity = quantity;
this.itemid = itemid;
}
}
function Item(id, description) {
if (id !== undefined) {
this.id = id;
this.description = description;
}
}
function Discount(id, description, percent) {
if (id !== undefined) {
this.id = id;
this.description = description;
this.percent = percent;
}
}
function Shipment(id, customerid, value) {
if (id != undefined) {
this.id = id;
this.customerid = customerid;
this.value = value;
}
}
function mapCustomer() {
// map customer
var customerMapping = new jones.TableMapping('customer');
customerMapping.mapField('id');
customerMapping.mapField('unikey');
customerMapping.mapField('firstName', 'firstname');
customerMapping.mapField('lastName', 'lastname');
customerMapping.mapOneToOne( {
fieldName: 'shoppingCart',
target: ShoppingCart,
targetField: 'customer'
} );
customerMapping.mapOneToMany( {
fieldName: 'shipments',
target: Shipment,
targetField: 'customer'
} );
customerMapping.mapManyToMany( {
fieldName: 'discounts',
target: Discount,
targetField: 'customers'
} );
customerMapping.applyToClass(Customer);
}
function mapShipment() {
var shipmentMapping = new jones.TableMapping('shipment');
shipmentMapping.mapField('id');
shipmentMapping.mapField('value', jones.converters.NumericConverter);
shipmentMapping.mapManyToOne( {
fieldName: 'customer',
foreignKey: 'fkshipmentcustomerid',
target: Customer
});
shipmentMapping.applyToClass(Shipment);
}
function mapShoppingCart() {
// map shopping cart
var shoppingCartMapping = new jones.TableMapping('shoppingcart');
shoppingCartMapping.mapField('id');
shoppingCartMapping.mapOneToOne( {
fieldName: 'customer',
foreignKey: 'fkshoppingcartcustomerid',
target: Customer
} );
shoppingCartMapping.mapOneToMany( {
fieldName: 'lineItems',
targetField: 'shoppingCart',
target: LineItem
} );
shoppingCartMapping.applyToClass(ShoppingCart);
}
function mapLineItem() {
// map line item
var lineItemMapping = new jones.TableMapping('lineitem');
lineItemMapping.mapField('line');
lineItemMapping.mapField('quantity');
lineItemMapping.mapField('shoppingcartid');
lineItemMapping.mapField('itemid');
lineItemMapping.mapManyToOne( {
fieldName: 'shoppingCart',
foreignKey: 'fklineitemshoppingcartid',
target: ShoppingCart
});
lineItemMapping.mapManyToOne( {
fieldName: 'item',
foreignKey: 'fklineitemitemid',
target: Item
});
lineItemMapping.applyToClass(LineItem);
}
function mapItem() {
var itemMapping = new jones.TableMapping('item');
itemMapping.mapField('id');
itemMapping.mapField('description');
itemMapping.mapOneToMany( {
fieldName: 'lineItems',
target: LineItem,
targetField: 'item'
} );
itemMapping.applyToClass(Item);
}
function mapDiscount() {
var discountMapping = new jones.TableMapping('discount');
discountMapping.mapField('id');
discountMapping.mapField('description');
discountMapping.mapField('percent');
discountMapping.mapManyToMany( {
fieldName: 'customers',
target: Customer,
joinTable: 'customerdiscount'
} );
discountMapping.applyToClass(Discount);
}
function CustomerDiscount(customerid, discountid) {
if (customerid !== undefined) {
this.customerid = customerid;
this.discountid = discountid;
}
}
function mapCustomerDiscount() {
var customerDiscountMapping = new jones.TableMapping('customerdiscount');
customerDiscountMapping.mapField('customerid');
customerDiscountMapping.mapField('discountid');
customerDiscountMapping.applyToClass(CustomerDiscount);
}
function FkDifferentDb(id) {
if (id !== undefined) {
this.id = id;
}
}
function mapFkDifferentDb() {
var fkDifferentDbMapping = new jones.TableMapping('testfk.fkdifferentdb');
fkDifferentDbMapping.mapField('id');
fkDifferentDbMapping.applyToClass(FkDifferentDb);
}
function verifyFK(testCase, tableMetadata, fks) {
function verify(name, expected, actual) {
var expectedValue = expected[name];
var actualValue = actual[name];
if (actualValue === undefined) {
testCase.appendErrorMessage('\nExpected ' + name + ' was undefined');
return;
}
switch(typeof expectedValue) {
case 'string':
if (expectedValue !== actualValue) {
testCase.appendErrorMessage('\nMismatch on ' + name + '; expected: ' + expectedValue + '; actual: ' + actualValue);
}
break;
case 'object':
if (!Array.isArray(actualValue)) {
testCase.appendErrorMessage('\nUnexpected not an array: ' + util.inspect(actualValue));
} else {
expectedValue.forEach(function(element) {
if (actualValue.indexOf(element) == -1) {
testCase.appendErrorMessage('\nExpected element missing from ' + name + ': ' + element + ' in ' + util.inspect(actualValue));
}
});
}
break;
}
}
if (!tableMetadata.foreignKeys) {
testCase.appendErrorMessage('\nMetadata for ' + tableMetadata.name + ' did not include foreignKeys.');
} else {
fks.forEach(function(fkexpected) {
var found = false;
tableMetadata.foreignKeys.forEach(function(fkactual) {
if (fkexpected.name === fkactual.name) {
found = true;
verify('targetTable', fkexpected, fkactual);
verify('targetDatabase', fkexpected, fkactual);
verify('columnNames', fkexpected, fkactual);
verify('targetColumnNames', fkexpected, fkactual);
}
});
if (!found) {
testCase.appendErrorMessage('\nNo foreign key ' + fkexpected.name + ' in table metadata for ' + tableMetadata.name);
}
});
}
}
function mapShop() {
mapCustomer();
mapShoppingCart();
mapLineItem();
mapItem();
mapDiscount();
mapCustomerDiscount();
mapShipment();
}
var shopDomainObjects = [Customer, ShoppingCart, LineItem, Item, Discount, CustomerDiscount, Shipment];
function createLineItem(line, quantity, itemid, item) {
var result = new LineItem();
result.line = line;
result.quantity = quantity;
result.itemid = itemid;
if (item) { result.item = item; }
return result;
}
function sortFunction(a, b) {
// sort based on id if it exists, or on line if it exists or throw an exception
if (a.id !== undefined && b.id !== undefined) {
return a.id - b.id;
}
if (a.line !== undefined && b.line !== undefined) {
return a.line - b.line;
}
throw new Error('Error: can only sort objects containing properties id or line.');
}
function verifyProjection(tc, p, e, a) {
var testCase = tc;
var projectionVerifications;
var projectionVerification;
var i;
function verifyOneProjection() {
udebug.log_detail('verifyOneProjection with', projectionVerifications.length, 'waiting:\n', projectionVerifications[0]);
var projection, expected, actual, domainObjectName, expectedField;
var actualField, expectedRelationship, actualRelationship;
function verifyProjectionField(fieldName) {
expectedField = expected[fieldName];
actualField = actual[fieldName];
if (expectedField !== actualField) {
testCase.appendErrorMessage('\n' + testCase.name +
' VerifyProjection failure for ' + domainObjectName + ' field ' + fieldName +
'\nexpected: (' + typeof expectedField + ') ' + expectedField +
'\nactual: (' + typeof actualField + ') ' + actualField);
}
}
function verifyProjectionRelationships(relationshipName) {
expectedRelationship = expected[relationshipName];
actualRelationship = actual[relationshipName];
if (Array.isArray(expectedRelationship)) {
if (Array.isArray(actualRelationship)) {
// we need to sort the actual array
// TODO let the user provide a sort function
actualRelationship.sort(sortFunction);
if (expectedRelationship.length === actualRelationship.length) {
// check each value in turn
for (i = 0; i < expectedRelationship.length; ++i) {
projectionVerifications.push([projection.relationships[relationshipName],
expectedRelationship[i], actualRelationship[i]]);
}
} else {
testCase.appendErrorMessage('\n' + testCase.name +
' VerifyProjection failure for ' + domainObjectName +
' relationship ' + relationshipName +
' expected relationship length: ' + expectedRelationship.length +
' actual relationship length: ' + actualRelationship.length);
}
} else {
testCase.appendErrorMessage('\n' + testCase.name +
' VerifyProjection failure for ' + domainObjectName +
' relationship ' + relationshipName +
' actual relationship is not an array: ' + actualRelationship);
}
} else {
// expected value is an object
if ((expectedRelationship === undefined && actualRelationship !== undefined) ||
(expectedRelationship === null && actualRelationship !== null) ||
(expectedRelationship !== undefined & actualRelationship === undefined) ||
(expectedRelationship !== null && actualRelationship === null)) {
// error
testCase.appendErrorMessage('\n' + testCase.name +
' VerifyProjection failure for ' + domainObjectName +
' relationship ' + relationshipName +
'\nexpected relationship: ' + util.inspect(expectedRelationship) +
'\nactual relationship: ' + util.inspect(actualRelationship));
} else {
if (!((expectedRelationship === undefined && actualRelationship === undefined) ||
(expectedRelationship === null && actualRelationship === null))) {
// we need to check the values
projectionVerifications.push([projection.relationships[relationshipName],
expectedRelationship, actualRelationship]);
}
}
}
}
while (projectionVerifications.length > 0) {
projectionVerification = projectionVerifications.shift();
projection = projectionVerification[0];
expected = projectionVerification[1];
actual = projectionVerification[2];
domainObjectName = projection.domainObject.prototype.constructor.name;
// check that the actual object exists and is unexpected
if ((expected !== null && actual === null) ||
(expected !== undefined && actual === undefined)) {
testCase.appendErrorMessage('\n' + testCase.name +
' VerifyProjection failure for ' + domainObjectName +
'\nexpected: ' + util.inspect(expected) + '\nactual: ' + actual);
continue;
}
// check for null and undefined
if ((expected === undefined && actual === undefined) ||
(expected === null && actual === null)) {
continue;
}
// verify the fields first
projection.fields.forEach(verifyProjectionField);
// now verify the relationships (iteratively)
if (projection.relationships) {
Object.keys(projection.relationships).forEach(verifyProjectionRelationships);
}
verifyOneProjection();
}
}
// verifyProjection starts here
projectionVerifications = [[p, e, a]];
verifyOneProjection();
}
mapShop();
// Simple Customer projection
var simpleShoppingCartProjection = new jones.Projection(ShoppingCart).
addField('id');
var simpleCustomerProjection = new jones.Projection(Customer).
addFields('id', 'unikey', 'firstName', 'lastName').
addRelationship('shoppingCart', simpleShoppingCartProjection);
// Complex Customer projection
var itemProjection = new jones.Projection(Item)
.addFields('id', 'description');
//LineItem -> Item
var lineItemProjection = new jones.Projection(LineItem)
.addFields('line', ['quantity', 'itemid'])
.addRelationship('item', itemProjection);
//ShoppingCart -> LineItem -> Item
var shoppingCartProjection = new jones.Projection(ShoppingCart)
.addFields('id')
.addRelationship('lineItems', lineItemProjection);
//Discount
var discountProjection = new jones.Projection(Discount)
.addField('id', 'description');
//Shipment
var shipmentProjection = new jones.Projection(Shipment)
.addField('id', 'value');
//Customer -> ShoppingCart -> LineItem -> Item
// \-> Discount
// \-> Shipment
var complexCustomerProjection = new jones.Projection(Customer)
.addFields('id', 'unikey', 'firstName', 'lastName')
.addRelationship('shoppingCart', shoppingCartProjection)
.addRelationship('discounts', discountProjection)
.addRelationship('shipments', shipmentProjection);
// Complex Discount projection
var discountItemProjection = new jones.Projection(Item)
.addFields('id', 'description');
//LineItem -> Item
var discountLineItemProjection = new jones.Projection(LineItem)
.addFields('line', ['quantity', 'itemid'])
.addRelationship('item', discountItemProjection);
//ShoppingCart -> LineItem -> Item
var discountShoppingCartProjection = new jones.Projection(ShoppingCart)
.addFields('id')
.addRelationship('lineItems', discountLineItemProjection);
//Customer -> ShoppingCart -> LineItem -> Item
var discountCustomerProjection = new jones.Projection(Customer)
.addField('id', 'firstName', 'lastName')
.addRelationship('shoppingCart', discountShoppingCartProjection);
//Discount -> Customer -> ShoppingCart -> LineItem -> Item
var complexDiscountProjection = new jones.Projection(Discount)
.addField('id', 'description', 'percent')
.addRelationship('customers',discountCustomerProjection);
// Expected results from complex customer queries
var expectedDiscount0 = new Discount(0, 'new customer', 10);
var expectedDiscount1 = new Discount(1, 'good customer', 15);
var expectedDiscount2 = new Discount(2, 'spring sale', 10);
var expectedDiscount3 = new Discount(3, 'internet special', 20);
var expectedDiscount4 = new Discount(4, 'closeout', 50);
var expectedItem10000 = new Item(10000, 'toothpaste');
var expectedItem10001 = new Item(10001, 'razor blade 10 pack');
var expectedItem10002 = new Item(10002, 'deodorant');
var expectedItem10003 = new Item(10003, 'hatchet');
var expectedItem10004 = new Item(10004, 'weed-b-gon');
var expectedItem10005 = new Item(10005, 'cola 24 pack');
var expectedItem10006 = new Item(10006, 'diet cola 24 pack');
var expectedItem10007 = new Item(10007, 'diet root beer 12 pack');
var expectedItem10008 = new Item(10008, 'whole wheat bread');
var expectedItem10009 = new Item(10009, 'raisin bran');
var expectedItem10010 = new Item(10010, 'milk gallon');
var expectedItem10011 = new Item(10011, 'half and half');
var expectedItem10012 = new Item(10012, 'tongue depressor');
var expectedItem10013 = new Item(10013, 'smelling salt');
var expectedItem10014 = new Item(10014, 'holy bible');
var expectedShoppingCart1000 = new ShoppingCart(1000, 100);
expectedShoppingCart1000.lineItems = [
createLineItem(0, 1, 10000, expectedItem10000),
createLineItem(1, 5, 10014, expectedItem10014),
createLineItem(2, 2, 10011, expectedItem10011)
];
var expectedShoppingCart1002 = new ShoppingCart(1002, 102);
expectedShoppingCart1002.lineItems = [
createLineItem(0, 10, 10008, expectedItem10008),
createLineItem(1, 4, 10010, expectedItem10010),
createLineItem(2, 40, 10002, expectedItem10002),
createLineItem(3, 100, 10011, expectedItem10011),
createLineItem(4, 1, 10013, expectedItem10013),
createLineItem(5, 8, 10005, expectedItem10005)
];
var expectedShoppingCart1003 = new ShoppingCart(1003, 103);
expectedShoppingCart1003.lineItems = [];
var expectedShipment10000 = new Shipment(10000, undefined, 120.99);
var expectedShipment10001 = new Shipment(10001, undefined, 130);
var expectedShipment10100 = new Shipment(10100, undefined, 1320.87);
var expectedShipment10102 = new Shipment(10102, undefined, 144.44);
var expectedShipment10200 = new Shipment(10200, undefined, 45.87);
var expectedShipment10201 = new Shipment(10201, undefined, 67.44);
var expectedShipment10202 = new Shipment(10202, undefined, 80.89);
var expectedShipment10203 = new Shipment(10203, undefined, 1045.87);
// expected Customer using complex projection
// Customer -> ShoppingCart -> LineItem -> Item
// \-> Discount
// \-> Shipment
var expectedCustomer100 = new Customer(100, 'Craig', 'Walton');
expectedCustomer100.shoppingCart = expectedShoppingCart1000;
expectedCustomer100.shipments = [expectedShipment10000, expectedShipment10001];
expectedCustomer100.discounts = [expectedDiscount0];
var expectedCustomer101 = new Customer(101, 'Sam', 'Burton');
expectedCustomer101.shoppingCart = null;
expectedCustomer101.shipments = [expectedShipment10100, expectedShipment10102];
expectedCustomer101.discounts = [expectedDiscount1, expectedDiscount3, expectedDiscount4];
var expectedCustomer102 = new Customer(102, 'Wal', 'Greeton');
expectedCustomer102.shoppingCart = expectedShoppingCart1002;
expectedCustomer102.shipments =
[expectedShipment10200, expectedShipment10201, expectedShipment10202, expectedShipment10203];
expectedCustomer102.discounts = [expectedDiscount2];
var expectedCustomer103 = new Customer(103, 'Burn', 'Sexton');
expectedCustomer103.shoppingCart = expectedShoppingCart1003;
expectedCustomer103.shipments = [];
expectedCustomer103.discounts = [expectedDiscount3];
var expectedCustomers =
{'100': expectedCustomer100,'101': expectedCustomer101,'102': expectedCustomer102,'103': expectedCustomer103};
var expectedShipments =
{ '10000': expectedShipment10000, '10001': expectedShipment10001,
'10100': expectedShipment10100, '10102': expectedShipment10102,
'10200': expectedShipment10200, '10201': expectedShipment10201,'10202': expectedShipment10202, '10203': expectedShipment10203};
exports.Customer = Customer;
exports.mapCustomer = mapCustomer;
exports.ShoppingCart = ShoppingCart;
exports.mapShoppingCart = mapShoppingCart;
exports.LineItem = LineItem;
exports.mapLineItem = mapLineItem;
exports.Item = Item;
exports.mapItem = mapItem;
exports.Discount = Discount;
exports.mapDiscount = mapDiscount;
exports.CustomerDiscount = CustomerDiscount;
exports.mapCustomerDiscount = mapCustomerDiscount;
exports.FkDifferentDb = FkDifferentDb;
exports.mapFkDifferentDb = mapFkDifferentDb;
exports.mapShop = mapShop;
exports.verifyFK = verifyFK;
exports.verifyProjection = verifyProjection;
exports.shopDomainObjects = shopDomainObjects;
exports.createLineItem = createLineItem;
exports.Shipment = Shipment;
exports.sortFunction = sortFunction;
exports.complexCustomerProjection = complexCustomerProjection;
exports.complexDiscountProjection = complexDiscountProjection;
exports.expectedCustomers = expectedCustomers;
exports.expectedShipments = expectedShipments;
exports.simpleCustomerProjection = simpleCustomerProjection;