001/* 002 * Copyright (c) 2004-2020 Tada AB and other contributors, as listed below. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the The BSD 3-Clause License 006 * which accompanies this distribution, and is available at 007 * http://opensource.org/licenses/BSD-3-Clause 008 * 009 * Contributors: 010 * Tada AB 011 * Chapman Flack 012 */ 013package org.postgresql.pljava.example.annotation; 014 015import java.sql.Connection; 016import java.sql.Date; 017import java.sql.DriverManager; 018import java.sql.PreparedStatement; 019import java.sql.ResultSet; 020import java.sql.SQLException; 021import java.sql.Savepoint; 022import java.sql.Statement; 023import java.sql.Time; 024import java.util.logging.Logger; 025 026import org.postgresql.pljava.SavepointListener; 027import org.postgresql.pljava.Session; 028import org.postgresql.pljava.SessionManager; 029import org.postgresql.pljava.TransactionListener; 030 031import org.postgresql.pljava.annotation.Function; 032import static org.postgresql.pljava.annotation.Function.Effects.*; 033import org.postgresql.pljava.annotation.SQLAction; 034 035/** 036 * Some methods used for testing the SPI JDBC driver. 037 * 038 * @author Thomas Hallgren 039 */ 040@SQLAction(provides = "employees tables", install = { 041 "CREATE TABLE javatest.employees1" + 042 " (" + 043 " id int PRIMARY KEY," + 044 " name varchar(200)," + 045 " salary int" + 046 " )", 047 048 "CREATE TABLE javatest.employees2" + 049 " (" + 050 " id int PRIMARY KEY," + 051 " name varchar(200)," + 052 " salary int," + 053 " transferDay date," + 054 " transferTime time" + 055 " )" 056 }, remove = { 057 "DROP TABLE javatest.employees2", 058 "DROP TABLE javatest.employees1" 059} 060) 061@SQLAction(requires = "issue228", install = "SELECT javatest.issue228()") 062public class SPIActions { 063 private static final String SP_CHECKSTATE = "sp.checkState"; 064 065 private static final SavepointListener spListener = new SavepointListener() { 066 @Override 067 public void onAbort(Session session, Savepoint savepoint, 068 Savepoint parent) throws SQLException { 069 log("Abort of savepoint " + savepoint.getSavepointId()); 070 nextState(session, 3, 0); 071 } 072 073 @Override 074 public void onCommit(Session session, Savepoint savepoint, 075 Savepoint parent) throws SQLException { 076 log("Commit of savepoint " + savepoint.getSavepointId()); 077 nextState(session, 3, 4); 078 } 079 080 @Override 081 public void onStart(Session session, Savepoint savepoint, 082 Savepoint parent) throws SQLException { 083 log("Start of savepoint " + savepoint.getSavepointId()); 084 nextState(session, 0, 1); 085 } 086 }; 087 088 @Function(schema="javatest", effects=STABLE) 089 public static String getDateAsString() throws SQLException { 090 ResultSet rs = null; 091 Statement stmt = null; 092 Connection conn = DriverManager 093 .getConnection("jdbc:default:connection"); 094 try { 095 stmt = conn.createStatement(); 096 rs = stmt.executeQuery("SELECT CURRENT_DATE"); 097 if (rs.next()) 098 return rs.getDate(1).toString(); 099 return "Date could not be retrieved"; 100 } finally { 101 if (rs != null) 102 rs.close(); 103 if (stmt != null) 104 stmt.close(); 105 conn.close(); 106 } 107 } 108 109 @Function(schema="javatest", effects=STABLE) 110 public static String getTimeAsString() throws SQLException { 111 ResultSet rs = null; 112 Statement stmt = null; 113 Connection conn = DriverManager 114 .getConnection("jdbc:default:connection"); 115 try { 116 stmt = conn.createStatement(); 117 rs = stmt.executeQuery("SELECT CURRENT_TIME"); 118 if (rs.next()) 119 return rs.getTime(1).toString(); 120 return "Time could not be retrieved"; 121 } finally { 122 if (rs != null) 123 rs.close(); 124 if (stmt != null) 125 stmt.close(); 126 conn.close(); 127 } 128 } 129 130 static void log(String msg) { 131 // GCJ has a somewhat serious bug (reported) 132 // 133 if ("GNU libgcj".equals(System.getProperty("java.vm.name"))) { 134 System.out.print("INFO: "); 135 System.out.println(msg); 136 } else 137 Logger.getAnonymousLogger().info(msg); 138 } 139 140 static void warn(String msg) { 141 // GCJ has a somewhat serious bug (reported) 142 // 143 if ("GNU libgcj".equals(System.getProperty("java.vm.name"))) { 144 System.out.print("WARNING: "); 145 System.out.println(msg); 146 } else 147 Logger.getAnonymousLogger().warning(msg); 148 } 149 150 @Function(schema="javatest", effects=IMMUTABLE) 151 public static int maxFromSetReturnExample(int base, int increment) 152 throws SQLException { 153 int max = Integer.MIN_VALUE; 154 Connection conn = DriverManager 155 .getConnection("jdbc:default:connection"); 156 PreparedStatement stmt = null; 157 ResultSet rs = null; 158 159 try { 160 stmt = conn 161 .prepareStatement("SELECT base FROM setReturnExample(?, ?)"); 162 stmt.setInt(1, base); 163 stmt.setInt(2, increment); 164 rs = stmt.executeQuery(); 165 while (rs.next()) { 166 base = rs.getInt(1); 167 if (base > max) 168 max = base; 169 } 170 return base; 171 } finally { 172 if (rs != null) 173 rs.close(); 174 if (stmt != null) 175 stmt.close(); 176 conn.close(); 177 } 178 } 179 180 /** 181 * Test of bug #1556 182 */ 183 @Function(schema="javatest") 184 public static void nestedStatements(int innerCount) throws SQLException { 185 Connection connection = DriverManager 186 .getConnection("jdbc:default:connection"); 187 Statement statement = connection.createStatement(); 188 189 // Create a set of ID's so that we can do somthing semi-useful during 190 // the long loop. 191 // 192 statement.execute("DELETE FROM javatest.employees1"); 193 statement.execute("INSERT INTO javatest.employees1 VALUES(" 194 + "1, 'Calvin Forrester', 10000)"); 195 statement.execute("INSERT INTO javatest.employees1 VALUES(" 196 + "2, 'Edwin Archer', 20000)"); 197 statement.execute("INSERT INTO javatest.employees1 VALUES(" 198 + "3, 'Rebecka Shawn', 30000)"); 199 statement.execute("INSERT INTO javatest.employees1 VALUES(" 200 + "4, 'Priscilla Johnson', 25000)"); 201 202 int idx = 1; 203 ResultSet results = statement 204 .executeQuery("SELECT * FROM javatest.hugeResult(" + innerCount 205 + ")"); 206 while (results.next()) { 207 Statement innerStatement = connection.createStatement(); 208 innerStatement 209 .executeUpdate("UPDATE javatest.employees1 SET salary = salary + 1 WHERE id=" 210 + idx); 211 innerStatement.close(); 212 if (++idx == 5) 213 idx = 0; 214 } 215 results.close(); 216 statement.close(); 217 connection.close(); 218 } 219 220 @SuppressWarnings("removal") // getAttribute / setAttribute 221 private static void nextState(Session session, int expected, int next) 222 throws SQLException { 223 Integer state = (Integer) session.getAttribute(SP_CHECKSTATE); 224 if (state == null || state.intValue() != expected) 225 throw new SQLException(SP_CHECKSTATE + ": Expected " + expected 226 + ", got " + state); 227 session.setAttribute(SP_CHECKSTATE, next); 228 } 229 230 @Function(schema="javatest", effects=IMMUTABLE) 231 @SuppressWarnings("removal") // setAttribute 232 public static int testSavepointSanity() throws SQLException { 233 Connection conn = DriverManager 234 .getConnection("jdbc:default:connection"); 235 236 // Create an anonymous savepoint. 237 // 238 log("Attempting to set an anonymous savepoint"); 239 Session currentSession = SessionManager.current(); 240 currentSession.setAttribute(SP_CHECKSTATE, 0); 241 currentSession.addSavepointListener(spListener); 242 243 Savepoint sp = conn.setSavepoint(); 244 nextState(currentSession, 1, 2); 245 try { 246 Statement stmt = conn.createStatement(); 247 log("Attempting to set a SAVEPOINT using SQL (should fail)"); 248 stmt.execute("SAVEPOINT foo"); 249 } catch (SQLException e) { 250 log("It failed allright. Everything OK then"); 251 log("Rolling back to anonymous savepoint"); 252 253 nextState(currentSession, 2, 3); 254 conn.rollback(sp); 255 nextState(currentSession, 1, 5); 256 return 1; 257 } finally { 258 currentSession.removeSavepointListener(spListener); 259 } 260 throw new SQLException( 261 "SAVEPOINT through SQL succeeded. That's bad news!"); 262 } 263 264 /** 265 * Confirm JDBC behavior of Savepoint, in particular that a Savepoint 266 * rolled back to still exists and can be rolled back to again or released. 267 */ 268 @Function(schema="javatest", provides="issue228") 269 public static void issue228() throws SQLException 270 { 271 boolean ok = true; 272 Connection conn = 273 DriverManager.getConnection("jdbc:default:connection"); 274 Statement s = conn.createStatement(); 275 try 276 { 277 Savepoint alice = conn.setSavepoint("alice"); 278 s.execute("SET LOCAL TIME ZONE 1"); 279 Savepoint bob = conn.setSavepoint("bob"); 280 s.execute("SET LOCAL TIME ZONE 2"); 281 conn.rollback(bob); 282 s.execute("SET LOCAL TIME ZONE 3"); 283 conn.releaseSavepoint(bob); 284 try 285 { 286 conn.rollback(bob); 287 ok = false; 288 warn("Savepoint \"bob\" should be invalid after release"); 289 } 290 catch ( SQLException e ) 291 { 292 if ( ! "3B001".equals(e.getSQLState()) ) 293 throw e; 294 } 295 conn.rollback(alice); 296 bob = conn.setSavepoint("bob"); 297 s.execute("SET LOCAL TIME ZONE 4"); 298 conn.rollback(alice); 299 try 300 { 301 conn.releaseSavepoint(bob); 302 ok = false; 303 warn( 304 "Savepoint \"bob\" should be invalid after outer rollback"); 305 } 306 catch ( SQLException e ) 307 { 308 if ( ! "3B001".equals(e.getSQLState()) ) 309 throw e; 310 } 311 conn.rollback(alice); 312 s.execute("SET LOCAL TIME ZONE 5"); 313 conn.releaseSavepoint(alice); 314 } 315 finally 316 { 317 s.close(); 318 if ( ok ) 319 log("issue 228 tests ok"); 320 } 321 } 322 323 @Function(schema="javatest", effects=IMMUTABLE) 324 @SuppressWarnings("removal") // setAttribute 325 public static int testTransactionRecovery() throws SQLException { 326 Connection conn = DriverManager 327 .getConnection("jdbc:default:connection"); 328 329 // Create an anonymous savepoint. 330 // 331 log("Attempting to set an anonymous savepoint"); 332 Session currentSession = SessionManager.current(); 333 currentSession.setAttribute(SP_CHECKSTATE, 0); 334 currentSession.addSavepointListener(spListener); 335 336 Statement stmt = conn.createStatement(); 337 Savepoint sp = conn.setSavepoint(); 338 nextState(currentSession, 1, 2); 339 340 try { 341 log("Attempting to execute a statement with a syntax error"); 342 stmt.execute("THIS MUST BE A SYNTAX ERROR"); 343 } catch (SQLException e) { 344 log("It failed. Let's try to recover " 345 + "by rolling back to anonymous savepoint"); 346 nextState(currentSession, 2, 3); 347 conn.rollback(sp); 348 nextState(currentSession, 1, 5); 349 log("Rolled back."); 350 log("Now let's try to execute a correct statement."); 351 352 currentSession.setAttribute(SP_CHECKSTATE, 0); 353 sp = conn.setSavepoint(); 354 nextState(currentSession, 1, 2); 355 ResultSet rs = stmt.executeQuery("SELECT 'OK'"); 356 while (rs.next()) { 357 log("Expected: OK; Retrieved: " + rs.getString(1)); 358 } 359 rs.close(); 360 stmt.close(); 361 nextState(currentSession, 2, 3); 362 conn.releaseSavepoint(sp); 363 nextState(currentSession, 4, 5); 364 return 1; 365 } finally { 366 currentSession.removeSavepointListener(spListener); 367 } 368 369 // Should never get here 370 return -1; 371 } 372 373 @Function(schema="javatest", name="transferPeople") 374 public static int transferPeopleWithSalary(int salary) throws SQLException { 375 Connection conn = DriverManager 376 .getConnection("jdbc:default:connection"); 377 PreparedStatement select = null; 378 PreparedStatement insert = null; 379 PreparedStatement delete = null; 380 ResultSet rs = null; 381 382 String stmt; 383 try { 384 stmt = "SELECT id, name, salary FROM employees1 WHERE salary > ?"; 385 log(stmt); 386 select = conn.prepareStatement(stmt); 387 388 stmt = "INSERT INTO employees2(id, name, salary, transferDay, transferTime) VALUES (?, ?, ?, ?, ?)"; 389 log(stmt); 390 insert = conn.prepareStatement(stmt); 391 392 stmt = "DELETE FROM employees1 WHERE id = ?"; 393 log(stmt); 394 delete = conn.prepareStatement(stmt); 395 396 log("assigning parameter value " + salary); 397 select.setInt(1, salary); 398 log("Executing query"); 399 rs = select.executeQuery(); 400 int rowNo = 0; 401 log("Doing next"); 402 while (rs.next()) { 403 log("Processing row " + ++rowNo); 404 int id = rs.getInt(1); 405 String name = rs.getString(2); 406 int empSal = rs.getInt(3); 407 408 insert.setInt(1, id); 409 insert.setString(2, name); 410 insert.setInt(3, empSal); 411 long now = System.currentTimeMillis(); 412 insert.setDate(4, new Date(now)); 413 insert.setTime(5, new Time(now)); 414 int nRows = insert.executeUpdate(); 415 log("Insert processed " + nRows + " rows"); 416 417 delete.setInt(1, id); 418 nRows = delete.executeUpdate(); 419 log("Delete processed " + nRows + " rows"); 420 log("Doing next"); 421 } 422 if (rowNo == 0) 423 log("No row found"); 424 return rowNo; 425 } finally { 426 if (select != null) 427 select.close(); 428 if (insert != null) 429 insert.close(); 430 if (delete != null) 431 delete.close(); 432 conn.close(); 433 } 434 } 435 436 static TransactionListener s_tlstnr; 437 438 public static void registerTransactionListener() throws SQLException 439 { 440 Session currentSession = SessionManager.current(); 441 if ( null == s_tlstnr ) 442 { 443 s_tlstnr = new XactListener(); 444 currentSession.addTransactionListener(s_tlstnr); 445 } 446 else 447 { 448 currentSession.removeTransactionListener(s_tlstnr); 449 s_tlstnr = null; 450 } 451 } 452 453 static class XactListener implements TransactionListener 454 { 455 public void onAbort(Session s) 456 { 457 System.err.println("aborting a transaction"); 458 } 459 public void onCommit(Session s) 460 { 461 System.err.println("committing a transaction"); 462 } 463 public void onPrepare(Session s) 464 { 465 System.err.println("preparing a transaction"); 466 } 467 } 468}