001/* 002 * Copyright (c) 2004-2025 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) throws SQLException { 131 // GCJ has a somewhat serious bug (reported) 132 // 133 if ("GNU libgcj" 134 .equals( 135 SessionManager.current().frozenSystemProperties() 136 .getProperty("java.vm.name"))) { 137 System.out.print("INFO: "); 138 System.out.println(msg); 139 } else 140 Logger.getAnonymousLogger().info(msg); 141 } 142 143 static void warn(String msg) throws SQLException { 144 // GCJ has a somewhat serious bug (reported) 145 // 146 if ("GNU libgcj" 147 .equals( 148 SessionManager.current().frozenSystemProperties() 149 .getProperty("java.vm.name"))) { 150 System.out.print("WARNING: "); 151 System.out.println(msg); 152 } else 153 Logger.getAnonymousLogger().warning(msg); 154 } 155 156 @Function(schema="javatest", effects=IMMUTABLE) 157 public static int maxFromSetReturnExample(int base, int increment) 158 throws SQLException { 159 int max = Integer.MIN_VALUE; 160 Connection conn = DriverManager 161 .getConnection("jdbc:default:connection"); 162 PreparedStatement stmt = null; 163 ResultSet rs = null; 164 165 try { 166 stmt = conn 167 .prepareStatement("SELECT base FROM setReturnExample(?, ?)"); 168 stmt.setInt(1, base); 169 stmt.setInt(2, increment); 170 rs = stmt.executeQuery(); 171 while (rs.next()) { 172 base = rs.getInt(1); 173 if (base > max) 174 max = base; 175 } 176 return base; 177 } finally { 178 if (rs != null) 179 rs.close(); 180 if (stmt != null) 181 stmt.close(); 182 conn.close(); 183 } 184 } 185 186 /** 187 * Test of bug #1556 188 */ 189 @Function(schema="javatest") 190 public static void nestedStatements(int innerCount) throws SQLException { 191 Connection connection = DriverManager 192 .getConnection("jdbc:default:connection"); 193 Statement statement = connection.createStatement(); 194 195 // Create a set of ID's so that we can do somthing semi-useful during 196 // the long loop. 197 // 198 statement.execute("DELETE FROM javatest.employees1"); 199 statement.execute("INSERT INTO javatest.employees1 VALUES(" 200 + "1, 'Calvin Forrester', 10000)"); 201 statement.execute("INSERT INTO javatest.employees1 VALUES(" 202 + "2, 'Edwin Archer', 20000)"); 203 statement.execute("INSERT INTO javatest.employees1 VALUES(" 204 + "3, 'Rebecka Shawn', 30000)"); 205 statement.execute("INSERT INTO javatest.employees1 VALUES(" 206 + "4, 'Priscilla Johnson', 25000)"); 207 208 int idx = 1; 209 ResultSet results = statement 210 .executeQuery("SELECT * FROM javatest.hugeResult(" + innerCount 211 + ")"); 212 while (results.next()) { 213 Statement innerStatement = connection.createStatement(); 214 innerStatement 215 .executeUpdate("UPDATE javatest.employees1 SET salary = salary + 1 WHERE id=" 216 + idx); 217 innerStatement.close(); 218 if (++idx == 5) 219 idx = 0; 220 } 221 results.close(); 222 statement.close(); 223 connection.close(); 224 } 225 226 @SuppressWarnings("removal") // getAttribute / setAttribute 227 private static void nextState(Session session, int expected, int next) 228 throws SQLException { 229 Integer state = (Integer) session.getAttribute(SP_CHECKSTATE); 230 if (state == null || state.intValue() != expected) 231 throw new SQLException(SP_CHECKSTATE + ": Expected " + expected 232 + ", got " + state); 233 session.setAttribute(SP_CHECKSTATE, next); 234 } 235 236 @Function(schema="javatest", effects=IMMUTABLE) 237 @SuppressWarnings("removal") // setAttribute 238 public static int testSavepointSanity() throws SQLException { 239 Connection conn = DriverManager 240 .getConnection("jdbc:default:connection"); 241 242 // Create an anonymous savepoint. 243 // 244 log("Attempting to set an anonymous savepoint"); 245 Session currentSession = SessionManager.current(); 246 currentSession.setAttribute(SP_CHECKSTATE, 0); 247 currentSession.addSavepointListener(spListener); 248 249 Savepoint sp = conn.setSavepoint(); 250 nextState(currentSession, 1, 2); 251 try { 252 Statement stmt = conn.createStatement(); 253 log("Attempting to set a SAVEPOINT using SQL (should fail)"); 254 stmt.execute("SAVEPOINT foo"); 255 } catch (SQLException e) { 256 log("It failed allright. Everything OK then"); 257 log("Rolling back to anonymous savepoint"); 258 259 nextState(currentSession, 2, 3); 260 conn.rollback(sp); 261 nextState(currentSession, 1, 5); 262 return 1; 263 } finally { 264 currentSession.removeSavepointListener(spListener); 265 } 266 throw new SQLException( 267 "SAVEPOINT through SQL succeeded. That's bad news!"); 268 } 269 270 /** 271 * Confirm JDBC behavior of Savepoint, in particular that a Savepoint 272 * rolled back to still exists and can be rolled back to again or released. 273 */ 274 @Function(schema="javatest", provides="issue228") 275 public static void issue228() throws SQLException 276 { 277 boolean ok = true; 278 Connection conn = 279 DriverManager.getConnection("jdbc:default:connection"); 280 Statement s = conn.createStatement(); 281 try 282 { 283 Savepoint alice = conn.setSavepoint("alice"); 284 s.execute("SET LOCAL TIME ZONE 1"); 285 Savepoint bob = conn.setSavepoint("bob"); 286 s.execute("SET LOCAL TIME ZONE 2"); 287 conn.rollback(bob); 288 s.execute("SET LOCAL TIME ZONE 3"); 289 conn.releaseSavepoint(bob); 290 try 291 { 292 conn.rollback(bob); 293 ok = false; 294 warn("Savepoint \"bob\" should be invalid after release"); 295 } 296 catch ( SQLException e ) 297 { 298 if ( ! "3B001".equals(e.getSQLState()) ) 299 throw e; 300 } 301 conn.rollback(alice); 302 bob = conn.setSavepoint("bob"); 303 s.execute("SET LOCAL TIME ZONE 4"); 304 conn.rollback(alice); 305 try 306 { 307 conn.releaseSavepoint(bob); 308 ok = false; 309 warn( 310 "Savepoint \"bob\" should be invalid after outer rollback"); 311 } 312 catch ( SQLException e ) 313 { 314 if ( ! "3B001".equals(e.getSQLState()) ) 315 throw e; 316 } 317 conn.rollback(alice); 318 s.execute("SET LOCAL TIME ZONE 5"); 319 conn.releaseSavepoint(alice); 320 } 321 finally 322 { 323 s.close(); 324 if ( ok ) 325 log("issue 228 tests ok"); 326 } 327 } 328 329 @Function(schema="javatest", effects=IMMUTABLE) 330 @SuppressWarnings("removal") // setAttribute 331 public static int testTransactionRecovery() throws SQLException { 332 Connection conn = DriverManager 333 .getConnection("jdbc:default:connection"); 334 335 // Create an anonymous savepoint. 336 // 337 log("Attempting to set an anonymous savepoint"); 338 Session currentSession = SessionManager.current(); 339 currentSession.setAttribute(SP_CHECKSTATE, 0); 340 currentSession.addSavepointListener(spListener); 341 342 Statement stmt = conn.createStatement(); 343 Savepoint sp = conn.setSavepoint(); 344 nextState(currentSession, 1, 2); 345 346 try { 347 log("Attempting to execute a statement with a syntax error"); 348 stmt.execute("THIS MUST BE A SYNTAX ERROR"); 349 } catch (SQLException e) { 350 log("It failed. Let's try to recover " 351 + "by rolling back to anonymous savepoint"); 352 nextState(currentSession, 2, 3); 353 conn.rollback(sp); 354 nextState(currentSession, 1, 5); 355 log("Rolled back."); 356 log("Now let's try to execute a correct statement."); 357 358 currentSession.setAttribute(SP_CHECKSTATE, 0); 359 sp = conn.setSavepoint(); 360 nextState(currentSession, 1, 2); 361 ResultSet rs = stmt.executeQuery("SELECT 'OK'"); 362 while (rs.next()) { 363 log("Expected: OK; Retrieved: " + rs.getString(1)); 364 } 365 rs.close(); 366 stmt.close(); 367 nextState(currentSession, 2, 3); 368 conn.releaseSavepoint(sp); 369 nextState(currentSession, 4, 5); 370 return 1; 371 } finally { 372 currentSession.removeSavepointListener(spListener); 373 } 374 375 // Should never get here 376 return -1; 377 } 378 379 @Function(schema="javatest", name="transferPeople") 380 public static int transferPeopleWithSalary(int salary) throws SQLException { 381 Connection conn = DriverManager 382 .getConnection("jdbc:default:connection"); 383 PreparedStatement select = null; 384 PreparedStatement insert = null; 385 PreparedStatement delete = null; 386 ResultSet rs = null; 387 388 String stmt; 389 try { 390 stmt = "SELECT id, name, salary FROM employees1 WHERE salary > ?"; 391 log(stmt); 392 select = conn.prepareStatement(stmt); 393 394 stmt = "INSERT INTO employees2(id, name, salary, transferDay, transferTime) VALUES (?, ?, ?, ?, ?)"; 395 log(stmt); 396 insert = conn.prepareStatement(stmt); 397 398 stmt = "DELETE FROM employees1 WHERE id = ?"; 399 log(stmt); 400 delete = conn.prepareStatement(stmt); 401 402 log("assigning parameter value " + salary); 403 select.setInt(1, salary); 404 log("Executing query"); 405 rs = select.executeQuery(); 406 int rowNo = 0; 407 log("Doing next"); 408 while (rs.next()) { 409 log("Processing row " + ++rowNo); 410 int id = rs.getInt(1); 411 String name = rs.getString(2); 412 int empSal = rs.getInt(3); 413 414 insert.setInt(1, id); 415 insert.setString(2, name); 416 insert.setInt(3, empSal); 417 long now = System.currentTimeMillis(); 418 insert.setDate(4, new Date(now)); 419 insert.setTime(5, new Time(now)); 420 int nRows = insert.executeUpdate(); 421 log("Insert processed " + nRows + " rows"); 422 423 delete.setInt(1, id); 424 nRows = delete.executeUpdate(); 425 log("Delete processed " + nRows + " rows"); 426 log("Doing next"); 427 } 428 if (rowNo == 0) 429 log("No row found"); 430 return rowNo; 431 } finally { 432 if (select != null) 433 select.close(); 434 if (insert != null) 435 insert.close(); 436 if (delete != null) 437 delete.close(); 438 conn.close(); 439 } 440 } 441 442 static TransactionListener s_tlstnr; 443 444 public static void registerTransactionListener() throws SQLException 445 { 446 Session currentSession = SessionManager.current(); 447 if ( null == s_tlstnr ) 448 { 449 s_tlstnr = new XactListener(); 450 currentSession.addTransactionListener(s_tlstnr); 451 } 452 else 453 { 454 currentSession.removeTransactionListener(s_tlstnr); 455 s_tlstnr = null; 456 } 457 } 458 459 static class XactListener implements TransactionListener 460 { 461 public void onAbort(Session s) 462 { 463 System.err.println("aborting a transaction"); 464 } 465 public void onCommit(Session s) 466 { 467 System.err.println("committing a transaction"); 468 } 469 public void onPrepare(Session s) 470 { 471 System.err.println("preparing a transaction"); 472 } 473 } 474}