1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18package tests.java.sql; 19 20import SQLite.Database; 21import SQLite.Function; 22import SQLite.FunctionContext; 23import dalvik.annotation.TestTargets; 24import dalvik.annotation.TestLevel; 25import dalvik.annotation.TestTargetNew; 26import dalvik.annotation.TestTargetClass; 27 28import junit.extensions.TestSetup; 29import junit.framework.Test; 30import junit.framework.TestCase; 31import junit.framework.TestSuite; 32 33import tests.support.Support_SQL; 34 35import java.sql.CallableStatement; 36import java.sql.Connection; 37import java.sql.PreparedStatement; 38import java.sql.ResultSet; 39import java.sql.SQLException; 40import java.sql.Statement; 41import java.util.Collection; 42import java.util.Collections; 43import java.util.HashSet; 44 45 46/** 47 * Functional test for the Statement.setQueryTimeout() method. Adopted from 48 * Apache Derby project (Apache License 2.0). 49 * 50 * TODO Test requires transaction isolation to be supported. => Ticket 69 51 * 52 * This test consists of four parts: 1. Executes a SELECT 53 * query in 4 different threads concurrently. The query calls a user-defined, 54 * server-side function which delays the execution, so that it takes several 55 * seconds even though the data volume is really low. The fetch operations take 56 * longer time than the timeout value set. Hence, this part tests getting 57 * timeouts from calls to ResultSet.next(). Two connections are used, two 58 * threads execute their statement in the context of one connection, the other 59 * two threads in the context of the other connection. Of the 4 threads, only 60 * one executes its statement with a timeout value. This way, the test ensures 61 * that the correct statement is affected by setQueryTimeout(), regardless of 62 * what connection/transaction it and other statements are executed in the 63 * context of. 64 * 65 * 2. Executes an INSERT query in multiple threads. This part tests 66 * getting timeouts from calls to Statement.execute(). Each thread executes the 67 * query in the context of a separate connection. There is no point in executing 68 * multiple statements on the same connection; since only one statement per 69 * connection executes at a time, there will be no interleaving of execution 70 * between them (contrary to the first part of this test, where calls to 71 * ResultSet.next() may be interleaved between the different threads). Half of 72 * the threads execute their statement with a timeout value set, this is to 73 * verify that the correct statements are affected by the timeout, while the 74 * other statements execute to completion. 75 * 3. Sets an invalid (negative) 76 * timeout. Verifies that the correct exception is thrown. 77 * 4. Tests that the query timeout value is not forgotten after the execution of a statement. 78 */ 79@TestTargetClass(Statement.class) 80public class QueryTimeoutTest extends TestCase { 81 82 private static Statement statement; 83 84 private static final int TIMEOUT = 1; // In seconds 85 private static final int CONNECTIONS = 100; 86 87 private static Connection[] connections = new Connection[CONNECTIONS]; 88 89 private static void printSQLException(SQLException e) { 90 while (e != null) { 91 e.printStackTrace(); 92 e = e.getNextException(); 93 } 94 } 95 96 /** 97 * This Exception class is used for getting fail-fast behaviour in this 98 * test. There is no point in wasting cycles running a test to the end when 99 * we know that it has failed. In order to enable chaining of exceptions in 100 * J2ME, this class defines its own "cause", duplicating existing 101 * functionality in J2SE. 102 */ 103 private static class TestFailedException extends Exception { 104 private Throwable cause; 105 106 public TestFailedException(Throwable t) { 107 super(); 108 cause = t; 109 } 110 111 public TestFailedException(String message) { 112 super(message); 113 cause = null; 114 } 115 116 public TestFailedException(String message, Throwable t) { 117 super(message); 118 cause = t; 119 } 120 121 public String toString() { 122 if (cause != null) { 123 return super.toString() + ": " + cause.toString(); 124 } else { 125 return super.toString(); 126 } 127 } 128 129 public void printStackTrace() { 130 super.printStackTrace(); 131 if (cause != null) { 132 if (cause instanceof SQLException) { 133 QueryTimeoutTest.printSQLException((SQLException) cause); 134 } else { 135 cause.printStackTrace(); 136 } 137 } 138 } 139 } 140 141 /** 142 * Used for executing the SQL statements for setting up this test (the 143 * preparation phase). The queries testing setQueryTimeout() are run by the 144 * StatementExecutor class. 145 */ 146 private static void exec(Connection connection, String queryString, 147 Collection ignoreExceptions) throws TestFailedException { 148 Statement statement = null; 149 try { 150 statement = connection.createStatement(); 151 System.out.println(" Executing "+queryString); 152 statement.execute(queryString); 153 } catch (SQLException e) { 154 String sqlState = e.getSQLState(); 155 if (!ignoreExceptions.contains(sqlState)) { 156 throw new TestFailedException(e); // See finally block below 157 } 158 } finally { 159 if (statement != null) { 160 try { 161 statement.close(); 162 } catch (SQLException ee) { 163 // This will discard an exception possibly thrown above :-( 164 // But we don't worry too much about this, since: 165 // 1. This is just a test 166 // 2. We don't expect close() to throw 167 // 3. If it does, this will be inspected by a developer 168 throw new TestFailedException(ee); 169 } 170 } 171 } 172 } 173 174 // Convenience method 175 private static void exec(Connection connection, String queryString) 176 throws TestFailedException { 177 exec(connection, queryString, Collections.EMPTY_SET); 178 } 179 180 private static void dropTables(Connection conn, String tablePrefix) 181 throws TestFailedException { 182 Collection ignore = new HashSet(); 183 //ignore.add("42Y55"); 184 185 exec(conn, "drop table if exists " + tablePrefix + "_orig;", ignore); 186 exec(conn, "drop table if exists " + tablePrefix + "_copy;", ignore); 187 } 188 189 private static void prepareTables(Connection conn, String tablePrefix) 190 throws TestFailedException { 191 System.out.println("Initializing tables with prefix " + tablePrefix); 192 193 dropTables(conn, tablePrefix); 194 195 exec(conn, "create table " + tablePrefix + "_orig (a int)"); 196 197 exec(conn, "create table " + tablePrefix + "_copy (a int)"); 198 199 for (int i = 0; i < 7; i++) { 200 exec(conn, "insert into " + tablePrefix + "_orig" 201 + " values ("+i+");"); 202 } 203 } 204 205 private static String getFetchQuery(String tablePrefix) { 206 /** 207 * The reason for using the mod function here is to force at least one 208 * invocation of ResultSet.next() to read more than one row from the 209 * table before returning. This is necessary since timeout is checked 210 * only when reading rows from base tables, and when the first row is 211 * read, the query still has not exceeded the timeout. 212 */ 213 return "select a from " + tablePrefix 214 + "_orig where mod(DELAY(1,a),3)=0"; 215 } 216 217 private static String getExecQuery(String tablePrefix) { 218 return "insert into " + tablePrefix + "_copy select a from " 219 + tablePrefix + "_orig where DELAY(1,1)=1"; 220 } 221 222 private static class StatementExecutor extends Thread { 223 private PreparedStatement statement; 224 private boolean doFetch; 225 private int timeout; 226 private SQLException sqlException; 227 private String name; 228 private long highestRunTime; 229 230 public StatementExecutor(PreparedStatement statement, boolean doFetch, 231 int timeout) { 232 this.statement = statement; 233 this.doFetch = doFetch; 234 this.timeout = timeout; 235 highestRunTime = 0; 236 sqlException = null; 237 if (timeout > 0) { 238 try { 239 statement.setQueryTimeout(timeout); 240 } catch (SQLException e) { 241 sqlException = e; 242 } 243 } 244 } 245 246 private void setHighestRunTime(long runTime) { 247 synchronized (this) { 248 highestRunTime = runTime; 249 } 250 } 251 252 public long getHighestRunTime() { 253 synchronized (this) { 254 return highestRunTime; 255 } 256 } 257 258 private boolean fetchRow(ResultSet resultSet) throws SQLException { 259 long startTime = System.currentTimeMillis(); 260 boolean hasNext = resultSet.next(); 261 long endTime = System.currentTimeMillis(); 262 long runTime = endTime - startTime; 263 if (runTime > highestRunTime) setHighestRunTime(runTime); 264 return hasNext; 265 } 266 267 public void run() { 268 if (sqlException != null) return; 269 270 ResultSet resultSet = null; 271 272 try { 273 if (doFetch) { 274 long startTime = System.currentTimeMillis(); 275 resultSet = statement.executeQuery(); 276 long endTime = System.currentTimeMillis(); 277 setHighestRunTime(endTime - startTime); 278 while (fetchRow(resultSet)) { 279 yield(); 280 } 281 } else { 282 long startTime = System.currentTimeMillis(); 283 statement.execute(); 284 long endTime = System.currentTimeMillis(); 285 setHighestRunTime(endTime - startTime); 286 } 287 } catch (SQLException e) { 288 synchronized (this) { 289 sqlException = e; 290 } 291 } finally { 292 if (resultSet != null) { 293 try { 294 resultSet.close(); 295 } catch (SQLException ex) { 296 if (sqlException != null) { 297 System.err.println("Discarding previous exception"); 298 sqlException.printStackTrace(); 299 } 300 sqlException = ex; 301 } 302 } 303 } 304 } 305 306 public SQLException getSQLException() { 307 synchronized (this) { 308 return sqlException; 309 } 310 } 311 } 312 313 /** 314 * This method compares a thrown SQLException's SQLState value to an 315 * expected SQLState. If they do not match, a TestFailedException is thrown 316 * with the given message string. 317 */ 318 private static void expectException(String expectSqlState, 319 SQLException sqlException, String failMsg) 320 throws TestFailedException { 321 if (sqlException == null) { 322 throw new TestFailedException(failMsg); 323 } else { 324 String sqlState = sqlException.getSQLState(); 325 if (!expectSqlState.equals(sqlState)) { 326 throw new TestFailedException(sqlException); 327 } 328 } 329 } 330 331 // A convenience method which wraps a SQLException 332 private static PreparedStatement prepare(Connection conn, String query) 333 throws TestFailedException { 334 try { 335 return conn.prepareStatement(query); 336 } catch (SQLException e) { 337 throw new TestFailedException(e); 338 } 339 } 340 341 /** 342 * Part 1 of this test. 343 */ 344 @TestTargetNew( 345 level = TestLevel.PARTIAL_COMPLETE, 346 notes = "Testing timeout with fetch operations", 347 method = "setQueryTimeout", 348 args = {int.class} 349 ) 350 public static void testTimeoutWithFetch() throws TestFailedException { 351 System.out.println("Testing timeout with fetch operations"); 352 353 Connection conn1 = connections[0]; 354 Connection conn2 = connections[1]; 355 356 try { 357 conn1.setAutoCommit(false); 358 conn2.setAutoCommit(false); 359 } catch (SQLException e) { 360 throw new TestFailedException("Unexpected Exception", e); 361 } 362 363 // The idea with these 4 statements is as follows: 364 // A - should time out 365 // B - different stmt on the same connection; should NOT time out 366 // C - different stmt on different connection; should NOT time out 367 // D - here just to create equal contention on conn1 and conn2 368 369 PreparedStatement statementA = prepare(conn1, getFetchQuery("t")); 370 PreparedStatement statementB = prepare(conn1, getFetchQuery("t")); 371 PreparedStatement statementC = prepare(conn2, getFetchQuery("t")); 372 PreparedStatement statementD = prepare(conn2, getFetchQuery("t")); 373 374 StatementExecutor[] statementExecutor = new StatementExecutor[4]; 375 statementExecutor[0] = new StatementExecutor(statementA, true, TIMEOUT); 376 statementExecutor[1] = new StatementExecutor(statementB, true, 0); 377 statementExecutor[2] = new StatementExecutor(statementC, true, 0); 378 statementExecutor[3] = new StatementExecutor(statementD, true, 0); 379 380 for (int i = 3; i >= 0; --i) { 381 statementExecutor[i].start(); 382 } 383 384 for (int i = 0; i < 4; ++i) { 385 try { 386 statementExecutor[i].join(); 387 } catch (InterruptedException e) { 388 throw new TestFailedException("Should never happen", e); 389 } 390 } 391 392 /** 393 * Actually, there is no guarantee that setting a query timeout for a 394 * statement will actually cause a timeout, even if execution of the 395 * statement takes longer than the specified timeout. However, these 396 * queries execute significantly longer than the specified query 397 * timeout. Also, the cancellation mechanism implemented should be quite 398 * responsive. In sum, we expect the statement to always time out. If it 399 * does not time out, however, we print the highest execution time for 400 * the query, as an assistance in determining why it failed. Compare the 401 * number to the TIMEOUT constant in this class (note that the TIMEOUT 402 * constant is in seconds, while the execution time is in milliseconds). 403 */ 404 expectException("XCL52", statementExecutor[0].getSQLException(), 405 "fetch did not time out. Highest execution time: " 406 + statementExecutor[0].getHighestRunTime() + " ms"); 407 408 System.out.println("Statement 0 timed out"); 409 410 for (int i = 1; i < 4; ++i) { 411 SQLException sqlException = statementExecutor[i].getSQLException(); 412 if (sqlException != null) { 413 throw new TestFailedException("Unexpected exception in " + i, 414 sqlException); 415 } 416 System.out.println("Statement " + i + " completed"); 417 } 418 419 try { 420 statementA.close(); 421 statementB.close(); 422 statementC.close(); 423 statementD.close(); 424 conn1.commit(); 425 conn2.commit(); 426 } catch (SQLException e) { 427 throw new TestFailedException(e); 428 } 429 } 430 431 /** 432 * 433 * @test {@link java.sql.Statement#setQueryTimeout(int) } 434 * 435 * Part two of this test. 436 */ 437 @TestTargetNew( 438 level = TestLevel.PARTIAL_COMPLETE, 439 notes = "test timeout with st.exec()", 440 method = "setQueryTimeout", 441 args = {int.class} 442 ) 443 public static void testTimeoutWithExec() 444 throws TestFailedException { 445 System.out.println("Testing timeout with an execute operation"); 446 447 for (int i = 0; i < connections.length; ++i) { 448 try { 449 connections[i].setAutoCommit(true); 450 } catch (SQLException e) { 451 throw new TestFailedException("Unexpected Exception", e); 452 } 453 } 454 455 PreparedStatement statements[] = new PreparedStatement[connections.length]; 456 for (int i = 0; i < statements.length; ++i) { 457 statements[i] = prepare(connections[i], getExecQuery("t")); 458 } 459 460 StatementExecutor[] executors = new StatementExecutor[statements.length]; 461 for (int i = 0; i < executors.length; ++i) { 462 int timeout = (i % 2 == 0) ? TIMEOUT : 0; 463 executors[i] = new StatementExecutor(statements[i], false, timeout); 464 } 465 466 for (int i = 0; i < executors.length; ++i) { 467 executors[i].start(); 468 } 469 470 for (int i = 0; i < executors.length; ++i) { 471 try { 472 executors[i].join(); 473 } catch (InterruptedException e) { 474 throw new TestFailedException("Should never happen", e); 475 } 476 } 477 478 /** 479 * Actually, there is no guarantee that setting a query timeout for a 480 * statement will actually cause a timeout, even if execution of the 481 * statement takes longer than the specified timeout. However, these 482 * queries execute significantly longer than the specified query 483 * timeout. Also, the cancellation mechanism implemented should be quite 484 * responsive. In sum, we expect the statement to always time out. If it 485 * does not time out, however, we print the highest execution time for 486 * the query, as an assistance in determining why it failed. Compare the 487 * number to the TIMEOUT constant in this class (note that the TIMEOUT 488 * constant is in seconds, while the execution time is in milliseconds). 489 */ 490 for (int i = 0; i < executors.length; ++i) { 491 int timeout = (i % 2 == 0) ? TIMEOUT : 0; 492 if (timeout > 0) { 493 expectException("XCL52", executors[i].getSQLException(), 494 "exec did not time out. Execution time: " 495 + executors[i].getHighestRunTime() + " ms"); 496 } else { 497 SQLException sqlException = executors[i].getSQLException(); 498 if (sqlException != null) { 499 throw new TestFailedException(sqlException); 500 } 501 } 502 } 503 504 System.out 505 .println("Statements that should time out timed out, and statements that should complete completed"); 506 507 for (int i = 0; i < statements.length; ++i) { 508 try { 509 statements[i].close(); 510 } catch (SQLException e) { 511 throw new TestFailedException(e); 512 } 513 } 514 } 515 516 517 /** 518 * 519 * @test {@link java.sql.Statement#setQueryTimeout(int) } 520 * 521 */ 522 @TestTargetNew( 523 level = TestLevel.PARTIAL_COMPLETE, 524 notes = "Testing setting a negative timeout value", 525 method = "setQueryTimeout", 526 args = {int.class} 527 ) 528 public static void testInvalidTimeoutValue(Connection conn) 529 throws TestFailedException { 530 531 try { 532 conn.setAutoCommit(true); 533 } catch (SQLException e) { 534 throw new TestFailedException("Unexpected Exception", e); 535 } 536 537 // Create statement 538 PreparedStatement stmt = null; 539 try { 540 stmt = conn.prepareStatement("select * from sys.systables"); 541 } catch (SQLException e) { 542 throw new TestFailedException("Unexpected Exception", e); 543 } 544 545 // Set (invalid) timeout value - expect exception 546 try { 547 stmt.setQueryTimeout(-1); 548 } catch (SQLException e) { 549 expectException("XJ074", e, 550 "negative timeout value should give exception"); 551 } 552 553 System.out 554 .println("Negative timeout value caused exception, as expected"); 555 556 // Execute the statement and fetch result 557 ResultSet rs = null; 558 try { 559 rs = stmt.executeQuery(); 560 System.out.println("Execute returned a ResultSet"); 561 rs.close(); 562 } catch (SQLException e) { 563 throw new TestFailedException("Unexpected Exception", e); 564 } finally { 565 try { 566 stmt.close(); 567 } catch (SQLException e) { 568 // This will discard an exception possibly thrown above :-( 569 // But we don't worry too much about this, since: 570 // 1. This is just a test 571 // 2. We don't expect close() to throw 572 // 3. If it does, this will be inspected by a developer 573 throw new TestFailedException("close should not throw", e); 574 } 575 } 576 } 577 578 /** 579 * 580 * @test {@link java.sql.Statement#setQueryTimeout(int) } 581 * 582 * Part two of this test. 583 */ 584 @TestTargetNew( 585 level = TestLevel.PARTIAL_COMPLETE, 586 notes = "timeout with executeUpdate call", 587 method = "setQueryTimeout", 588 args = {int.class} 589 ) 590 public static void testTimeoutWithExecuteUpdate() 591 throws TestFailedException { 592 System.out.println("Testing timeout with executeUpdate call."); 593 try { 594 Statement stmt = connections[0].createStatement(); 595 stmt.setQueryTimeout(TIMEOUT); 596 stmt.executeUpdate(getExecQuery("t")); 597 } catch (SQLException sqle) { 598 expectException("XCL52", sqle, "Should have timed out."); 599 } 600 } 601 602 /** Test for DERBY-1692. */ 603 @TestTargetNew( 604 level = TestLevel.PARTIAL_COMPLETE, 605 notes = "Testing that Statement considers timeout.", 606 method = "setQueryTimeout", 607 args = {int.class} 608 ) 609 public static void testRememberTimeoutValue() 610 throws TestFailedException { 611 String sql = getFetchQuery("t"); 612 try { 613 Statement stmt = connections[0].createStatement(); 614 statementRemembersTimeout(stmt); 615 PreparedStatement ps = connections[0].prepareStatement(sql); 616 statementRemembersTimeout(ps); 617 CallableStatement cs = connections[0].prepareCall(sql); 618 statementRemembersTimeout(cs); 619 } catch (SQLException sqle) { 620 throw new TestFailedException("Unexpected Exception", sqle); 621 } 622 } 623 624 public static void statementRemembersTimeout(Statement stmt) 625 throws SQLException, TestFailedException { 626 System.out.println("Testing that Statement remembers timeout."); 627 stmt.setQueryTimeout(1); 628 for (int i = 0; i < 3; i++) { 629 try { 630 ResultSet rs = stmt.executeQuery(getFetchQuery("t")); 631 while (rs.next()) { 632 // do nothing 633 } 634 throw new TestFailedException("Should have timed out."); 635 } catch (SQLException sqle) { 636 expectException("XCL52", sqle, "Should have timed out."); 637 } 638 } 639 stmt.close(); 640 } 641 642 private static void statementRemembersTimeout(PreparedStatement ps) 643 throws SQLException, TestFailedException { 644 String name = (ps instanceof CallableStatement) ? "CallableStatement" 645 : "PreparedStatement"; 646 System.out.println("Testing that " + name + " remembers timeout."); 647 ps.setQueryTimeout(1); 648 for (int i = 0; i < 3; i++) { 649 try { 650 ResultSet rs = ps.executeQuery(); 651 while (rs.next()) { 652 // do nothing 653 } 654 throw new TestFailedException("Should have timed out."); 655 } catch (SQLException sqle) { 656 expectException("XCL52", sqle, "Should have timed out."); 657 } 658 } 659 ps.close(); 660 } 661 662 /** 663 * A function 664 * arg0 : int seconds 665 * 666 */ 667 static class Delay implements SQLite.Function { 668 669 public void function(FunctionContext fc, String[] args) { 670 int seconds = new Integer(args[0]).intValue(); 671 int value = new Integer(args[1]).intValue(); 672 try { 673 Thread.sleep(seconds * 1000); 674 } catch (InterruptedException e) { 675 // Ignore 676 } 677 fc.set_result(value); 678 679 } 680 681 public void last_step(FunctionContext fc) { 682 // TODO Auto-generated method stub 683 684 } 685 686 public void step(FunctionContext fc, String[] args) { 687 // TODO Auto-generated method stub 688 689 } 690 691 } 692 693 /** 694 * The actual main bulk of this test. Sets up the environment, prepares 695 * tables, runs the tests, and shuts down. 696 */ 697 public static Test suite() { 698 699 TestSetup setup = new TestSetup( new TestSuite (QueryTimeoutTest.class)) { 700 public void setUp() { 701 702 // Establish connections 703 Support_SQL.loadDriver(); 704 try { 705 706 for (int i = 0; i < connections.length; ++i) { 707 connections[i] = Support_SQL.getConnection(); 708 } 709 710 for (int i = 0; i < connections.length; ++i) { 711 connections[i] 712 .setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); 713 } 714 715 // setup Delay function 716 prepare(); 717 718 } catch (Throwable e) { 719 fail("Unexpected SQLException " + e.toString()); 720 } 721 722 System.out.println("Connections set up"); 723 724 } 725 726 public void tearDown() { 727 for (int i = connections.length - 1; i >= 0; --i) { 728 if (connections[i] != null) { 729 try { 730 connections[i].close(); 731 } catch (SQLException ex) { 732 printSQLException(ex); 733 } 734 } 735 } 736 System.out.println("Closed connections"); 737 } 738 739 public void prepare() throws TestFailedException { 740 System.out.println("Preparing for testing queries with timeout"); 741 Database db = new Database(); 742 743 Connection conn = connections[0]; 744 745 746 try { 747 db.open(Support_SQL.getFilename(), 1); 748 conn.setAutoCommit(true); 749 } catch (Exception e) { 750 throw new TestFailedException("Unexpected Exception", e); 751 } 752 753 Function delayFc = new Delay(); 754 db.create_function("DELAY", 2, delayFc); 755 756 prepareTables(conn, "t"); 757 } 758 }; 759 760 TestSuite ts = new TestSuite(); 761 ts.addTestSuite(QueryTimeoutTest.class); 762 763 return setup; 764 } 765 766} 767