Skip to content

Commit

Permalink
Server Message Handler and SQLException Chaining (#2251)
Browse files Browse the repository at this point in the history
* first commit on: ServerMessageHandler - to intercept/ignore/up-grade/down-grade serv messages, and Chained Exceptions so we can see many srv messages in a SQLException

* Added 3 classes

* And the changes made to existing files

* Removed get/set ServerMessageHandler from ISQLServerStatement and SQLServerStatement
Simplified some code and formatted it to meet requirements
Added test cases

* smaller simplifications

* fixed Copyright in one file!
Also fixed smaller code format mishaps

* Fixed code formatting and removed some stuff from the test cases

* removed public for my get/setMessageHandler from interface ISQLServerConnection

* Code formatting

* Changed a cast in 'SQLServerStatement' from 'SQLServerConnection' to 'ISQLServerConnection' this might solve my strange problem!
Also added get/setServerMessageHandler to getVerifiedMethodNames() in RequestBoundaryMethodsTest.java

* Update SQLServerConnectionPoolProxy.java

* Made some changes requested by reviewer: lilgreenbird

---------

Co-authored-by: Jeffery Wasty <v-jeffwasty@microsoft.com>
  • Loading branch information
goranschwarz and Jeffery-Wasty committed Mar 22, 2024
1 parent 9a8849b commit c74a51e
Show file tree
Hide file tree
Showing 15 changed files with 1,227 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
93 changes: 93 additions & 0 deletions src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java
Original file line number Diff line number Diff line change
@@ -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 <code>getProcedureName()</code> as
* received from SQL Server
*
* @return Line Number
*/
public long getLineNumber();

/**
* Creates a SQLServerException or SQLServerWarning from this SQLServerMessage<br>
* @return
* <ul>
* <li>SQLServerException if it's a SQLServerError object</li>
* <li>SQLServerWarning if it's a SQLServerInfoMessage object</li>
* </ul>
*/
public SQLException toSqlExceptionOrSqlWarning();

/**
* Check if this is a isErrorMessage
* @return true if it's an instance of SQLServerError
*/
public default boolean isErrorMessage() {
return this instanceof SQLServerError;
}

/**
* Check if this is a SQLServerInfoMessage
* @return true if it's an instance of SQLServerInfoMessage
*/
public default boolean isInfoMessage() {
return this instanceof SQLServerInfoMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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;

/**
* 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:
* <ul>
* <li><b>"message feedback"</b><br>
* Display Server messages from a long running SQL Statement<br>
* Like <code>RAISERROR ('Progress message...', 0, 1) WITH NOWAIT</code><br>
* Or Status messages from a running backup...<br>
* </li>
* <li><b>"Universal" error logging</b><br>
* Your error-message handler can contain the logic for handling all error logging.
* </li>
* <li><b>"Universal" error handling</b><br>
* Error-handling logic can be placed in your error-message handler, instead of being repeated throughout your application.
* </li>
* <li><b>Remapping of error-message severity</b>, based on application requirements<br>
* Your error-message handler can contain logic for recognizing specific error messages, and downgrading or upgrading
* their severity based on application considerations rather than the severity rating of the server.
* For example, during a cleanup operation that deletes old rows, you might want to downgrade the severity of a
* message that a row does not exist. However, you may want to upgrade the severity in other circumstances.
* </li>
* </ul>
* <p>
* 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:
* <ul>
* <li><b>"message feedback"</b><br>
* Display Server messages from a long running SQL Statement<br>
* Like <code>RAISERROR ('Progress message...', 0, 1) WITH NOWAIT</code><br>
* Or Status messages from a running backup...<br>
* </li>
* <li><b>"Universal" error logging</b><br>
* Your error-message handler can contain the logic for handling all error logging.
* </li>
* <li><b>"Universal" error handling</b><br>
* Error-handling logic can be placed in your error-message handler, instead of being repeated throughout your application.
* </li>
* <li><b>Remapping of error-message severity</b>, based on application requirements<br>
* Your error-message handler can contain logic for recognizing specific error messages, and downgrading or upgrading
* their severity based on application considerations rather than the severity rating of the server.
* For example, during a cleanup operation that deletes old rows, you might want to downgrade the severity of a
* message that a row does not exist. However, you may want to upgrade the severity in other circumstances.
* </li>
* </ul>
*
* Example code:
* <pre>
* 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;
* }
* </pre>
*
* @param serverErrorOrWarning
* @return
* <ul>
* <li><b>unchanged</b> same object as passed in.<br>
* The JDBC driver will works as if no message hander was installed<br>
* Possibly used for logging functionality<br>
* </li>
* <li><b>null</b><br>
* The JDBC driver will <i>discard</i> this message. No SQLException will be thrown
* </li>
* <li><b>SQLServerInfoMessage</b> object<br>
* Create a "SQL warning" from a input database error, and return it.
* This results in the warning being added to the warning-message chain.
* </li>
* <li><b>SQLServerError</b> object<br>
* If the originating message is a SQL warning (SQLServerInfoMessage object), messageHandler can evaluate
* the SQL warning as urgent and create and return a SQL exception (SQLServerError object)
* to be thrown once control is returned to the JDBC Driver.
* </li>
* </ul>
*/
ISQLServerMessage messageHandler(ISQLServerMessage serverErrorOrWarning);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4927,6 +4927,27 @@ void addWarning(String warningString) {
}
}

// Any changes to SQLWarnings should be synchronized.
/** Used to add plain SQLWarning messages (if they do not hold extended information, like: ErrorSeverity, ServerName, ProcName etc */
void addWarning(SQLWarning sqlWarning) {
warningSynchronization.lock();
try {
if (null == sqlWarnings) {
sqlWarnings = sqlWarning;
} else {
sqlWarnings.setNextWarning(sqlWarning);
}
} finally {
warningSynchronization.unlock();
}
}

// Any changes to SQLWarnings should be synchronized.
/** Used to add messages that holds extended information, like: ErrorSeverity, ServerName, ProcName etc */
void addWarning(ISQLServerMessage sqlServerMessage) {
addWarning(new SQLServerWarning(sqlServerMessage.getSQLServerMessage()));
}

@Override
public void clearWarnings() throws SQLServerException {
warningSynchronization.lock();
Expand Down Expand Up @@ -8499,6 +8520,34 @@ public void setIPAddressPreference(String iPAddressPreference) {
public String getIPAddressPreference() {
return activeConnectionProperties.getProperty(SQLServerDriverStringProperty.IPADDRESS_PREFERENCE.toString());
}



/** Message handler */
private transient ISQLServerMessageHandler serverMessageHandler;

/**
* Set current message handler
*
* @param messageHandler
* @return The previously installed message handler (null if none)
*/
@Override
public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler)
{
ISQLServerMessageHandler installedMessageHandler = this.serverMessageHandler;
this.serverMessageHandler = messageHandler;
return installedMessageHandler;
}

/**
* @return Get Currently installed message handler on the connection
*/
@Override
public ISQLServerMessageHandler getServerMessageHandler()
{
return this.serverMessageHandler;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,16 @@ public void setAccessTokenCallbackClass(String accessTokenCallbackClass) {
wrappedConnection.setAccessTokenCallbackClass(accessTokenCallbackClass);
}

@Override
public ISQLServerMessageHandler getServerMessageHandler() {
return wrappedConnection.getServerMessageHandler();
}

@Override
public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler) {
return wrappedConnection.setServerMessageHandler(messageHandler);
}

/**
* Returns the current value for 'calcBigDecimalPrecision'.
*
Expand Down

0 comments on commit c74a51e

Please sign in to comment.