blob: 05df1bb523a970188247f44082a88614860791dc [file] [log] [blame]
/*
*
* Derby - Class org.apache.derbyTesting.functionTests.lang.StatementPlanCacheTest
*
* 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.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
/**
* Tests statement plan caching.
* <BR>
* Size of the cache can be configured by derby.language.statementCacheSize.
* derby.language.statementCacheSize.
* <BR>
* The statement cache can be viewed using the diagnostic table
* SYSCS_DIAG.STATEMENT_CACHE
* <BR>
* The test also uses the fact that toString() for a Derby embedded
* prepared statement returns a string identifier that matches the
* ID column of SYSCS_DIAG.STATEMENT_CACHE.
*
*/
public class StatementPlanCacheTest extends BaseJDBCTestCase {
private static int CACHE_SIZE;
private int statmentCacheSize;
private PreparedStatement cacheInPlanPS_ID;
private PreparedStatement cacheInPlanPS_TEXT;
public StatementPlanCacheTest(String name) {
super(name);
statmentCacheSize = CACHE_SIZE;
}
/**
* Runs in embedded only since it's testing the server side cache.
*/
public static Test suite() {
BaseTestSuite suite = new BaseTestSuite("StatementPlanCacheTest");
CACHE_SIZE = 100; // default cache size
suite.addTest(baseSuite("default"));
suite.addTest(suiteWithSizeSet(5));
suite.addTest(suiteWithSizeSet(140));
// no caching
suite.addTest(DatabasePropertyTestSetup.singleProperty(
new StatementPlanCacheTest("noCachingTest"),
"derby.language.statementCacheSize", "0", true));
return new CleanDatabaseTestSetup(suite);
}
private static Test suiteWithSizeSet(int cacheSize)
{
// Sets up the cache size picked up by the constructor of this class
CACHE_SIZE = cacheSize;
String cs = Integer.toString(cacheSize);
return DatabasePropertyTestSetup.singleProperty(
baseSuite(cs),
"derby.language.statementCacheSize", cs, true);
}
private static Test baseSuite(String name) {
BaseTestSuite suite = new BaseTestSuite(
"StatementPlanCacheTest:derby.language.statementCacheSize=" + name);
suite.addTestSuite(StatementPlanCacheTest.class);
return suite;
}
/**
* Prepare the statement that sees if a statement given its
* ID is in the cache. We hold onto it so that the statement
* cache can be emptied and executing this will not alter
* the state of cache.
* Empty the statement cache so that each fixture starts
* out with an empty cache.
*/
protected void setUp() throws SQLException {
cacheInPlanPS_ID = prepareStatement(
"SELECT COUNT(ID) FROM SYSCS_DIAG.STATEMENT_CACHE WHERE ID = ?");
cacheInPlanPS_TEXT = prepareStatement(
"SELECT COUNT(ID) FROM SYSCS_DIAG.STATEMENT_CACHE WHERE " +
"SCHEMANAME = ? AND SQL_TEXT = ?");
emptyStatementCache();
}
protected void tearDown() throws Exception {
cacheInPlanPS_ID.close();
cacheInPlanPS_ID = null;
cacheInPlanPS_TEXT.close();
cacheInPlanPS_TEXT = null;
super.tearDown();
}
/**
* Check that when the cache size is set to zero that
* no caching takes place. Tests with Statement, PreparedStatement
* and CallableStatement.
*/
public void noCachingTest() throws SQLException
{
String schema = this.getTestConfiguration().getUserName();
String sql = "VALUES 1";
Statement s = createStatement();
s.executeQuery(sql).close();
s.close();
assertFalse(sql, isPlanInCache(schema, sql));
prepareStatement(sql).close();
assertFalse(sql, isPlanInCache(schema, sql));
sql = "CALL SYSCS_UTIL.SYSCS_SET_STATISTICS_TIMING(0)";
prepareCall(sql).close();
assertFalse(sql, isPlanInCache(schema, sql));
}
/**
* Check that the same plan can be shared across
* Statement, PreparedStatement and CallableStatement.
*/
public void testAcrossStatementObjects() throws SQLException
{
String schema = this.getTestConfiguration().getUserName();
String sql = sql = "CALL SYSCS_UTIL.SYSCS_SET_STATISTICS_TIMING(0)";
Statement s = createStatement();
s.execute(sql);
s.close();
assertTrue(sql, isPlanInCache(schema, sql));
PreparedStatement ps = prepareStatement(sql);
assertTrue(sql, isPlanInCache(ps));
CallableStatement cs = prepareCall(sql);
assertTrue(sql, isPlanInCache(cs));
// Check the prepared statement matches the callable
assertEquals(ps.toString(), cs.toString());
ps.close();
cs.close();
}
/**
* Test that statements that fail to compile do not end up in the cache.
*/
public void testCompileFailuresNotInCache() throws SQLException
{
String schema = this.getTestConfiguration().getUserName();
// Parse error
String sql = "TO BE OR NOT TO BE";
assertCompileError("42X01", sql);
assertFalse(sql, isPlanInCache(schema, sql));
// Valid tokens but missing elements
sql = "CREATE PROCEDURE BAD_PROC() EXTERNAL NAME 'lll' LANGUAGE JAVA";
assertCompileError("42X01", sql);
assertFalse(sql, isPlanInCache(schema, sql));
// Bind error
sql = "SELECT * FROM NO_SUCH_TABLE_EXISTS";
assertCompileError("42X05", sql);
assertFalse(sql, isPlanInCache(schema, sql));
}
/**
* Test statement caching according to the size of the cache
* using PreparedStatement.
*
*/
public void testPreparedStatementPlanCaching() throws SQLException
{
checkPreparedPlanInCache(statmentCacheSize, "VALUES &");
}
/**
* Test statement caching according to the size of the cache
* using CallableStatement.
*
*/
public void testCallableStatementPlanCaching() throws SQLException
{
checkPreparedPlanInCache(statmentCacheSize,
"CALL SYSCS_UTIL.SYSCS_SET_STATISTICS_TIMING(&)");
}
/**
* Compile a number of statements based upon the sqlbase
* replacing the and with a number increasing from 0 to number - 1
*
* Uses PreparedStatement unless sqlbase starts with CALL then
* CallableStatement is used.
*
* Asserts that the statements appear in the cache and that recompiling
* it picks up the same plan.
*/
private void checkPreparedPlanInCache(int number,
String sqlBase) throws SQLException
{
boolean isCall = sqlBase.startsWith("CALL ");
String[] sql = new String[number];
String[] firstCompileID = new String[sql.length];
for (int i = 0; i < firstCompileID.length; i++ )
{
sql[i] = getSQL(sqlBase, i);
PreparedStatement ps = isCall ?
prepareCall(sql[i]) : prepareStatement(sql[i]);
assertTrue(sql[i], isPlanInCache(ps));
firstCompileID[i] = ps.toString();
ps.close();
}
for (int i = 0; i < firstCompileID.length; i++ )
{
// Check caching is across statements
PreparedStatement ps = isCall ?
prepareCall(sql[i]) : prepareStatement(sql[i]);
PreparedStatement ps2 = isCall ?
prepareCall(sql[i]) : prepareStatement(sql[i]);
assertTrue(sql[i], isPlanInCache(ps));
assertEquals(sql[i], firstCompileID[i], ps.toString());
assertEquals(sql[i], ps.toString(), ps2.toString());
ps.close();
ps2.close();
// Check the caching is across connections
Connection c2 = openDefaultConnection();
PreparedStatement psD = isCall ?
c2.prepareCall(sql[i]) : c2.prepareStatement(sql[i]);
assertEquals(sql[i], firstCompileID[i], psD.toString());
psD.close();
c2.close();
}
String schema = this.getTestConfiguration().getUserName();
// Now check that futher statements throw out existing cache entries.
for (int n = firstCompileID.length; n < firstCompileID.length*2; n++)
{
// Compile a new statement, ends up in cache.
String sqlN = getSQL(sqlBase, n);
PreparedStatement ps = isCall ?
prepareCall(sqlN) : prepareStatement(sqlN);
assertTrue(sqlN, isPlanInCache(ps));
ps.close();
}
// Can only assume some of the original statements will
// have been thrown out.
boolean thrownOut = false;
for (int i = 0; i < sql.length; i++)
{
if (isPlanInCache(schema, sql[i]))
continue;
thrownOut = true;
break;
}
assertTrue("Expect a plan to thrown out", thrownOut);
}
private static String getSQL(String sqlBase, int i)
{
StringBuffer sb = new StringBuffer();
int rp = sqlBase.indexOf('&');
sb.append(sqlBase.substring(0, rp));
sb.append(i);
if (rp+1 < sqlBase.length())
sb.append(sqlBase.substring(rp+1));
return sb.toString();
}
private boolean isPlanInCache(PreparedStatement ps) throws SQLException {
cacheInPlanPS_ID.setString(1, ps.toString());
ResultSet rs = cacheInPlanPS_ID.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
assertTrue("Statement in cache multiple times ", count <= 1);
return count == 1;
}
private boolean isPlanInCache(String schema, String sql) throws SQLException {
cacheInPlanPS_TEXT.setString(1, schema);
cacheInPlanPS_TEXT.setString(2, sql);
ResultSet rs = cacheInPlanPS_TEXT.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
assertTrue("Statement in cache multiple times ", count <= 1);
cacheInPlanPS_TEXT.clearParameters();
return count == 1;
}
}