Re: revisiting transaction isolation - Mailing list pgsql-jdbc
From | Oliver Jowett |
---|---|
Subject | Re: revisiting transaction isolation |
Date | |
Msg-id | 40FCE032.30201@opencloud.com Whole thread Raw |
In response to | Re: revisiting transaction isolation (Oliver Jowett <oliver@opencloud.com>) |
Responses |
Re: revisiting transaction isolation
|
List | pgsql-jdbc |
Oliver Jowett wrote: > A quick skim of AbstractJdbc2Connection turns up these methods as > candidates for ignoring autocommit: > > - getTransactionIsolation() > - getPGType (both versions) on a cache miss > > It seems reasonable to suppress BEGINs for both of those cases. I can > put together a patch to do that. I'll leave the metadata queries alone > for the moment. And here is the patch. I reworked execSQLQuery / execSQLUpdate to always ignore autocommit, since all places they're called from want this behaviour. Also tweaked them to detect unexpected cases (no resultset from query, resultset from update). -O Index: org/postgresql/core/BaseConnection.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/core/BaseConnection.java,v retrieving revision 1.8 diff -u -c -r1.8 BaseConnection.java *** org/postgresql/core/BaseConnection.java 29 Jun 2004 06:43:24 -0000 1.8 --- org/postgresql/core/BaseConnection.java 20 Jul 2004 08:35:05 -0000 *************** *** 31,36 **** --- 31,37 ---- /** * Execute a SQL query that returns a single resultset. + * Never causes a new transaction to be started regardless of the autocommit setting. * * @param s the query to execute * @return the (non-null) returned resultset *************** *** 40,45 **** --- 41,47 ---- /** * Execute a SQL query that does not return results. + * Never causes a new transaction to be started regardless of the autocommit setting. * * @param s the query to execute * @throws SQLException if something goes wrong. Index: org/postgresql/core/BaseStatement.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/core/BaseStatement.java,v retrieving revision 1.13 diff -u -c -r1.13 BaseStatement.java *** org/postgresql/core/BaseStatement.java 16 Jul 2004 09:07:53 -0000 1.13 --- org/postgresql/core/BaseStatement.java 20 Jul 2004 08:35:05 -0000 *************** *** 53,56 **** --- 53,65 ---- * @throws SQLException if something goes wrong. */ public boolean executeWithFlags(String p_sql, int flags) throws SQLException; + + /** + * Execute a prepared query, passing additional query flags. + * + * @param flags additional {@link QueryExecutor} flags for execution; these + * are bitwise-ORed into the default flags. + * @throws SQLException if something goes wrong. + */ + public boolean executeWithFlags(int flags) throws SQLException; } Index: org/postgresql/jdbc2/AbstractJdbc2Connection.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Connection.java,v retrieving revision 1.13 diff -u -c -r1.13 AbstractJdbc2Connection.java *** org/postgresql/jdbc2/AbstractJdbc2Connection.java 16 Jul 2004 09:07:57 -0000 1.13 --- org/postgresql/jdbc2/AbstractJdbc2Connection.java 20 Jul 2004 08:35:06 -0000 *************** *** 175,184 **** /** * Simple query execution. */ ! public ResultSet execSQLQuery(String s) throws SQLException ! { ! Statement stat = (Statement) createStatement(); ! boolean hasResultSet = stat.execute(s); // Transfer warnings to the connection, since the user never // has a chance to see the statement itself. --- 175,189 ---- /** * Simple query execution. */ ! public ResultSet execSQLQuery(String s) throws SQLException { ! BaseStatement stat = (BaseStatement) createStatement(); ! boolean hasResultSet = stat.executeWithFlags(s, QueryExecutor.QUERY_SUPPRESS_BEGIN); ! ! while (!hasResultSet && stat.getUpdateCount() != -1) ! hasResultSet = stat.getMoreResults(); ! ! if (!hasResultSet) ! throw new PSQLException("postgresql.stat.noresult", PSQLState.NO_DATA); // Transfer warnings to the connection, since the user never // has a chance to see the statement itself. *************** *** 186,206 **** if (warnings != null) addWarning(warnings); - while (!hasResultSet && stat.getUpdateCount() != -1) - hasResultSet = stat.getMoreResults(); - return stat.getResultSet(); } ! public void execSQLUpdate(String s) throws SQLException ! { ! execSQLUpdate(s, 0); ! } ! ! private void execSQLUpdate(String s, int flags) throws SQLException ! { BaseStatement stmt = (BaseStatement) createStatement(); ! stmt.executeWithFlags(s, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | flags); // Transfer warnings to the connection, since the user never // has a chance to see the statement itself. --- 191,203 ---- if (warnings != null) addWarning(warnings); return stat.getResultSet(); } ! public void execSQLUpdate(String s) throws SQLException { BaseStatement stmt = (BaseStatement) createStatement(); ! if (stmt.executeWithFlags(s, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | QueryExecutor.QUERY_SUPPRESS_BEGIN)) ! throw new PSQLException("postgresql.stat.result"); // Transfer warnings to the connection, since the user never // has a chance to see the statement itself. *************** *** 526,532 **** if (haveMinimumServerVersion("7.4") && readOnly != this.readOnly) { String readOnlySql = "SET SESSION CHARACTERISTICS AS TRANSACTION " + (readOnly ? "READ ONLY" : "READ WRITE"); ! execSQLUpdate(readOnlySql, QueryExecutor.QUERY_SUPPRESS_BEGIN); // No BEGIN regardles of our autocommit setting } this.readOnly = readOnly; --- 523,529 ---- if (haveMinimumServerVersion("7.4") && readOnly != this.readOnly) { String readOnlySql = "SET SESSION CHARACTERISTICS AS TRANSACTION " + (readOnly ? "READ ONLY" : "READ WRITE"); ! execSQLUpdate(readOnlySql); // nb: no BEGIN triggered. } this.readOnly = readOnly; *************** *** 633,658 **** public int getTransactionIsolation() throws SQLException { String level = null; - Statement stmt = createStatement(); ! if (haveMinimumServerVersion("7.3")) { ! ResultSet rs = stmt.executeQuery("SHOW TRANSACTION ISOLATION LEVEL"); if (rs.next()) level = rs.getString(1); ! ! // Transfer warnings to the connection, since the user never ! // has a chance to see the statement itself. ! SQLWarning warnings = stmt.getWarnings(); ! if (warnings != null) ! addWarning(warnings); } else { ! stmt.executeUpdate("SHOW TRANSACTION ISOLATION LEVEL"); ! SQLWarning warning = stmt.getWarnings(); if (warning != null) level = warning.getMessage(); - } ! stmt.close(); // XXX revisit: throw exception instead of silently eating the error in unkwon cases? if (level == null) --- 630,661 ---- public int getTransactionIsolation() throws SQLException { String level = null; ! if (haveMinimumServerVersion("7.3")) { ! // 7.3+ returns the level as a query result. ! ResultSet rs = execSQLQuery("SHOW TRANSACTION ISOLATION LEVEL"); // nb: no BEGIN triggered if (rs.next()) level = rs.getString(1); ! rs.close(); } else { ! // 7.2 returns the level as an INFO message. Ew. ! // We juggle the warning chains a bit here. ! ! // Swap out current warnings. ! SQLWarning saveWarnings = getWarnings(); ! clearWarnings(); ! ! // Run the query any examine any resulting warnings. ! execSQLUpdate("SHOW TRANSACTION ISOLATION LEVEL"); // nb: no BEGIN triggered ! SQLWarning warning = getWarnings(); if (warning != null) level = warning.getMessage(); ! // Swap original warnings back. ! clearWarnings(); ! if (saveWarnings != null) ! addWarning(saveWarnings); ! } // XXX revisit: throw exception instead of silently eating the error in unkwon cases? if (level == null) *************** *** 694,700 **** throw new PSQLException("postgresql.con.isolevel", PSQLState.TRANSACTION_STATE_INVALID, new Integer(level)); String isolationLevelSQL = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL " + isolationLevelName; ! execSQLUpdate(isolationLevelSQL, QueryExecutor.QUERY_SUPPRESS_BEGIN); // No BEGIN regardles of our autocommit setting } protected String getIsolationLevelName(int level) --- 697,703 ---- throw new PSQLException("postgresql.con.isolevel", PSQLState.TRANSACTION_STATE_INVALID, new Integer(level)); String isolationLevelSQL = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL " + isolationLevelName; ! execSQLUpdate(isolationLevelSQL); // nb: no BEGIN triggered } protected String getIsolationLevelName(int level) *************** *** 887,893 **** query.setString(1, typeName); ! BaseResultSet result = (BaseResultSet)query.executeQuery(); if (result.next()) { oid = result.getInt(1); oidTypeCache.put(new Integer(oid), typeName); --- 890,899 ---- query.setString(1, typeName); ! if (! ((BaseStatement)query).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN) ) ! throw new PSQLException("postgresql.stat.noresult", PSQLState.NO_DATA); ! ! ResultSet result = query.getResultSet(); if (result.next()) { oid = result.getInt(1); oidTypeCache.put(new Integer(oid), typeName); *************** *** 926,932 **** query.setInt(1, oid); ! ResultSet result = query.executeQuery(); if (result.next()) { typeName = result.getString(1); typeOidCache.put(typeName, new Integer(oid)); --- 932,941 ---- query.setInt(1, oid); ! if (! ((BaseStatement)query).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN) ) ! throw new PSQLException("postgresql.stat.noresult", PSQLState.NO_DATA); ! ! ResultSet result = query.getResultSet(); if (result.next()) { typeName = result.getString(1); typeOidCache.put(typeName, new Integer(oid)); Index: org/postgresql/jdbc2/AbstractJdbc2ResultSet.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2ResultSet.java,v retrieving revision 1.43 diff -u -c -r1.43 AbstractJdbc2ResultSet.java *** org/postgresql/jdbc2/AbstractJdbc2ResultSet.java 16 Jul 2004 09:07:59 -0000 1.43 --- org/postgresql/jdbc2/AbstractJdbc2ResultSet.java 20 Jul 2004 08:35:06 -0000 *************** *** 158,163 **** --- 158,171 ---- // Fetch all results. String cursorName = getString(columnIndex); String fetchSql = "FETCH ALL IN \"" + cursorName + "\""; + + // nb: no BEGIN triggered here. This is fine. If someone + // committed, and the cursor was not holdable (closing the + // cursor), we avoid starting a new xact and promptly causing + // it to fail. If the cursor *was* holdable, we don't want a + // new xact anyway since holdable cursor state isn't affected + // by xact boundaries. If our caller didn't commit at all, or + // autocommit was on, then we wouldn't issue a BEGIN anyway. ResultSet rs = connection.execSQLQuery(fetchSql); ((AbstractJdbc2ResultSet)rs).setRefCursor(cursorName); return rs; Index: org/postgresql/jdbc2/AbstractJdbc2Statement.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java,v retrieving revision 1.27 diff -u -c -r1.27 AbstractJdbc2Statement.java *** org/postgresql/jdbc2/AbstractJdbc2Statement.java 16 Jul 2004 09:08:00 -0000 1.27 --- org/postgresql/jdbc2/AbstractJdbc2Statement.java 20 Jul 2004 08:35:06 -0000 *************** *** 282,288 **** return executeWithFlags(0); } ! private boolean executeWithFlags(int flags) throws SQLException { checkClosed(); if (isFunction && !returnTypeSet) --- 282,288 ---- return executeWithFlags(0); } ! public boolean executeWithFlags(int flags) throws SQLException { checkClosed(); if (isFunction && !returnTypeSet) Index: org/postgresql/test/jdbc2/ConnectionTest.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/test/jdbc2/ConnectionTest.java,v retrieving revision 1.14 diff -u -c -r1.14 ConnectionTest.java *** org/postgresql/test/jdbc2/ConnectionTest.java 10 Apr 2004 13:53:11 -0000 1.14 --- org/postgresql/test/jdbc2/ConnectionTest.java 20 Jul 2004 08:35:06 -0000 *************** *** 242,249 **** assertEquals(Connection.TRANSACTION_READ_COMMITTED, con.getTransactionIsolation()); - - Statement stmt = con.createStatement(); // Now run some tests with autocommit enabled. con.setAutoCommit(true); --- 242,247 ---- *************** *** 274,280 **** --- 272,297 ---- con.setAutoCommit(false); assertEquals(Connection.TRANSACTION_READ_COMMITTED, con.getTransactionIsolation()); + con.commit(); + + // Test that getTransactionIsolation() does not actually start a new txn. + con.getTransactionIsolation(); // Shouldn't start a new transaction. + con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); // Should be ok -- we're not in a transaction. + con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // Should still be ok. + + // Test that we can't change isolation mid-transaction + Statement stmt = con.createStatement(); + stmt.executeQuery("SELECT 1"); // Start transaction. + stmt.close(); + + try { + con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + fail("Expected an exception when changing transaction isolation mid-transaction"); + } catch (SQLException e) { + // Ok. + } + con.rollback(); TestUtil.closeDB(con); }
pgsql-jdbc by date: