368 lines
11 KiB
JavaScript

/*
Copyright (c) 2013, 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 udebug = unified_debug.getLogger("lib/read_write.js");
var mysql = require("mysql");
var util = require("util");
/** This is the test for data conversions. It reads using one implementation
* (either a mysql-js adapter or a sql driver) and writes using an
* implementation (either a mysql-js adapter or a sql driver) and
* then compares the results.
*/
/** Create a driver for sql connections.
*/
function SQLDriver(connectionProperties) {
/* Translate our properties to the driver's */
this.props = {};
if(connectionProperties.mysql_socket) {
this.props.SocketPath = connectionProperties.mysql_socket;
} else {
this.props.host = connectionProperties.mysql_host;
this.props.port = connectionProperties.mysql_port;
}
if(connectionProperties.mysql_user) {
this.props.user = connectionProperties.mysql_user;
}
if(connectionProperties.mysql_password) {
this.props.password = connectionProperties.mysql_password;
}
this.props.database = connectionProperties.database;
this.props.debug = connectionProperties.mysql_debug;
udebug.log('SQLDriver using properties: ', util.inspect(this.props));
};
SQLDriver.prototype.connect = function(callback) {
this.connection = mysql.createConnection(this.props);
this.connection.connect(callback);
};
SQLDriver.prototype.remove = function(tableMapping, element, callback) {
// create delete statement and values object
var statement = 'delete from ' + tableMapping.table + ' where ';
var keys = [];
var separator = '';
tableMapping.pkFields.forEach(function(pkField) {
statement += separator;
statement += pkField.columnName;
statement += ' = ?';
keys.push(element[pkField.fieldName]);
separator = ' and ';
});
udebug.log_detail(statement, ' with keys: ', keys);
this.connection.query(statement, keys, callback);
};
SQLDriver.prototype.insert = function(tableMapping, element, callback, rw, index) {
function onInsert(err) {
callback(err, rw, index);
}
var insertClause = 'insert into ' + tableMapping.table + ' (';
var valueClause = ') values (';
var values = [];
var value;
var separator = '';
tableMapping.fields.forEach(function(field) {
value = element[field.fieldName];
// only send defined values (including explicit nulls)
if (typeof(value) !== 'undefined') {
insertClause += separator;
insertClause += field.columnName;
valueClause += separator;
valueClause += '? ';
separator = ', ';
values.push(value);
}
});
insertClause += valueClause;
insertClause += ')';
udebug.log_detail(insertClause, ' with values: ', values);
this.connection.query(insertClause, values, onInsert);
};
SQLDriver.prototype.select = function(tableMapping, element, callback, rw, index) {
function onSelect(err, results) {
callback(err, results[0], rw, index);
}
var selectClause = 'select ';
var whereClause = ' from ' + tableMapping.table + ' where ';
var keys = [];
var separator = '';
// append ", columnName as fieldName"
tableMapping.fields.forEach(function(field) {
selectClause += separator;
selectClause += field.columnName;
selectClause += ' as ';
selectClause += field.fieldName;
separator = ', ';
});
separator = '';
// append ", pkField = ?"
tableMapping.pkFields.forEach(function(pkField) {
whereClause += separator;
whereClause += pkField.columnName;
whereClause += ' = ?';
keys.push(element[pkField.fieldName]);
separator = ' and ';
});
selectClause += whereClause;
udebug.log_detail(selectClause, ' with keys: ', keys);
this.connection.query(selectClause, keys, onSelect);
};
/** Augment the table mapping with additional metadata to facilitate creating
* SQL from a domain object. Primary key columns will be identified with the
* column name and corresponding field name.
*/
function augmentTableMapping(tableMapping, tableMetadata) {
// create list of primary key columns
var pkColumns = [];
tableMetadata.columns.forEach(function(column) {
if (column.isInPrimaryKey) {
pkColumns.push(column.name);
}
});
tableMapping.pkFields = [];
tableMapping.fields.forEach(function(field) {
if (pkColumns.indexOf(field.columnName) !== -1) {
tableMapping.pkFields.push({fieldName: field.fieldName, columnName: field.columnName});
}
});
udebug.log('augmentTableMapping ', tableMapping.pkFields);
};
/** Construct a new ReadWrite for a write implementation,
* a read implementation, and test data.
* The write implementation can be either an adapter or a driver.
* The read implementation can be either an adapter or a driver.
* The test data can be a domain object or a plain object.
*
*/
var ReadWrite = function(testCase, tableNameOrConstructor, data, session, SQLDriver) {
this.testCase = testCase;
this.tableNameOrConstructor = tableNameOrConstructor;
this.data = data;
this.session = session;
this.SQLDriver = SQLDriver;
this.numberChecked = 0;
this.numberRemoved = 0;
};
/** Set up the structure for running the tests:
* Make sure the data object is an array.
* Get the table tableMapping for the domain object or table.
* Connect to the sql driver.
* Get the tableMetadata for the domain object or table.
* Delete all rows corresponding to the data.
*/
ReadWrite.prototype.setUp = function(callback) {
var rw = this;
function onRemove(err) {
if (err) {
// delete will return 02000 if no row deleted
if (err.sqlstate !== '02000') {
rw.testCase.appendErrorMessage(util.inspect(err));
}
}
if (++rw.numberRemoved == rw.data.length) {
// continue if no errors
if (rw.testCase.hasNoErrors()) {
callback();
} else {
rw.testCase.failOnError();
}
}
}
function onTableMetadata(err, tableMetadata) {
if (err) {
rw.testCase.appendErrorMessage('sql driver failed to connect.' + err);
rw.sqlDriver = undefined;
rw.testCase.fail();
} else {
rw.tableMetadata = tableMetadata;
augmentTableMapping(rw.tableMapping, rw.tableMetadata);
// delete all rows corresponding to data
rw.data.forEach(function(element) {
rw.remove(element, onRemove);
});
}
}
function onConnect(err) {
if (err) {
rw.testCase.appendErrorMessage('sql driver failed to connect.' + err);
rw.sqlDriver = undefined;
rw.testCase.fail();
} else {
// get metadata for table
rw.session.getTableMetadata(rw.tableMapping.database, rw.tableMapping.table, onTableMetadata);
}
}
function onTableMapping(err, tableMapping) {
if (err) {
rw.testCase.appendErrorMessage('failed to get table mapping.' + err);
rw.testCase.fail();
} else {
rw.tableMapping = tableMapping;
udebug.log('got mapping for ', rw.tableMapping.table);
// set up sql driver
if (rw.sqlDriver === undefined) {
rw.sqlDriver = new SQLDriver(global.test_conn_properties);
rw.sqlDriver.connect(onConnect);
} else {
console.log('already set up sql driver')
// already set up sql driver
onConnect(null);
}
}
}
// setUp starts here
if (!Array.isArray(rw.data)) {
rw.data = [rw.data];
}
rw.session.getMapping(rw.tableNameOrConstructor, onTableMapping);
};
ReadWrite.prototype.incrementCheckCountAndExit = function() {
if (++this.numberChecked == this.data.length) {
this.testCase.failOnError();
}
};
ReadWrite.prototype.removeAdapter = function(element, onRemove) {
this.session.remove(this.tableNameOrConstructor, element, onRemove);
};
ReadWrite.prototype.removeSQL = function(element, onRemove) {
this.sqlDriver.remove(this.tableMapping, element, onRemove);
};
ReadWrite.prototype.checkResult = function(err, result, rw, index) {
udebug.log('checkResult', err, rw.data, result, index);
var rw = rw;
if (err) {
rw.testCase.appendErrorMessage('checkResult err on read: ', err);
} else {
if (typeof(rw.tableNameOrConstructor) === 'string') {
// table name; check properties in data against properties in result
var x;
var data = rw.data[index];
for (x in data) {
if (data.hasOwnProperty(x)) {
rw.testCase.errorIfNotEqual('mismatch in ' + x, data[x], result[x]);
}
}
} else {
// constructor
}
}
rw.incrementCheckCountAndExit();
};
/** Write data to the table or constructor.
* @param callback (err, readWrite)
* @return nothing
*/
ReadWrite.prototype.writeAdapter = function(callback) {
var rw = this;
// write all elements of data
var index = 0;
rw.data.forEach(function(element) {
rw.session.persist(rw.tableNameOrConstructor, element, callback, rw, index++);
});
};
ReadWrite.prototype.writeSQL = function(callback) {
var rw = this;
var index = 0;
rw.data.forEach(function(element) {
rw.sqlDriver.insert(rw.tableMapping, element, callback, rw, index++);
});
};
ReadWrite.prototype.readAdapter = function(err, rw, index) {
if (err) {
rw.testCase.appendErrorMessage('readAdapter err on write: ' + err);
rw.incrementCheckCountAndExit();
} else {
rw.session.find(rw.tableNameOrConstructor, rw.data[index],
rw.checkResult, rw, index);
}
};
ReadWrite.prototype.readSQL = function(err, rw, index) {
if (err) {
rw.testCase.appendErrorMessage('readSQL err on write: ' + err);
rw.incrementCheckCountAndExit();
} else {
rw.sqlDriver.select(rw.tableMapping, rw.data[index],
rw.checkResult, rw, index);
}
};
ReadWrite.prototype.writeAdapterReadAdapter = function() {
var rw = this;
rw.remove = rw.removeAdapter;
function run() {
rw.writeAdapter(rw.readAdapter);
}
rw.setUp(run);
};
ReadWrite.prototype.writeAdapterReadSQL = function() {
var rw = this;
rw.remove = rw.removeAdapter;
function run() {
rw.writeAdapter(rw.readSQL);
}
rw.setUp(run);
};
ReadWrite.prototype.writeSQLReadAdapter = function() {
var rw = this;
rw.remove = rw.removeSQL;
function run() {
rw.writeSQL(rw.readAdapter);
}
rw.setUp(run);
};
ReadWrite.prototype.writeSQLReadSQL = function() {
var rw = this;
rw.remove = rw.removeSQL;
function run() {
rw.writeSQL(rw.readSQL);
}
rw.setUp(run);
};
exports.ReadWrite = ReadWrite;