List:Commits« Previous MessageNext Message »
From:Todd Farmer Date:October 15 2008 5:13am
Subject:bzr push into connector-j/branches/branch_5_1 branch (todd.farmer:740
to 741) Bug#40031
View as plain text  
  741 Todd Farmer	2008-10-14
      BUG#40031 - allow CallableStatement.execute() to call a stored procedure that has been defined as NO SQL or SQL READ DATA.
added:
  src/testsuite/simple/ReadOnlyCallableStatementTest.java
modified:
  src/com/mysql/jdbc/CallableStatement.java
  src/com/mysql/jdbc/DatabaseMetaData.java
  src/com/mysql/jdbc/DatabaseMetaDataUsingInfoSchema.java
  src/com/mysql/jdbc/PreparedStatement.java
  src/testsuite/regression/ConnectionRegressionTest.java

  740 mark@stripped	2008-10-13
       Fixed BUG#34185 - Statement.getGeneratedKeys() does not raise exception when statement was not created with Statement.RETURN_GENERATED_KEYS flags.
modified:
  CHANGES
  src/com/mysql/jdbc/CallableStatement.java
  src/com/mysql/jdbc/StatementImpl.java
  src/testsuite/regression/StatementRegressionTest.java

=== modified file 'src/com/mysql/jdbc/CallableStatement.java'
--- a/src/com/mysql/jdbc/CallableStatement.java	2008-10-13 16:46:27 +0000
+++ b/src/com/mysql/jdbc/CallableStatement.java	2008-10-15 05:12:44 +0000
@@ -34,7 +34,9 @@ import java.sql.Clob;
 import java.sql.Date;
 import java.sql.ParameterMetaData;
 import java.sql.Ref;
+import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.sql.Types;
@@ -147,6 +149,10 @@ public class CallableStatement extends P
 
 		Map parameterMap;
 
+		boolean isReadOnlySafeProcedure = false;
+		
+		boolean isReadOnlySafeChecked = false;
+
 		/**
 		 * Constructor that converts a full list of parameter metadata into one
 		 * that only represents the placeholders present in the {CALL ()}.
@@ -161,6 +167,8 @@ public class CallableStatement extends P
 			int[] localParameterMap = placeholderToParameterIndexMap;
 			int parameterMapLength = localParameterMap.length;
 			
+			this.isReadOnlySafeProcedure = fullParamInfo.isReadOnlySafeProcedure;
+			this.isReadOnlySafeChecked = fullParamInfo.isReadOnlySafeChecked;
 			parameterList = new ArrayList(fullParamInfo.numParameters);
 			parameterMap = new HashMap(fullParamInfo.numParameters);
 			
@@ -2352,5 +2360,71 @@ public class CallableStatement extends P
 		setNCharacterStream(getNamedParamIndex(parameterName, false), value, length);
 		
 	}
+	
+	/**
+	 * Check whether the stored procedure alters any data or is safe for read-only usage.
+	 * 
+	 * @return true if procedure does not alter data
+	 * @throws SQLException
+	 */
+	private boolean checkReadOnlyProcedure() throws SQLException {
+		if (this.connection.getNoAccessToProcedureBodies()) {
+			return false;
+		}
+		
+		synchronized(this.paramInfo){
+			if(this.paramInfo.isReadOnlySafeChecked){
+				return this.paramInfo.isReadOnlySafeProcedure;
+			}
+		}
+		
+		ResultSet rs = null;
+		try {
+			String procName = extractProcedureName();
+
+
+			String catalog = this.currentCatalog;
+			
+			if (procName.indexOf(".") != -1) {
+				catalog = procName.substring(0, procName.indexOf("."));
+				procName = procName.substring(procName.indexOf(".") + 1);
+				procName = new String(StringUtils.stripEnclosure(procName.getBytes(), "`", "`"));
+								}
+			PreparedStatement ps = ((DatabaseMetaData) this.connection.getMetaData()).prepareMetaDataSafeStatement(
+					"SELECT SQL_DATA_ACCESS FROM "
+						+ " information_schema.routines "
+						+ " WHERE routine_schema = ? "
+						+ " AND routine_name = ?");
+			
+			ps.setString(1, catalog);
+			ps.setString(2, procName);
+			rs = ps.executeQuery();
+			if(rs.next()) {
+				String sqlDataAccess = rs.getString(1);
+				rs.close();
+				ps.close();
+				if(sqlDataAccess.equals("READS SQL DATA") || sqlDataAccess.equals("NO SQL")){
+					synchronized(this.paramInfo){
+						this.paramInfo.isReadOnlySafeChecked = true;
+						this.paramInfo.isReadOnlySafeProcedure = true;
+					}
+					return true;
+				}
+			}
+		} catch (SQLException e) {
+			// swallow the Exception
+		}
+		synchronized(this.paramInfo){
+			this.paramInfo.isReadOnlySafeChecked = false;
+			this.paramInfo.isReadOnlySafeProcedure = false;
+		}
+		return false;
+					
+	}
+
+	protected boolean checkReadOnlySafeStatement() throws SQLException {
+		return (super.checkReadOnlySafeStatement() || this.checkReadOnlyProcedure());
+	}
+
 
 }

=== modified file 'src/com/mysql/jdbc/DatabaseMetaData.java'
--- a/src/com/mysql/jdbc/DatabaseMetaData.java	2008-09-09 17:58:50 +0000
+++ b/src/com/mysql/jdbc/DatabaseMetaData.java	2008-10-15 05:12:44 +0000
@@ -8129,4 +8129,26 @@ public class DatabaseMetaData implements
 	public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {
 		return true;
 	}
+	
+	/**
+	 * Get a prepared statement to query information_schema tables.
+	 * 
+	 * @return PreparedStatement
+	 * @throws SQLException
+	 */
+	protected PreparedStatement prepareMetaDataSafeStatement(String sql)
+			throws SQLException {
+		// Can't use server-side here as we coerce a lot of types to match
+		// the spec.
+		PreparedStatement pStmt = (PreparedStatement) this.conn
+				.clientPrepareStatement(sql);
+
+		if (pStmt.getMaxRows() != 0) {
+			pStmt.setMaxRows(0);
+		}
+
+		pStmt.setHoldResultsOpenOverClose(true);
+
+		return pStmt;
+	}
 }

=== modified file 'src/com/mysql/jdbc/DatabaseMetaDataUsingInfoSchema.java'
--- a/src/com/mysql/jdbc/DatabaseMetaDataUsingInfoSchema.java	2008-02-21 02:52:52 +0000
+++ b/src/com/mysql/jdbc/DatabaseMetaDataUsingInfoSchema.java	2008-10-15 05:12:44 +0000
@@ -1223,18 +1223,5 @@ public class DatabaseMetaDataUsingInfoSc
 		}
 	}
 
-	private PreparedStatement prepareMetaDataSafeStatement(String sql)
-			throws SQLException {
-		// Can't use server-side here as we coerce a lot of types to match
-		// the spec.
-		PreparedStatement pStmt = (PreparedStatement) this.conn.clientPrepareStatement(sql);
 
-		if (pStmt.getMaxRows() != 0) {
-			pStmt.setMaxRows(0);
-		}
-
-		pStmt.setHoldResultsOpenOverClose(true);
-
-		return pStmt;
-	}
 }

=== modified file 'src/com/mysql/jdbc/PreparedStatement.java'
--- a/src/com/mysql/jdbc/PreparedStatement.java	2008-07-31 13:21:48 +0000
+++ b/src/com/mysql/jdbc/PreparedStatement.java	2008-10-15 05:12:44 +0000
@@ -894,6 +894,16 @@ public class PreparedStatement extends c
 			bytesOut.write(buf, lastwritten, size - lastwritten);
 		}
 	}
+	
+	/**
+	 * Check to see if the statement is safe for read-only slaves after failover.
+	 * 
+	 * @return true if safe for read-only.
+	 * @throws SQLException
+	 */
+	protected boolean checkReadOnlySafeStatement() throws SQLException {
+		return ((!this.connection.isReadOnly()) || (this.firstCharOfStmt == 'S'));
+	}
 
 	/**
 	 * Some prepared statements return multiple results; the execute method
@@ -911,10 +921,10 @@ public class PreparedStatement extends c
 		
 		ConnectionImpl locallyScopedConn = this.connection;
 		
-		if (locallyScopedConn.isReadOnly() && (this.firstCharOfStmt != 'S')) {
-			throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") //$NON-NLS-1$
-					+ Messages.getString("PreparedStatement.21"), //$NON-NLS-1$
-					SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
+		if(!checkReadOnlySafeStatement()) {
+			 throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") //$NON-NLS-1$
+							+ Messages.getString("PreparedStatement.21"), //$NON-NLS-1$
+							SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
 		}
 		
 		ResultSetInternalMethods rs = null;

=== modified file 'src/testsuite/regression/ConnectionRegressionTest.java'
--- a/src/testsuite/regression/ConnectionRegressionTest.java	2008-09-29 18:31:30 +0000
+++ b/src/testsuite/regression/ConnectionRegressionTest.java	2008-10-15 05:12:44 +0000
@@ -1606,7 +1606,7 @@ public class ConnectionRegressionTest ex
 		}
 	}
 
-	private Connection getMasterSlaveReplicationConnection()
+	protected Connection getMasterSlaveReplicationConnection()
 			throws SQLException {
 
 		Connection replConn = new ReplicationDriver().connect(

=== added file 'src/testsuite/simple/ReadOnlyCallableStatementTest.java'
--- a/src/testsuite/simple/ReadOnlyCallableStatementTest.java	1970-01-01 00:00:00 +0000
+++ b/src/testsuite/simple/ReadOnlyCallableStatementTest.java	2008-10-15 05:12:44 +0000
@@ -0,0 +1,126 @@
+package testsuite.simple;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import testsuite.regression.ConnectionRegressionTest;
+
+public class ReadOnlyCallableStatementTest extends ConnectionRegressionTest {
+	public ReadOnlyCallableStatementTest(String name) {
+		super(name);
+
+		// TODO Auto-generated constructor stub
+	}
+
+	public void testReadOnlyWithProcBodyAccess() throws Exception {
+		if (versionMeetsMinimum(5, 0)) {
+			Connection replConn = null;
+			Properties props = getMasterSlaveProps();
+			props.setProperty("autoReconnect", "true");
+	
+			
+			try {
+				createProcedure("testProc1", "()\n"
+								+ "READS SQL DATA\n"
+								+ "begin\n"
+								+ "SELECT NOW();\n"
+								+ "end\n");
+
+				createProcedure("`testProc.1`", "()\n"
+						+ "READS SQL DATA\n"
+						+ "begin\n"
+						+ "SELECT NOW();\n"
+						+ "end\n");
+				
+				replConn = getMasterSlaveReplicationConnection();
+				replConn.setReadOnly(true);
+				
+				CallableStatement stmt = replConn.prepareCall("CALL testProc1()");
+				stmt.execute();
+				stmt.execute();
+				
+				stmt = replConn.prepareCall("CALL test.testProc1()");
+				stmt.execute();
+				
+				stmt = replConn.prepareCall("CALL test.`testProc.1`()");
+				stmt.execute();
+				
+			} finally {
+			
+				closeMemberJDBCResources();
+				
+				if (replConn != null) {
+					replConn.close();
+				}
+			}
+		}
+	}
+	
+	public void testNotReadOnlyWithProcBodyAccess() throws Exception {
+		if (versionMeetsMinimum(5, 0)) {
+			
+			Connection replConn = null;
+			Properties props = getMasterSlaveProps();
+			props.setProperty("autoReconnect", "true");
+		
+			
+			try {
+				createProcedure("testProc2", "()\n"
+								+ "MODIFIES SQL DATA\n"
+								+ "begin\n"
+								+ "SELECT NOW();\n"
+								+ "end\n");
+
+				createProcedure("`testProc.2`", "()\n"
+						+ "MODIFIES SQL DATA\n"
+						+ "begin\n"
+						+ "SELECT NOW();\n"
+						+ "end\n");
+				
+				replConn = getMasterSlaveReplicationConnection();
+				replConn.setReadOnly(true);
+				
+				CallableStatement stmt = replConn.prepareCall("CALL testProc2()");
+
+				try{
+					stmt.execute();
+					fail("Should not execute because procedure modifies data.");
+				} catch (SQLException e) {
+					assertEquals("Should error for read-only connection.", e.getSQLState(), "S1009");
+				}
+
+				stmt = replConn.prepareCall("CALL test.testProc2()");
+
+				try{
+					stmt.execute();
+					fail("Should not execute because procedure modifies data.");
+				} catch (SQLException e) {
+					assertEquals("Should error for read-only connection.", e.getSQLState(), "S1009");
+				}
+
+				stmt = replConn.prepareCall("CALL test.`testProc.2`()");
+
+				try{
+					stmt.execute();
+					fail("Should not execute because procedure modifies data.");
+				} catch (SQLException e) {
+					assertEquals("Should error for read-only connection.", e.getSQLState(), "S1009");
+				}
+
+				
+			} finally {
+			
+				closeMemberJDBCResources();
+				
+				if (replConn != null) {
+					replConn.close();
+				}
+			}
+		}
+	}
+	
+
+
+}

Thread
bzr push into connector-j/branches/branch_5_1 branch (todd.farmer:740to 741) Bug#40031Todd Farmer15 Oct