blob: a442ec396ee0e410ee0b30aae1104bf08bb33b33 [file]
/**
* Derby - Class org.apache.derbyTesting.functionTests.tests.lang.SpillHashTest
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.derbyTesting.functionTests.tests.lang;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.BitSet;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseJDBCTestSetup;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
/**
* Test BackingStoreHashtable spilling to disk.
* BackingStoreHashtable is used to implement hash joins, distinct, scroll insensitive cursors,
* outer joins, and the HAVING clause.
*/
public class SpillHashTest extends BaseJDBCTestCase {
private static final String[] prep = {
"create table ta (ca1 integer, ca2 char(200))",
"create table tb (cb1 integer, cb2 char(200))",
"insert into ta(ca1,ca2) values(null, 'Anull')",
"insert into tb(cb1,cb2) values(null, 'Bnull')"
};
private static final String[][] initDupVals = {
{ "0a", "0b"},
{ "1a", "1b"},
{ "2a"}
};
private static final String[][] spillDupVals = {
{},
{ "1c" },
{ "2b" },
{ "3a", "3b", "3c" }
};
private static final int LOTS_OF_ROWS = 10000;
private PreparedStatement joinStmt;
private PreparedStatement distinctStmt;
/**
* Basic constructor.
*/
public SpillHashTest(String name) {
super(name);
}
/**
* Sets the auto commit to false.
*/
protected void initializeConnection(Connection conn) throws SQLException {
conn.setAutoCommit(false);
}
/**
* Returns the implemented tests.
*
* @return An instance of <code>Test</code> with the implemented tests to
* run.
*/
public static Test suite() {
// suite of tests with light load on the tables
BaseTestSuite light = new BaseTestSuite();
// suite of tests with heavy load on the tables
BaseTestSuite heavy = new BaseTestSuite();
light.addTest(new SpillHashTest("testJoinLight"));
light.addTest(new SpillHashTest("testDistinctLight"));
light.addTest(new SpillHashTest("testCursorLight"));
heavy.addTest(new SpillHashTest("testJoinHeavy"));
heavy.addTest(new SpillHashTest("testDistinctHeavy"));
heavy.addTest(new SpillHashTest("testCursorHeavy"));
Test lightSetup = new BaseJDBCTestSetup(light) {
protected void setUp() throws Exception {
super.setUp();
Statement stmt = getConnection().createStatement();
PreparedStatement insA = stmt.getConnection().prepareStatement("insert into ta(ca1,ca2) values(?,?)");
PreparedStatement insB = stmt.getConnection().prepareStatement("insert into tb(cb1,cb2) values(?,?)");
insertDups(insA, insB, initDupVals);
getConnection().commit();
stmt.close();
//System.out.println("2");
}
};
Test heavySetup = new BaseJDBCTestSetup(heavy) {
protected void setUp() throws Exception {
super.setUp();
Statement stmt = getConnection().createStatement();
PreparedStatement insA = stmt.getConnection().prepareStatement("insert into ta(ca1,ca2) values(?,?)");
PreparedStatement insB = stmt.getConnection().prepareStatement("insert into tb(cb1,cb2) values(?,?)");
for (int i = 1; i <= LOTS_OF_ROWS; i++) {
insA.setInt(1, i);
insA.setString(2, ca2Val(i));
insA.executeUpdate();
insB.setInt(1, i);
insB.setString(2, cb2Val(i));
insB.executeUpdate();
if ((i & 0xff) == 0)
stmt.getConnection().commit();
}
insertDups(insA, insB, spillDupVals);
getConnection().commit();
stmt.close();
//System.out.println("3");
}
};
BaseTestSuite mainSuite = new BaseTestSuite();
mainSuite.addTest(lightSetup);
mainSuite.addTest(heavySetup);
return new CleanDatabaseTestSetup(mainSuite) {
protected void decorateSQL(Statement stmt) throws SQLException {
for(int i = 0; i < prep.length; i++) {
stmt.executeUpdate(prep[i]);
}
//System.out.println("1");
}
};
}
protected void setUp() throws Exception {
joinStmt = getConnection().prepareStatement("select ta.ca1, ta.ca2, tb.cb2 from ta, tb where ca1 = cb1");
distinctStmt = getConnection().prepareStatement("select distinct ca1 from ta");
}
protected void tearDown() throws Exception {
joinStmt.close();
distinctStmt.close();
joinStmt = null;
distinctStmt = null;
super.tearDown();
}
public void testJoinLight() throws SQLException {
runJoin(getConnection(), 0, new String[][][] {initDupVals});
}
public void testDistinctLight() throws SQLException {
runDistinct(getConnection(), 0, new String[][][] {initDupVals});
}
public void testCursorLight() throws SQLException {
runCursor(getConnection(), 0, new String[][][] {initDupVals});
}
public void testJoinHeavy() throws SQLException {
runJoin(getConnection(), LOTS_OF_ROWS, new String[][][] {initDupVals, spillDupVals});
}
public void testDistinctHeavy() throws SQLException {
runDistinct(getConnection(), LOTS_OF_ROWS, new String[][][] {initDupVals, spillDupVals});
}
public void testCursorHeavy() throws SQLException {
runCursor(getConnection(), LOTS_OF_ROWS, new String[][][] {initDupVals, spillDupVals});
}
private static void insertDups(PreparedStatement insA,
PreparedStatement insB, String[][] dupVals) throws SQLException {
for (int i = 0; i < dupVals.length; i++) {
insA.setInt(1, -i);
insB.setInt(1, -i);
String[] vals = dupVals[i];
for (int j = 0; j < vals.length; j++) {
insA.setString(2, "A" + vals[j]);
insA.executeUpdate();
insB.setString(2, "B" + vals[j]);
insB.executeUpdate();
}
}
}
private static String ca2Val(int col1Val) {
return "A" + col1Val;
}
private static String cb2Val(int col1Val) {
return "B" + col1Val;
}
private void runJoin(Connection conn, int maxColValue,
String[][][] dupVals) throws SQLException {
int expectedRowCount = maxColValue; // plus expected duplicates, to be counted below
ResultSet rs = joinStmt.executeQuery();
BitSet joinRowFound = new BitSet(maxColValue);
int dupKeyCount = 0;
for (int i = 0; i < dupVals.length; i++) {
if (dupVals[i].length > dupKeyCount)
dupKeyCount = dupVals[i].length;
}
BitSet[] dupsFound = new BitSet[dupKeyCount];
int[] dupCount = new int[dupKeyCount];
for (int i = 0; i < dupKeyCount; i++) {
// count the number of rows with column(1) == -i
dupCount[i] = 0;
for (int j = 0; j < dupVals.length; j++) {
if (i < dupVals[j].length)
dupCount[i] += dupVals[j][i].length;
}
dupsFound[i] = new BitSet(dupCount[i] * dupCount[i]);
expectedRowCount += dupCount[i] * dupCount[i];
}
int count;
for (count = 0; rs.next(); count++) {
int col1Val = rs.getInt(1);
assertFalse("Null in join column.", rs.wasNull());
assertFalse("Invalid value in first join column.", col1Val > maxColValue);
if (col1Val > 0) {
assertFalse("Multiple rows for value " + col1Val, joinRowFound.get(col1Val - 1));
joinRowFound.set(col1Val - 1);
String col2Val = trim(rs.getString(2));
String col3Val = trim(rs.getString(3));
assertFalse("Incorrect value in column 2 or 3 of join.",
!(ca2Val(col1Val).equals(col2Val) && cb2Val(col1Val).equals(col3Val)));
} else {
// col1Val <= 0, there are duplicates in the source tables
int dupKeyIdx = -col1Val;
int col2Idx = findDupVal(rs, 2, 'A', dupKeyIdx, dupVals);
int col3Idx = findDupVal(rs, 3, 'B', dupKeyIdx, dupVals);
if (col2Idx < 0 || col3Idx < 0) {
continue;
}
int idx = col2Idx + dupCount[dupKeyIdx] * col3Idx;
assertFalse("Repeat of row with key value 0", dupsFound[dupKeyIdx].get(idx));
dupsFound[dupKeyIdx].set(idx);
}
}
assertEquals("Incorrect number of rows in join.", expectedRowCount, count);
rs.close();
}
private static int findDupVal(ResultSet rs, int col, char prefix,
int keyIdx, String[][][] dupVals) throws SQLException {
String colVal = rs.getString(col);
if (colVal != null && colVal.length() > 1 || colVal.charAt(0) == prefix) {
colVal = trim(colVal.substring(1));
int dupIdx = 0;
for (int i = 0; i < dupVals.length; i++) {
if (keyIdx < dupVals[i].length) {
for (int j = 0; j < dupVals[i][keyIdx].length; j++, dupIdx++) {
if (colVal.equals(dupVals[i][keyIdx][j]))
return dupIdx;
}
}
}
}
fail("Incorrect value in column " + col + " of join with duplicate keys.");
return -1;
} // end of findDupVal
private static String trim(String str) {
if (str == null)
return str;
return str.trim();
}
private void runDistinct(Connection conn, int maxColValue,
String[][][] dupVals) throws SQLException {
//System.out.println("Running distinct");
ResultSet rs = distinctStmt.executeQuery();
checkAllCa1(rs, false, false, maxColValue, dupVals, "DISTINCT");
rs.close();
}
private static void runCursor(Connection conn, int maxColValue,
String[][][] dupVals) throws SQLException {
//System.out.println("Running scroll insensitive cursor");
DatabaseMetaData dmd = conn.getMetaData();
boolean holdOverCommit = dmd.supportsOpenCursorsAcrossCommit();
Statement stmt;
if (holdOverCommit)
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
ResultSet.HOLD_CURSORS_OVER_COMMIT);
else
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("SELECT ca1 FROM ta");
checkAllCa1(rs, true, holdOverCommit, maxColValue, dupVals,
"scroll insensitive cursor");
rs.close();
}
private static void checkAllCa1(ResultSet rs, boolean expectDups,
boolean holdOverCommit, int maxColValue, String[][][] dupVals,
String label) throws SQLException {
int dupKeyCount = 0;
for (int i = 0; i < dupVals.length; i++) {
if (dupVals[i].length > dupKeyCount)
dupKeyCount = dupVals[i].length;
}
int[] expectedDupCount = new int[dupKeyCount];
int[] dupFoundCount = new int[dupKeyCount];
for (int i = 0; i < dupKeyCount; i++) {
dupFoundCount[i] = 0;
if (!expectDups)
expectedDupCount[i] = 1;
else {
expectedDupCount[i] = 0;
for (int j = 0; j < dupVals.length; j++) {
if (i < dupVals[j].length)
expectedDupCount[i] += dupVals[j][i].length;
}
}
}
BitSet found = new BitSet(maxColValue);
int count = 0;
boolean nullFound = false;
for (count = 0; rs.next();) {
int col1Val = rs.getInt(1);
if (rs.wasNull()) {
if (nullFound) {
fail("Too many nulls returned by " + label);
}
nullFound = true;
continue;
}
assertFalse("Invalid value returned by " + label,
col1Val <= -dupKeyCount || col1Val > maxColValue);
if (col1Val <= 0) {
dupFoundCount[-col1Val]++;
if (!expectDups) {
assertFalse(label + " returned a duplicate.",
dupFoundCount[-col1Val] > 1);
} else {
assertFalse(
label + " returned too many duplicates.",
dupFoundCount[-col1Val] > expectedDupCount[-col1Val]);
}
} else {
assertFalse(label + " returned a duplicate.", found
.get(col1Val));
found.set(col1Val);
count++;
}
if (holdOverCommit) {
rs.getStatement().getConnection().commit();
holdOverCommit = false;
}
}
assertFalse("Incorrect number of rows in " + label,
count != maxColValue);
for (int i = 0; i < dupFoundCount.length; i++) {
assertFalse("A duplicate key row is missing in " + label,
dupFoundCount[i] != expectedDupCount[i]);
}
}
}