| /** |
| * 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]); |
| } |
| } |
| } |