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