368 lines
11 KiB
JavaScript
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;
|