Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server Message Handler and SQLException Chaining #2251

Merged
merged 15 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

javadoc class description

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

{
/**
* 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 @@ -4928,6 +4928,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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we now have 2 of these, add comment on how they differ and how they are used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

addWarning(new SQLServerWarning(sqlServerMessage.getSQLServerMessage()));
}

@Override
public void clearWarnings() throws SQLServerException {
warningSynchronization.lock();
Expand Down Expand Up @@ -8500,6 +8521,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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does the setter need to return anything? there is already a getter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just convenience...
Meaning: not have to call getter if you want to save the previous installed handler...
Which may be used if you want to restore previous installed handler at a later point!

For example the java Properties.getProperty("someName") does the same thing!
https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html#setProperty-java.lang.String-java.lang.String-

*/
@Override
public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler)
{
ISQLServerMessageHandler installedMessageHandler = this.serverMessageHandler;
this.serverMessageHandler = messageHandler;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this overwrites any existing, maybe should check and log a message?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes a setter usually overwrites previous values...
If you don't want to overwrite it, use the getter and check if you have one...

If you want: I can generate a log message (let me know the LogLevel)
But from my perspective, I would leave it as is

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
Loading