diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
index 2f54a98ab..a6054b1fc 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
@@ -483,6 +483,19 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold,
*/
void setAccessTokenCallbackClass(String accessTokenCallbackClass);
+ /**
+ * Get Currently installed message handler on the connection
+ * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)}
+ * @return
+ */
+ ISQLServerMessageHandler getServerMessageHandler();
+
+ /**
+ * Set message handler on the connection
+ * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)}
+ */
+ ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler);
+
/**
* Returns the current flag for calcBigDecimalPrecision.
*
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java
new file mode 100644
index 000000000..055e812b2
--- /dev/null
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java
@@ -0,0 +1,93 @@
+/*
+ * Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
+ * available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+package com.microsoft.sqlserver.jdbc;
+
+import java.sql.SQLException;
+
+public interface ISQLServerMessage
+{
+ /**
+ * Returns SQLServerError containing detailed info about SQL Server Message as received from SQL Server.
+ *
+ * @return SQLServerError
+ */
+ public SQLServerError getSQLServerMessage();
+
+ /**
+ * Returns error message as received from SQL Server
+ *
+ * @return Error Message
+ */
+ public String getErrorMessage();
+
+ /**
+ * Returns error number as received from SQL Server
+ *
+ * @return Error Number
+ */
+ public int getErrorNumber();
+
+ /**
+ * Returns error state as received from SQL Server
+ *
+ * @return Error State
+ */
+ public int getErrorState();
+
+ /**
+ * Returns Severity of error (as int value) as received from SQL Server
+ *
+ * @return Error Severity
+ */
+ public int getErrorSeverity();
+
+ /**
+ * Returns name of the server where exception occurs as received from SQL Server
+ *
+ * @return Server Name
+ */
+ public String getServerName();
+
+ /**
+ * Returns name of the stored procedure where exception occurs as received from SQL Server
+ *
+ * @return Procedure Name
+ */
+ public String getProcedureName();
+
+ /**
+ * Returns line number where the error occurred in Stored Procedure returned by getProcedureName()
as
+ * received from SQL Server
+ *
+ * @return Line Number
+ */
+ public long getLineNumber();
+
+ /**
+ * Creates a SQLServerException or SQLServerWarning from this SQLServerMessage
+ * @return
+ *
RAISERROR ('Progress message...', 0, 1) WITH NOWAIT
+ * For example code, see {@link #messageHandler(ISQLServerMessage)} + */ +public interface ISQLServerMessageHandler +{ + /** + * You can use the ISQLServerMessageHandler interface to customize the way JDBC handles error messages generated by the SQL Server. + * Implementing ISQLServerMessageHandler in your own class for handling error messages can provide the following benefits: + *
RAISERROR ('Progress message...', 0, 1) WITH NOWAIT
+ * public ISQLServerMessage messageHandler(ISQLServerMessage serverErrorOrWarning) + * { + * ISQLServerMessage retObj = serverErrorOrWarning; + * + * if (serverErrorOrWarning.isErrorMessage()) { + * + * // Downgrade: 2601 -- Cannot insert duplicate key row... + * if (2601 == serverErrorOrWarning.getErrorNumber()) { + * retObj = serverErrorOrWarning.getSQLServerMessage().toSQLServerInfoMessage(); + * } + * + * // Discard: 3701 -- Cannot drop the table ... + * if (3701 == serverErrorOrWarning.getErrorNumber()) { + * retObj = null; + * } + * } + * + * return retObj; + * } + + *+ * + * @param serverErrorOrWarning + * @return + *
+ * This is later on used when creating a SQLServerException.
+ * This simply create a SQLServerInfoMessage from this SQLServerError,
+ * without changing the message content.
+ * @return
+ */
+ public ISQLServerMessage toSQLServerInfoMessage()
+ {
+ return toSQLServerInfoMessage(-1, -1);
+ }
+
+ /**
+ * Downgrade a Error message into a Info message
+ *
+ * This simply create a SQLServerInfoMessage from this SQLServerError,
+ *
+ * @param newErrorSeverity - The new ErrorSeverity
+ *
+ * @return
+ */
+ public ISQLServerMessage toSQLServerInfoMessage(int newErrorSeverity)
+ {
+ return toSQLServerInfoMessage(newErrorSeverity, -1);
+ }
+
+ /**
+ * Downgrade a Error message into a Info message
+ *
+ * This simply create a SQLServerInfoMessage from this SQLServerError,
+ *
+ * @param newErrorSeverity - If you want to change the ErrorSeverity (-1: leave unchanged)
+ * @param newErrorNumber - If you want to change the ErrorNumber (-1: leave unchanged)
+ *
+ * @return
+ */
+ public ISQLServerMessage toSQLServerInfoMessage(int newErrorSeverity, int newErrorNumber)
+ {
+ if (newErrorSeverity != -1) {
+ this.setErrorSeverity(newErrorSeverity);
+ }
+
+ if (newErrorNumber != -1) {
+ this.setErrorNumber(newErrorNumber);
+ }
+
+ return new SQLServerInfoMessage(this);
+ }
+
+ /**
+ * Set a new ErrorSeverity for this Message
+ * @param newSeverity
+ */
+ public void setErrorSeverity(int newSeverity)
+ {
+ this.errorSeverity = newSeverity;
+ }
+
+ /**
+ * Set a new ErrorNumber for this Message
+ * @param newSeverity
+ */
+ public void setErrorNumber(int newErrorNumber)
+ {
+ this.errorNumber = newErrorNumber;
+ }
+
+ @Override
+ public SQLException toSqlExceptionOrSqlWarning()
+ {
+ return new SQLServerException(this);
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
index 943804dcf..77367708a 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
@@ -7,6 +7,7 @@
import java.sql.SQLFeatureNotSupportedException;
import java.text.MessageFormat;
+import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
@@ -178,6 +179,15 @@ static String getErrString(String errCode) {
logException(obj, errText, bStack);
}
+ public SQLServerException(SQLServerError sqlServerError) {
+ super(sqlServerError.getErrorMessage(),
+ generateStateCode(null, sqlServerError.getErrorNumber(), sqlServerError.getErrorState()),
+ sqlServerError.getErrorNumber(),
+ null);
+
+ this.sqlServerError = sqlServerError;
+ }
+
/**
* Constructs a new SQLServerException.
*
@@ -261,6 +271,21 @@ static void makeFromDatabaseError(SQLServerConnection con, Object obj, String er
SQLServerException.checkAndAppendClientConnId(errText, con), state, sqlServerError, bStack);
theException.setDriverErrorCode(DRIVER_ERROR_FROM_DATABASE);
+ // Add any extra messages to the SQLException error chain
+ List
+ * Instead of just holding the SQL Server message (like a normal SQLWarning, it also holds all the
+ * SQL Servers extended information, like: ErrorSeverity, ServerName, ProcName etc
+ *
+ * This enables client to print out extra information about the message.
+ * A SQLServerInfoMessage is produced when reading the TDS Stream and added to the Connection as a SQLServerWarning
+ */
+public final class SQLServerInfoMessage extends StreamPacket implements ISQLServerMessage {
+ SQLServerError msg = new SQLServerError();
+
+ SQLServerInfoMessage() {
+ super(TDS.TDS_MSG);
+ }
+
+ SQLServerInfoMessage(SQLServerError msg) {
+ super(TDS.TDS_MSG);
+ this.msg = msg;
+ }
+
+ @Override
+ void setFromTDS(TDSReader tdsReader) throws SQLServerException {
+ if (TDS.TDS_MSG != tdsReader.readUnsignedByte())
+ assert false;
+ msg.setContentsFromTDS(tdsReader);
+ }
+
+ @Override
+ public SQLServerError getSQLServerMessage() {
+ return msg;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ return msg.getErrorMessage();
+ }
+
+ @Override
+ public int getErrorNumber() {
+ return msg.getErrorNumber();
+ }
+
+ @Override
+ public int getErrorState() {
+ return msg.getErrorState();
+ }
+
+ @Override
+ public int getErrorSeverity() {
+ return msg.getErrorSeverity();
+ }
+
+ @Override
+ public String getServerName() {
+ return msg.getServerName();
+ }
+
+ @Override
+ public String getProcedureName()
+ {
+ return msg.getProcedureName();
+ }
+
+ @Override
+ public long getLineNumber()
+ {
+ return msg.getLineNumber();
+ }
+
+ /**
+ * Upgrade a Info message into a Error message
+ *
+ * This simply create a SQLServerError from this SQLServerInfoMessage,
+ * without changing the message content.
+ * @return
+ */
+ public ISQLServerMessage toSQLServerError()
+ {
+ return toSQLServerError(-1, -1);
+ }
+
+ /**
+ * Upgrade a Info message into a Error message
+ *
+ * This simply create a SQLServerError from this SQLServerInfoMessage.
+ *
+ * @param newErrorSeverity - The new ErrorSeverity
+ *
+ * @return
+ */
+ public ISQLServerMessage toSQLServerError(int newErrorSeverity)
+ {
+ return toSQLServerError(newErrorSeverity, -1);
+ }
+
+ /**
+ * Upgrade a Info message into a Error message
+ *
+ * This simply create a SQLServerError from this SQLServerInfoMessage.
+ *
+ * @param newErrorSeverity - If you want to change the ErrorSeverity (-1: leave unchanged)
+ * @param newErrorNumber - If you want to change the ErrorNumber (-1: leave unchanged)
+ *
+ * @return
+ */
+ public ISQLServerMessage toSQLServerError(int newErrorSeverity, int newErrorNumber)
+ {
+ if (newErrorSeverity != -1) {
+ this.msg.setErrorSeverity(newErrorSeverity);
+ }
+
+ if (newErrorNumber != -1) {
+ this.msg.setErrorNumber(newErrorNumber);
+ }
+
+ return new SQLServerError(this.msg);
+ }
+
+ @Override
+ public SQLException toSqlExceptionOrSqlWarning()
+ {
+ return new SQLServerWarning(this.msg);
+ }
+}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java
index 866b9ea13..cbfe102ca 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java
@@ -1620,7 +1620,6 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException {
}
}
}
-
// If the current command (whatever it was) produced an error then stop parsing and propagate it up.
// In this case, the command is likely to be a RAISERROR, but it could be anything.
if (doneToken.isError())
@@ -1690,8 +1689,8 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException {
@Override
boolean onInfo(TDSReader tdsReader) throws SQLServerException {
- StreamInfo infoToken = new StreamInfo();
- infoToken.setFromTDS(tdsReader);
+ SQLServerInfoMessage infoMessage = new SQLServerInfoMessage();
+ infoMessage.setFromTDS(tdsReader);
// Under some circumstances the server cannot produce the cursored result set
// that we requested, but produces a client-side (default) result set instead.
@@ -1705,13 +1704,40 @@ boolean onInfo(TDSReader tdsReader) throws SQLServerException {
// ErrorCause: Server cursor is not supported on the specified SQL, falling back to default result set
// ErrorCorrectiveAction: None required
//
- if (16954 == infoToken.msg.getErrorNumber())
+ if (16954 == infoMessage.msg.getErrorNumber())
executedSqlDirectly = true;
- SQLWarning warning = new SQLWarning(
- infoToken.msg.getErrorMessage(), SQLServerException.generateStateCode(connection,
- infoToken.msg.getErrorNumber(), infoToken.msg.getErrorState()),
- infoToken.msg.getErrorNumber());
+
+ // Call the message handler to see what that think of the message
+ // - discard
+ // - upgrade to Error
+ // - or simply pass on
+ ISQLServerMessageHandler msgHandler = ((ISQLServerConnection)getConnection()).getServerMessageHandler();
+ if (msgHandler != null) {
+
+ // Let the message handler decide if the error should be unchanged, up/down-graded or ignored
+ ISQLServerMessage srvMessage = msgHandler.messageHandler(infoMessage);
+
+ // Ignored
+ if (srvMessage == null) {
+ return true;
+ }
+
+ // The message handler changed it to an "Error Message"
+ if (srvMessage.isErrorMessage()) {
+ // Set/Add the error message to the "super"
+ addDatabaseError( (SQLServerError)srvMessage );
+ return true;
+ }
+
+ // Still a "info message", just set infoMessage and the code in the below section will create the Warnings
+ if (srvMessage.isInfoMessage()) {
+ infoMessage = (SQLServerInfoMessage)srvMessage;
+ }
+ }
+
+ // Create the SQLWarning and add them to the Warning chain
+ SQLWarning warning = new SQLServerWarning(infoMessage.msg);
if (sqlWarnings == null) {
sqlWarnings = new Vector<>();
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java
new file mode 100644
index 000000000..81d588bd9
--- /dev/null
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java
@@ -0,0 +1,47 @@
+/*
+ * Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
+ * available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+package com.microsoft.sqlserver.jdbc;
+
+import java.sql.SQLWarning;
+
+/**
+ * Holds information about SQL Server messages that is considered as Informational Messages (normally if SQL Server Severity is at 10)
+ *
+ * Instead of just holding the SQL Server message (like a normal SQLWarning, it also holds all the
+ * SQL Servers extended information, like: ErrorSeverity, ServerName, ProcName etc
+ *
+ * This enables client to print out extra information about the message.
+ * Do a "long running procedure" and check that the message handler receives the "feedback" messages
+ */
+ @Test
+ public void testMsgHandlerWithProcedureFeedback() throws Exception {
+ try (SQLServerConnection conn = getConnection()) {
+
+ class TestMsgHandler implements ISQLServerMessageHandler
+ {
+ int numOfCalls = 0;
+ Map
+ * Where all entries in the errorChain will be added {@link java.sql.SQLException#setNextException(SQLException)}
+ */
+ private List
+ * Like: In what procedure was the message produced.
+ *
+ * Like: In what procedure was the message produced.
+ */
+public class SQLServerWarning
+extends SQLWarning
+{
+ private static final long serialVersionUID = -5212432397705929142L;
+
+ /** SQL server error */
+ private SQLServerError sqlServerError;
+
+ /*
+ * Create a SQLWarning from an SQLServerError object
+ */
+ public SQLServerWarning(SQLServerError sqlServerError) {
+ super(sqlServerError.getErrorMessage(),
+ SQLServerException.generateStateCode(null, sqlServerError.getErrorNumber(), sqlServerError.getErrorState()),
+ sqlServerError.getErrorNumber(),
+ null);
+
+ this.sqlServerError = sqlServerError;
+ }
+
+ /**
+ * Returns SQLServerError object containing detailed info about exception as received from SQL Server. This API
+ * returns null if no server error has occurred.
+ *
+ * @return SQLServerError
+ */
+ public SQLServerError getSQLServerError() {
+ return sqlServerError;
+ }
+}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/StreamInfo.java b/src/main/java/com/microsoft/sqlserver/jdbc/StreamInfo.java
deleted file mode 100644
index 5b4cb571f..000000000
--- a/src/main/java/com/microsoft/sqlserver/jdbc/StreamInfo.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
- * available under the terms of the MIT License. See the LICENSE file in the project root for more information.
- */
-
-package com.microsoft.sqlserver.jdbc;
-
-final class StreamInfo extends StreamPacket {
- final SQLServerError msg = new SQLServerError();
-
- StreamInfo() {
- super(TDS.TDS_MSG);
- }
-
- void setFromTDS(TDSReader tdsReader) throws SQLServerException {
- if (TDS.TDS_MSG != tdsReader.readUnsignedByte())
- assert false;
- msg.setContentsFromTDS(tdsReader);
- }
-}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java
index c18d61a0c..90eebb8df 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java
@@ -197,6 +197,14 @@ final SQLServerError getDatabaseError() {
return databaseError;
}
+ public void addDatabaseError(SQLServerError databaseError) {
+ if (this.databaseError == null) {
+ this.databaseError = databaseError;
+ } else {
+ this.databaseError.addError(databaseError);
+ }
+ }
+
TDSTokenHandler(String logContext) {
this.logContext = logContext;
}
@@ -258,13 +266,29 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException {
}
boolean onError(TDSReader tdsReader) throws SQLServerException {
- if (null == databaseError) {
- databaseError = new SQLServerError();
- databaseError.setFromTDS(tdsReader);
- } else {
- (new SQLServerError()).setFromTDS(tdsReader);
+ SQLServerError tmpDatabaseError = new SQLServerError();
+ tmpDatabaseError.setFromTDS(tdsReader);
+
+ ISQLServerMessageHandler msgHandler = tdsReader.getConnection().getServerMessageHandler();
+ if (msgHandler != null) {
+ // Let the message handler decide if the error should be unchanged/down-graded or ignored
+ ISQLServerMessage srvMessage = msgHandler.messageHandler(tmpDatabaseError);
+
+ // Ignored
+ if (srvMessage == null) {
+ return true;
+ }
+
+ // Down-graded to a SQLWarning
+ if (srvMessage.isInfoMessage()) {
+ tdsReader.getConnection().addWarning(srvMessage);
+ return true;
+ }
}
+ // set/add the database error
+ addDatabaseError(tmpDatabaseError);
+
return true;
}
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java
index d758950f8..926b6a766 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java
@@ -521,6 +521,10 @@ private List
+ *
+ */
+ @Test
+ public void testMsgHandlerWithStatement() throws Exception {
+ try (SQLServerConnection conn = getConnection()) {
+
+ class TestMsgHandler implements ISQLServerMessageHandler
+ {
+ int numOfCalls = 0;
+ int numOfDowngrades = 0;
+ int numOfDiscards = 0;
+
+ @Override
+ public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning)
+ {
+ numOfCalls++;
+ ISQLServerMessage retObj = srvErrorOrWarning;
+
+ if (srvErrorOrWarning.isErrorMessage()) {
+
+ // Downgrade: 2601 -- Cannot insert duplicate key row in object 'dbo.#msghandler_tmp_table' with unique index 'ix_id'. The duplicate key value is (1)
+ if (2601 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = srvErrorOrWarning.getSQLServerMessage().toSQLServerInfoMessage();
+ numOfDowngrades++;
+ }
+
+ // Discard: 3701 -- Cannot drop the table '#msghandler_tmp_table', because it does not exist or you do not have permission.
+ if (3701 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = null;
+ numOfDiscards++;
+ }
+ }
+
+ if (srvErrorOrWarning.isInfoMessage()) {
+
+ // Discard: 3621 -- The statement has been terminated.
+ if (3621 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = null;
+ numOfDiscards++;
+ }
+ }
+
+ return retObj;
+ }
+ }
+ TestMsgHandler testMsgHandler = new TestMsgHandler();
+
+ // Create a massage handler
+ conn.setServerMessageHandler(testMsgHandler);
+
+ try (Statement stmnt = conn.createStatement()) {
+
+ stmnt.executeUpdate("CREATE TABLE #msghandler_tmp_table(id int, c1 varchar(255))");
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'create table', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'create table', at Statement.");
+
+ stmnt.executeUpdate("CREATE UNIQUE INDEX ix_id ON #msghandler_tmp_table(id)");
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'create index', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Statement.");
+
+ stmnt.executeUpdate("INSERT INTO #msghandler_tmp_table VALUES(1, 'row 1')");
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'first insert', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'first insert', at Statement.");
+
+ stmnt.executeUpdate("INSERT INTO #msghandler_tmp_table VALUES(1, 'row 1 - again - msg handler downgrades it')");
+ assertNotNull(conn .getWarnings(), "Expecting at least ONE SQLWarnings from 'second insert', which is a duplicate row, at Connection.");
+ assertNull (stmnt.getWarnings(), "Expecting NO SQLWarnings from 'second insert', which is a duplicate row, at Statement.");
+ conn.clearWarnings(); // Clear Warnings at Connection level
+
+ stmnt.executeUpdate("DROP TABLE #msghandler_tmp_table");
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'first drop table', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'first drop table', at Statement.");
+
+ stmnt.executeUpdate("DROP TABLE #msghandler_tmp_table"); // This should be IGNORED by the message handler
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'second drop table, since it should be IGNORED by the message handler', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'second drop table, since it should be IGNORED by the message handler', at Statement.");
+
+ // numOfCalls to the message handler should be: 3
+ assertEquals(3, testMsgHandler.numOfCalls, "Number of message calls to the message handler.");
+
+ // numOfDowngrades in the message handler should be: 1
+ assertEquals(1, testMsgHandler.numOfDowngrades, "Number of message Downgrades in the message handler.");
+
+ // numOfDiscards in the message handler should be: 2
+ assertEquals(2, testMsgHandler.numOfDiscards, "Number of message Discards in the message handler.");
+ }
+
+ } catch (SQLException ex) {
+ fail(TestResource.getResource("R_unexpectedErrorMessage"));
+ }
+ }
+
+
+
+ /**
+ * Test message handler with PreparedStatement
+ *
+ *
+ */
+ @Test
+ public void testMsgHandlerWithPreparedStatement() throws Exception {
+ try (SQLServerConnection conn = getConnection()) {
+
+ class TestMsgHandler implements ISQLServerMessageHandler
+ {
+ int numOfCalls = 0;
+ int numOfDowngrades = 0;
+ int numOfDiscards = 0;
+
+ @Override
+ public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning)
+ {
+ numOfCalls++;
+ ISQLServerMessage retObj = srvErrorOrWarning;
+
+ if (srvErrorOrWarning.isErrorMessage()) {
+
+ // Downgrade: 2601 -- Cannot insert duplicate key row in object 'dbo.#msghandler_tmp_table' with unique index 'ix_id'. The duplicate key value is (1)
+ if (2601 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = srvErrorOrWarning.getSQLServerMessage().toSQLServerInfoMessage();
+ numOfDowngrades++;
+ }
+
+ // Discard: 3701 -- Cannot drop the table '#msghandler_tmp_table', because it does not exist or you do not have permission.
+ if (3701 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = null;
+ numOfDiscards++;
+ }
+ }
+
+ if (srvErrorOrWarning.isInfoMessage()) {
+
+ // Discard: 3621 -- The statement has been terminated.
+ if (3621 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = null;
+ numOfDiscards++;
+ }
+ }
+
+ return retObj;
+ }
+ }
+ TestMsgHandler testMsgHandler = new TestMsgHandler();
+
+ // Create a massage handler
+ conn.setServerMessageHandler(testMsgHandler);
+
+ try (Statement stmnt = conn.createStatement()) {
+ stmnt.executeUpdate("CREATE TABLE #msghandler_tmp_table(id int, c1 varchar(255))");
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'create table', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'create table', at Statement.");
+ }
+
+ try (Statement stmnt = conn.createStatement()) {
+ stmnt.executeUpdate("CREATE UNIQUE INDEX ix_id ON #msghandler_tmp_table(id)");
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'create index', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Statement.");
+ }
+
+ try (PreparedStatement stmnt = conn.prepareStatement("INSERT INTO #msghandler_tmp_table VALUES(?, ?)")) {
+ stmnt.setInt(1, 1);
+ stmnt.setString(2, "row 1");
+ stmnt.executeUpdate();
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'first insert', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'first insert', at Statement.");
+ }
+
+ try (PreparedStatement stmnt = conn.prepareStatement("INSERT INTO #msghandler_tmp_table VALUES(?, ?)")) {
+ stmnt.setInt(1, 1);
+ stmnt.setString(2, "row 1 - again - msg handler downgrades it");
+ stmnt.executeUpdate();
+ assertNotNull(conn .getWarnings(), "Expecting at least ONE SQLWarnings from 'second insert', which is a duplicate row, at Connection.");
+ assertNull (stmnt.getWarnings(), "Expecting NO SQLWarnings from 'second insert', which is a duplicate row, at Statement.");
+ conn.clearWarnings(); // Clear Warnings at Connection level
+ }
+
+ try (Statement stmnt = conn.createStatement()) {
+ stmnt.executeUpdate("DROP TABLE #msghandler_tmp_table");
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'first drop table', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'first drop table', at Statement.");
+ }
+
+ try (Statement stmnt = conn.createStatement()) {
+ stmnt.executeUpdate("DROP TABLE #msghandler_tmp_table");
+ assertNull(conn .getWarnings(), "Expecting NO SQLWarnings from 'first drop table', at Connection.");
+ assertNull(stmnt.getWarnings(), "Expecting NO SQLWarnings from 'first drop table', at Statement.");
+ }
+
+
+ // numOfCalls to the message handler should be: 3
+ assertEquals(3, testMsgHandler.numOfCalls, "Number of message calls to the message handler.");
+
+ // numOfDowngrades in the message handler should be: 1
+ assertEquals(1, testMsgHandler.numOfDowngrades, "Number of message Downgrades in the message handler.");
+
+ // numOfDiscards in the message handler should be: 2
+ assertEquals(2, testMsgHandler.numOfDiscards, "Number of message Discards in the message handler.");
+
+ } catch (SQLException ex) {
+ fail(TestResource.getResource("R_unexpectedErrorMessage"));
+ }
+ }
+
+
+
+ /**
+ * Test message handler with CallableStatement
+ *
+ *
+ */
+ @Test
+ public void testMsgHandlerWithCallableStatement() throws Exception {
+ try (SQLServerConnection conn = getConnection()) {
+
+ class TestMsgHandler implements ISQLServerMessageHandler
+ {
+ int numOfCalls = 0;
+ int numOfDowngrades = 0;
+ int numOfDiscards = 0;
+
+ @Override
+ public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning)
+ {
+ numOfCalls++;
+ ISQLServerMessage retObj = srvErrorOrWarning;
+
+ if (srvErrorOrWarning.isErrorMessage()) {
+
+ // Downgrade: 2601 -- Cannot insert duplicate key row in object 'dbo.#msghandler_tmp_table' with unique index 'ix_id'. The duplicate key value is (1)
+ if (2601 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = srvErrorOrWarning.getSQLServerMessage().toSQLServerInfoMessage();
+ numOfDowngrades++;
+ }
+
+ // Downgrade: 3701 -- Cannot drop the table '#msghandler_tmp_table', because it does not exist or you do not have permission.
+ if (3701 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = null;
+ numOfDiscards++;
+ }
+ }
+
+ if (srvErrorOrWarning.isInfoMessage()) {
+
+ // Discard: 3621 -- The statement has been terminated.
+ if (3621 == srvErrorOrWarning.getErrorNumber()) {
+ retObj = null;
+ numOfDiscards++;
+ }
+ }
+
+ return retObj;
+ }
+ }
+ TestMsgHandler testMsgHandler = new TestMsgHandler();
+
+ // Create a massage handler
+ conn.setServerMessageHandler(testMsgHandler);
+
+ // SQL to create procedure
+ String sqlCreateProc = ""
+ + "CREATE PROCEDURE #msghandler_tmp_proc( \n"
+ + " @out_row_count INT OUTPUT \n"
+ + ") \n"
+ + "AS \n"
+ + "BEGIN \n"
+ + " -- Create a dummy table, with index \n"
+ + " CREATE TABLE #msghandler_tmp_table(id int, c1 varchar(255)) \n"
+ + " CREATE UNIQUE INDEX ix_id ON #msghandler_tmp_table(id) \n"
+ + " \n"
+ + " -- Insert records 1 \n"
+ + " INSERT INTO #msghandler_tmp_table VALUES(1, 'row 1') \n"
+ + " \n"
+ + " -- Insert records 1 -- Again, which will FAIL, but the message handler will downgrade it into a INFO Message \n"
+ + " INSERT INTO #msghandler_tmp_table VALUES(1, 'row 1 - again - msg handler downgrades it') \n"
+ + " \n"
+ + " -- Count records \n"
+ + " SELECT @out_row_count = count(*) \n"
+ + " FROM #msghandler_tmp_table \n"
+ + " \n"
+ + " -- Drop the table \n"
+ + " DROP TABLE #msghandler_tmp_table \n"
+ + " \n"
+ + " -- Drop the table agin... The message handler will DISCARD the error\n"
+ + " DROP TABLE #msghandler_tmp_table \n"
+ + " \n"
+ + " RETURN 1 \n"
+ + "END \n"
+ ;
+
+ // Create the proc
+ try (Statement stmnt = conn.createStatement()) {
+ stmnt.executeUpdate(sqlCreateProc);
+ assertEquals(0, getWarningCount("Conn" , conn .getWarnings()), "Expecting NO SQLWarnings from 'create proc', at Connection.");
+ assertEquals(0, getWarningCount("Stmnt", conn .getWarnings()), "Expecting NO SQLWarnings from 'create proc', at Statement.");
+ }
+
+ // Execute the proc
+ try (CallableStatement cstmnt = conn.prepareCall("{ ? =call #msghandler_tmp_proc(?) }")) {
+ cstmnt.registerOutParameter(1, Types.INTEGER);
+ cstmnt.registerOutParameter(2, Types.INTEGER);
+ cstmnt.execute();
+ int procReturnCode = cstmnt.getInt(1);
+ int procRowCount = cstmnt.getInt(2);
+
+ assertEquals(1, procReturnCode, "Expecting ReturnCode 1 from the temp procedure.");
+ assertEquals(1, procRowCount , "Expecting procRowCount 1 from the temp procedure.");
+
+ assertEquals(1, getWarningCount("Conn" , conn .getWarnings()), "Expecting NO SQLWarnings from 'exec proc', at Connection.");
+ assertEquals(0, getWarningCount("CStmnt", cstmnt.getWarnings()), "Expecting NO SQLWarnings from 'exec proc', at CallableStatement.");
+ }
+
+ // numOfCalls to the message handler should be: 3
+ assertEquals(3, testMsgHandler.numOfCalls, "Number of message calls to the message handler.");
+
+ // numOfDowngrades in the message handler should be: 1
+ assertEquals(1, testMsgHandler.numOfDowngrades, "Number of message Downgrades in the message handler.");
+
+ // numOfDiscards in the message handler should be: 2
+ assertEquals(2, testMsgHandler.numOfDiscards, "Number of message Discards in the message handler.");
+
+ } catch (SQLException ex) {
+ fail(TestResource.getResource("R_unexpectedErrorMessage"));
+ }
+ }
+
+
+
+
+ /**
+ * Test message handler with CallableStatement -- and "feedback" messages
+ *