org.hsqldb.test.TestTextTable Maven / Gradle / Ivy
Show all versions of sqltool Show documentation
/* Copyright (c) 2001-2021, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.test;
import java.io.IOException;
import java.io.PrintStream;
import java.sql.SQLException;
import org.hsqldb.lib.FileUtil;
import java.sql.Statement;
/** test various text table features
*
* @author [email protected]
*/
public class TestTextTable extends TestBase {
java.sql.Statement m_statement;
java.sql.Connection m_connection;
private class TextTableDescriptor {
private String m_name;
private String m_columnSpec;
private String m_separator;
private String m_separatorSpec;
private Object[][] m_data;
public TextTableDescriptor(String name, String columnSpec,
String separator, String separatorSpec,
Object[][] data) {
m_name = name;
m_columnSpec = columnSpec;
m_separator = separator;
m_separatorSpec = separatorSpec;
m_data = data;
}
public final String getName() {
return m_name;
}
public final String getColumnSpec() {
return m_columnSpec;
}
public final String getSeparator() {
return m_separator;
}
public final String getSeparatorSpec() {
return m_separatorSpec;
}
public final Object[][] getData() {
return m_data;
}
public final Object[][] appendRowData(Object[] rowData) {
Object[][] newData = new Object[m_data.length + 1][rowData.length];
for (int row = 0; row < m_data.length; ++row) {
newData[row] = m_data[row];
}
newData[m_data.length] = rowData;
m_data = newData;
return m_data;
}
/**
* creates a text file as described by this instance
*/
private void createTextFile() {
PrintStream textFile = null;
try {
String completeFileName = m_name + ".csv";
FileUtil.getFileUtil().delete(completeFileName);
textFile = new PrintStream(
FileUtil.getFileUtil().openOutputStreamElementAppend(
completeFileName));
new java.io.File(completeFileName).deleteOnExit();
} catch (IOException ex) {
fail(ex.toString());
}
for (int row = 0; row < m_data.length; ++row) {
StringBuilder buf = new StringBuilder();
int colCount = m_data[row].length;
for (int col = 0; col < colCount; ++col) {
buf.append(m_data[row][col].toString());
if (col + 1 != colCount) {
buf.append(m_separator);
}
}
textFile.println(buf.toString());
}
textFile.close();
}
private String getDataSourceSpec() {
return m_name + ".csv;encoding=UTF-8;fs=" + m_separatorSpec;
}
private void createTable(java.sql.Connection connection)
throws SQLException {
String dropTable = "DROP TABLE \"" + m_name + "\" IF EXISTS;";
String createTable = "CREATE TEXT TABLE \"" + m_name + "\" ( "
+ m_columnSpec + " );";
Statement statement = connection.createStatement();
statement.execute(dropTable);
statement.execute(createTable);
boolean test = isReadOnly(m_name);
String setTableSource = "SET TABLE \"" + m_name + "\" SOURCE \""
+ getDataSourceSpec() + "\"";
statement.execute(setTableSource);
}
}
;
TextTableDescriptor m_products = new TextTableDescriptor("products",
"ID INTEGER PRIMARY KEY, \"name\" VARCHAR(20)", "\t", "\\t",
new Object[][] {
new Object[] {
Integer.valueOf(1), "Apples"
}, new Object[] {
Integer.valueOf(2), "Oranges"
}
});
TextTableDescriptor m_customers = new TextTableDescriptor("customers",
"ID INTEGER PRIMARY KEY," + "\"name\" VARCHAR(50),"
+ "\"address\" VARCHAR(50)," + "\"city\" VARCHAR(50),"
+ "\"postal\" VARCHAR(50)", ";", "\\semi", new Object[][] {
new Object[] {
Integer.valueOf(1), "Food, Inc.", "Down Under", "Melbourne", "509"
}, new Object[] {
Integer.valueOf(2), "Simply Delicious", "Down Under", "Melbourne",
"518"
}, new Object[] {
Integer.valueOf(3), "Pure Health", "10 Fish St.", "San Francisco",
"94107"
}
});
/** Creates a new instance of TestTextTable */
public TestTextTable(String testName) {
super(testName, "jdbc:hsqldb:file:test");
}
/**
* sets up all text files for the test database
*/
private void setupTextFiles() {
m_products.createTextFile();
m_customers.createTextFile();
}
/**
* creates the database tables needed for the test
*/
private void setupDatabase() {
try {
m_connection = newConnection();
m_statement = m_connection.createStatement();
m_products.createTable(m_connection);
m_customers.createTable(m_connection);
} catch (SQLException ex) {
fail(ex.toString());
}
}
public void setUp() throws Exception {
super.setUp();
setupTextFiles();
setupDatabase();
}
protected void tearDown() {
executeStatement("SHUTDOWN");
super.tearDown();
}
/**
* returns the data source definition for a given text table
*/
private String getDataSourceSpec(String tableName) {
String spec = null;
try {
java.sql.ResultSet results = m_statement.executeQuery(
"SELECT DATA_SOURCE_DEFINTION FROM INFORMATION_SCHEMA.SYSTEM_TEXTTABLES "
+ "WHERE TABLE_NAME='" + tableName + "'");
results.next();
spec = results.getString(1);
} catch (SQLException ex) {
fail("getDataSourceSpec(" + tableName + ") failed: "
+ ex.toString());
}
return spec;
}
/**
* determines whether a given table is currently read-only
*/
private boolean isReadOnly(String tableName) {
boolean isReadOnly = true;
try {
java.sql.ResultSet systemTables = m_statement.executeQuery(
"SELECT READ_ONLY FROM INFORMATION_SCHEMA.SYSTEM_TABLES "
+ "WHERE TABLE_NAME='" + m_products.getName() + "'");
systemTables.next();
isReadOnly = systemTables.getBoolean(1);
} catch (SQLException ex) {
fail("isReadOnly(" + tableName + ") failed: " + ex.toString());
}
return isReadOnly;
}
/**
* checks different field separators
*/
private void checkSeparators() {
String[][] separators = new String[][] {
// special separators
new String[] {
";", "\\semi"
}, new String[] {
"\"", "\\quote"
}, new String[] {
" ", "\\space"
}, new String[] {
"'", "\\apos"
},
//new String[] { "\n", "\\n" },
// doesn't work as expected - seems I don't understand how this is intended to work?
new String[] {
"\t", "\\t"
}, new String[] {
"\\", "\\"
},
// some arbitrary separators which need not to be escaped
new String[] {
".", "."
}, new String[] {
"-", "-"
}, new String[] {
"#", "#"
}, new String[] {
",", ","
}
// unicode character
//new String[] { "\u1234", "\\u1234" }
// doesn't work. How do I specify in a FileOutputStream which encoding to use when writing
// strings?
};
for (int i = 0; i < separators.length; ++i) {
String separator = separators[i][0];
String separatorSpec = separators[i][1];
// create the file
String tableName = "customers_" + i;
TextTableDescriptor tempCustomersDesc =
new TextTableDescriptor(tableName,
m_customers.getColumnSpec(),
separator, separatorSpec,
m_customers.getData());
tempCustomersDesc.createTextFile();
try {
tempCustomersDesc.createTable(m_connection);
} catch (Throwable t) {
fail("checkSeparators: separator '" + separatorSpec
+ "' doesn't work: " + t.toString());
}
executeStatement("SET TABLE \"" + tableName + "\" SOURCE OFF");
executeStatement("DROP TABLE \"" + tableName + "\"");
}
}
/**
* verifies the content of a given table is as expected
* @param tableName
* the name of the table whose content is to check
* @param expectedValues
* the values expected in the table
*/
private void verifyTableContent(String tableName,
Object[][] expectedValues) {
String selectStmt = "SELECT * FROM \"" + tableName + "\" ORDER BY ID";
try {
java.sql.ResultSet results = m_statement.executeQuery(selectStmt);
int row = 0;
while (results.next()) {
row = results.getRow();
Object[] expectedRowContent = expectedValues[row - 1];
for (int col = 0; col < expectedRowContent.length; ++col) {
Object expectedValue = expectedRowContent[col];
Object foundValue = results.getObject(col + 1);
assertEquals("table " + tableName + ", row " + row
+ ", column " + col + ":", expectedValue,
foundValue);
}
}
// finally ensure that there are not more rows in the table than expected
assertEquals("table " + tableName + "'s row count: ",
expectedValues.length, row);
} catch (junit.framework.AssertionFailedError e) {
throw e;
} catch (Throwable t) {
fail("verifyTableContent(" + tableName + ") failed with "
+ t.toString());
}
}
/**
* executes a given m_statement
*
* Basically, this method calls m_statement.execute(sql)
,
* but wraps any SQLException
s into a JUnit error.
*/
private void executeStatement(String sql) {
try {
m_statement.execute(sql);
} catch (SQLException ex) {
fail(ex.toString());
}
}
/**
* verifies the initial content of the "products" text table, plus a simple insertion
*/
private void verifyInitialContent() {
verifyTableContent(m_products.getName(), m_products.getData());
verifyTableContent(m_customers.getName(), m_customers.getData());
}
/**
* does some very basic insertion tests
*/
private void checkInsertions() {
// check whether inserting a value succeeds
executeStatement("INSERT INTO \"" + m_products.getName()
+ "\" VALUES ( 3, 'Pears' )");
verifyTableContent(m_products.getName(),
m_products.appendRowData(new Object[] {
Integer.valueOf(3), "Pears"
}));
// check whether the PK constraint works
try {
m_statement.execute("INSERT INTO \"" + m_products.getName()
+ "\" VALUES ( 1, 'Green Apples' )");
fail("PKs do not work as expected.");
} catch (SQLException e) {}
}
/**
* verifies whether implicit and explicit dis/connections from/to the text table source work
* as expected
*/
private void checkSourceConnection() {
String sqlSetTable = "SET TABLE \"" + m_products.getName() + "\"";
// preconditions for the following tests
assertEquals(
"internal error: retrieving the data source does not work properly at all.",
m_products.getDataSourceSpec(),
getDataSourceSpec(m_products.getName()));
assertFalse("internal error: table should not be read-only, initially",
isReadOnly(m_products.getName()));
// disconnect, see if the table behaves well afterwards
executeStatement(sqlSetTable + " SOURCE OFF");
assertEquals(
"Disconnecting a text table should not reset the table source.",
m_products.getDataSourceSpec(),
getDataSourceSpec(m_products.getName()));
assertTrue(
"Disconnecting from the table source should put the table into read-only mode.",
isReadOnly(m_products.getName()));
try {
java.sql.ResultSet tableContent =
m_statement.executeQuery("SELECT * FROM \""
+ m_products.getName() + "\"");
assertFalse("A disconnected table should be empty.",
tableContent.next());
} catch (SQLException ex) {
fail("Selecting from a disconnected table should return an empty result set.");
}
// reconnect, see if the table works as expected then
executeStatement(sqlSetTable + " SOURCE ON");
verifyTableContent(m_products.getName(), m_products.getData());
// check whether dis-/reconnecting a readonly table preserves the readonly-ness
executeStatement(sqlSetTable + " READONLY TRUE");
assertTrue("Setting the table to read-only failed.",
isReadOnly(m_products.getName()));
executeStatement(sqlSetTable + " SOURCE OFF");
assertTrue("Still, a disconnected table should be read-only.",
isReadOnly(m_products.getName()));
executeStatement(sqlSetTable + " SOURCE ON");
assertTrue(
"A reconnected readonly table should preserve its readonly-ness.",
isReadOnly(m_products.getName()));
executeStatement(sqlSetTable + " READONLY FALSE");
assertFalse("Unable to reset the readonly-ness.",
isReadOnly(m_products.getName()));
// check whether setting an invalid data source sets the table to readonly, by
// preserving the data source
try {
// create a malformed file
String fileName = "malformed.csv";
PrintStream textFile = new PrintStream(
FileUtil.getFileUtil().openOutputStreamElementAppend(
fileName));
textFile.println("not a number;some text");
textFile.close();
new java.io.File(fileName).deleteOnExit();
// try setting it as source
String newDataSourceSpec = fileName + ";encoding=UTF-8;fs=\\semi";
try {
m_statement.execute(sqlSetTable + " SOURCE \""
+ newDataSourceSpec + "\"");
fail("a malformed data source was accepted silently.");
} catch (java.sql.SQLException es) { /* that's expected here */
}
// new - a malformed data source assignment by user should not survive
// and should revert to the existing one
assertTrue(
"A table with an invalid data source should fall back to original read-only.",
!isReadOnly(m_products.getName()));
assertEquals(
"A data source which cannot be set should nonetheless be remembered.",
m_products.getDataSourceSpec(),
getDataSourceSpec(m_products.getName()));
// the data source spec should even survive a shutdown
executeStatement("SHUTDOWN");
m_connection = newConnection();
m_statement = m_connection.createStatement();
assertEquals("A data source should survive a database shutdown.",
m_products.getDataSourceSpec(),
getDataSourceSpec(m_products.getName()));
assertTrue(
"After shutdown and DB-reconnect, the table should keepe read-only attribute.",
!isReadOnly(m_products.getName()));
// reconnect after fixing the file
FileUtil.getFileUtil().delete(fileName);
textFile = new PrintStream(
FileUtil.getFileUtil().openOutputStreamElementAppend(
fileName));
textFile.println("1;some text");
textFile.close();
m_statement.execute(sqlSetTable + " SOURCE \"" + newDataSourceSpec
+ "\"");
assertFalse(
"The file was fixed, reconnect was successful, so the table shouldn't be read-only.",
isReadOnly(m_products.getName()));
// finally re-create the proper version of the table for any further tests
m_statement.execute(sqlSetTable + " SOURCE \""
+ m_products.getDataSourceSpec() + "\"");
verifyTableContent(m_products.getName(), m_products.getData());
} catch (junit.framework.AssertionFailedError e) {
throw e;
} catch (Throwable t) {
fail("checkSourceConnection: unable to check invalid data sources, error: "
+ t.toString());
}
}
/**
* basic tests for text files
*/
public void testTextFiles() {
verifyInitialContent();
checkInsertions();
checkSeparators();
checkSourceConnection();
}
public static void main(String[] argv) {
runWithResult(TestTextTable.class, "testTextFiles");
}
}