From 19500899ca6b4c703238024e55604d8141a0a4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20Schwarz?= Date: Thu, 9 Nov 2023 15:28:46 +0100 Subject: [PATCH 01/12] 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 --- .../sqlserver/jdbc/{StreamInfo.java => SQLServerInfoMessage.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/com/microsoft/sqlserver/jdbc/{StreamInfo.java => SQLServerInfoMessage.java} (100%) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/StreamInfo.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java similarity index 100% rename from src/main/java/com/microsoft/sqlserver/jdbc/StreamInfo.java rename to src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java From 3d08589ab84a5317130b4d475b879bb7919940fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20Schwarz?= Date: Thu, 9 Nov 2023 15:30:42 +0100 Subject: [PATCH 02/12] Added 3 classes --- .../sqlserver/jdbc/ISQLServerMessage.java | 82 +++++++++++++++++ .../jdbc/ISQLServerMessageHandler.java | 87 +++++++++++++++++++ .../sqlserver/jdbc/SQLServerWarning.java | 36 ++++++++ 3 files changed, 205 insertions(+) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java 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..f422f9fef --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java @@ -0,0 +1,82 @@ +/* + * 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 ISQLServerMessage containing detailed info about SQL Server Message as received from SQL Server. + * + * @return ISQLServerMessage + */ +// public ISQLServerMessage getSQLServerMessage(); + public SQLServerError getSQLServerMessage(); + +// /** +// * Set SQLServerError which holds detailed info about SQL Server Message. +// * +// * @return SQLServerError +// */ +// public void setSQLServerMessage(SQLServerError msg); + + /** + * 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 + */ + public SQLException toSqlExceptionOrSqlWarning(); + +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java new file mode 100644 index 000000000..17c304019 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (C) 2010-2019 Goran Schwarz + * + * This file is part of DbxTune + * DbxTune is a family of sub-products *Tune, hence the Dbx + * Here are some of the tools: AseTune, IqTune, RsTune, RaxTune, HanaTune, + * SqlServerTune, PostgresTune, MySqlTune, MariaDbTune, Db2Tune, ... + * + * DbxTune is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DbxTune is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DbxTune. If not, see . + ******************************************************************************/ +package com.microsoft.sqlserver.jdbc; + +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: + *
    + *
  • "message feedback"
    + * Display Server messages from a long running SQL Statement
    + * Like RAISERROR ('Progress message...', 0, 1) WITH NOWAIT
    + * Or Status message from a running backup...
    + *
  • + *
  • "Universal" error logging
    + * Your error-message handler can contain the logic for handling all error logging. + *
  • + *
  • "Universal" error handling
    + * Error-handling logic can be placed in your error-message handler, instead of being repeated throughout your application. + *
  • + *
  • Remapping of error-message severity, based on application requirements
    + * 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. + *
  • + *
+ * + * Example code: + *
+     * public StreamPacket messageHandler(StreamPacket databaseErrorOrWarning)
+     * {
+     * FIXME: CHANGE THE BELOW CODE TO SOMETHING BETTER
+     * TODO: SQLServerError and needs SQLServerInfoMessage needs to extend a new class "SQLServerMessage" or similar... which makes it easier ...
+     *     System.out.println("--------------------messageHandler received: " + databaseErrorOrWarning);
+     *     if (databaseErrorOrWarning instanceof SQLServerError) {
+     *         System.out.println("--------------------DOWNGRADE-------------------: " + databaseErrorOrWarning);
+     *         databaseErrorOrWarning = databaseErrorOrWarning = errorMsg.toSQLServerInfoMessage();
+     *     }
+     *     return databaseErrorOrWarning;
+     * }
+    
+     * 
+ * + * @param databaseErrorOrWarning + * @return + *
    + *
  • unchanged same object as passed in.
    + * The JDBC driver will works as if no message hander was installed
    + * Possibly used for logging functionality
    + *
  • + *
  • null
    + * The JDBC driver will discard this message. No SQLException will be thrown + *
  • + *
  • SQLServerInfoMessage object
    + * Create a "SQL warning" from a input database error, and return it. + * This results in the warning being added to the warning-message chain. + *
  • + *
  • SQLServerError object
    + * 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. + *
  • + *
+ */ + ISQLServerMessage messageHandler(ISQLServerMessage databaseErrorOrWarning); +} 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..81a99d94a --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java @@ -0,0 +1,36 @@ +/* + * 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; + +public class SQLServerWarning +extends SQLWarning +{ + private static final long serialVersionUID = -5212432397705929142L; + + /** SQL server error */ + private SQLServerError sqlServerError; + + 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; + } +} From 7d4c8018aa8d614243e43eebc1a2450392a7bcaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20Schwarz?= Date: Thu, 9 Nov 2023 15:35:25 +0100 Subject: [PATCH 03/12] And the changes made to existing files --- .../sqlserver/jdbc/ISQLServerConnection.java | 13 ++ .../sqlserver/jdbc/ISQLServerStatement.java | 17 +++ .../sqlserver/jdbc/SQLServerConnection.java | 71 +++++++++++ .../jdbc/SQLServerConnectionPoolProxy.java | 12 ++ .../sqlserver/jdbc/SQLServerError.java | 117 +++++++++++++++++- .../sqlserver/jdbc/SQLServerException.java | 27 ++++ .../sqlserver/jdbc/SQLServerInfoMessage.java | 110 +++++++++++++++- .../sqlserver/jdbc/SQLServerStatement.java | 92 ++++++++++++-- .../microsoft/sqlserver/jdbc/tdsparser.java | 51 +++++++- 9 files changed, 493 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java index 8025410c9..184a729eb 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java @@ -481,4 +481,17 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold, * @param accessTokenCallbackClass */ void setAccessTokenCallbackClass(String accessTokenCallbackClass); + + /** + * Get Currently installed message handler on the connection + * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)} + * @return + */ + public ISQLServerMessageHandler getServerMessageHandler(); + + /** + * Set message handler on the connection + * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)} + */ + public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java index fbc078f37..aea3f1cfa 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java @@ -58,4 +58,21 @@ public interface ISQLServerStatement extends java.sql.Statement, Serializable { * if any error occurs */ void setCancelQueryTimeout(int seconds) throws SQLServerException; + + /** + * Set a message handler for the Statement. + * + * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)} + * @return Previously installed message handler on this Statement + */ + public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler msgHandler); + + /** + * Get Currently installed message handler for the Statement. If no message handler is installed, + * we will use any installed message handler on the connection. + * + * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)} + * @return + */ + public ISQLServerMessageHandler getServerMessageHandler(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index acdf6723c..e59340d42 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -4727,6 +4727,20 @@ void addWarning(String warningString) { } } + // Any changes to SQLWarnings should be synchronized. + void addWarning(SQLWarning sqlWarning) { + warningSynchronization.lock(); + try { + if (null == sqlWarnings) { + sqlWarnings = sqlWarning; + } else { + sqlWarnings.setNextWarning(sqlWarning); + } + } finally { + warningSynchronization.unlock(); + } + } + @Override public void clearWarnings() throws SQLServerException { warningSynchronization.lock(); @@ -8250,6 +8264,63 @@ 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() + { +// // THE BELOW SHOULD BE REMOVED +// // IT'S JUST AT DUMMY IMPLEMENTATION DURING DEVELOPMENT +// if (this.messageHandler == null) +// { +// this.messageHandler = new ISQLServerMessageHandler() +// { +// @Override +// public ISQLServerMessage messageHandler(ISQLServerMessage databaseErrorOrWarning) +// { +// System.out.println("--------------------messageHandler received: " + databaseErrorOrWarning); +// if (databaseErrorOrWarning instanceof SQLServerError) { +// SQLServerError errorMsg = (SQLServerError)databaseErrorOrWarning; +// System.out.println("--------------------DOWNGRADE-------------------: " + databaseErrorOrWarning); +// databaseErrorOrWarning = errorMsg.toSQLServerInfoMessage(); +// System.out.println("--------------------DOWNGRADED--to--------------: " + databaseErrorOrWarning); +// } +// if (databaseErrorOrWarning instanceof SQLServerInfoMessage) { +// SQLServerInfoMessage infoMsg = (SQLServerInfoMessage)databaseErrorOrWarning; +// if (infoMsg.getSQLServerMessage().getErrorNumber() == 50000) +// { +// System.out.println("--------------------UPGRADE-------------------: " + databaseErrorOrWarning); +// databaseErrorOrWarning = infoMsg.toSQLServerError(16); +// System.out.println("--------------------UPGRADED--to--------------: " + databaseErrorOrWarning); +// } +// } +// return databaseErrorOrWarning; +// } +// }; +// } + return this.serverMessageHandler; + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java index 248957b3a..2fa895e52 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java @@ -691,4 +691,16 @@ public String getAccessTokenCallbackClass() { 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); + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerError.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerError.java index 3454e5aef..1b4c1e836 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerError.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerError.java @@ -6,12 +6,15 @@ package com.microsoft.sqlserver.jdbc; import java.io.Serializable; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; /** * SQLServerError represents a TDS error or message event. */ -public final class SQLServerError extends StreamPacket implements Serializable { +public final class SQLServerError extends StreamPacket implements Serializable, ISQLServerMessage { /** * List SQL Server transient errors drivers will retry on from @@ -221,6 +224,18 @@ public long getLineNumber() { super(TDS.TDS_ERR); } + SQLServerError(SQLServerError errorMsg) { + super(TDS.TDS_ERR); + this.errorNumber = errorMsg.errorNumber; + this.errorState = errorMsg.errorState; + this.errorSeverity = errorMsg.errorSeverity; + this.errorMessage = errorMsg.errorMessage; + this.serverName = errorMsg.serverName; + this.procName = errorMsg.procName; + this.lineNumber = errorMsg.lineNumber; + } + + @Override void setFromTDS(TDSReader tdsReader) throws SQLServerException { if (TDS.TDS_ERR != tdsReader.readUnsignedByte()) assert false; @@ -237,4 +252,104 @@ void setContentsFromTDS(TDSReader tdsReader) throws SQLServerException { procName = tdsReader.readUnicodeString(tdsReader.readUnsignedByte()); lineNumber = tdsReader.readUnsignedInt(); } + + + + /** + * Holds any "overflow messages", or messages that has been added after the first message. + *

+ * This is later on used when creating a SQLServerException.
+ * Where all entries in the errorChain will be added {@link java.sql.SQLException#setNextException(SQLException)} + */ + private List errorChain; + + void addError(SQLServerError sqlServerError) { + if (errorChain == null) { + errorChain = new ArrayList<>(); + } + errorChain.add(sqlServerError); + } + + List getErrorChain() { + return errorChain; + } + + @Override + public SQLServerError getSQLServerMessage() + { + return this; + } + + /** + * Downgrade a Error message into a Info message + *

+ * 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 6721c7b55..e6494067c 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,23 @@ 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 errorChain = sqlServerError.getErrorChain(); + if (errorChain != null) + { + for (SQLServerError srvError : errorChain) + { + String state2 = generateStateCode(con, srvError.getErrorNumber(), srvError.getErrorState()); + + SQLServerException chainException = new SQLServerException(obj, + SQLServerException.checkAndAppendClientConnId(srvError.getErrorMessage(), con), + state2, srvError, bStack); + chainException.setDriverErrorCode(DRIVER_ERROR_FROM_DATABASE); + + theException.setNextException(chainException); + } + } + // Close the connection if we get a severity 20 or higher error class (nClass is severity of error). if ((sqlServerError.getErrorSeverity() >= 20) && (null != con)) { con.notifyPooledConnection(theException); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java index 5b4cb571f..c1552d9d3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java @@ -5,11 +5,18 @@ package com.microsoft.sqlserver.jdbc; -final class StreamInfo extends StreamPacket { - final SQLServerError msg = new SQLServerError(); +import java.sql.SQLException; - StreamInfo() { +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; } void setFromTDS(TDSReader tdsReader) throws SQLServerException { @@ -17,4 +24,101 @@ void setFromTDS(TDSReader tdsReader) throws SQLServerException { 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 d9dfdfdb6..915c3e7d9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -1581,7 +1581,7 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { } } } - +//System.out.println(">>>>>>>>>>> SQLServerStatement::getNextResult.onDone(): doneToken.isError()=" + doneToken.isError()); // 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()) @@ -1633,8 +1633,17 @@ 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); +//System.out.println("------------------ SQLServerStatement:NextResult.onInfo() \n" +// + "infoMessage.msg.getErrorNumber = |" + infoMessage.msg.getErrorNumber() + "| \n" +// + "infoMessage.msg.getErrorSeverity = |" + infoMessage.msg.getErrorSeverity() + "| \n" +// + "infoMessage.msg.getErrorState = |" + infoMessage.msg.getErrorState() + "| \n" +// + "infoMessage.msg.getLineNumber = |" + infoMessage.msg.getLineNumber() + "| \n" +// + "infoMessage.msg.getProcedureName = |" + infoMessage.msg.getProcedureName() + "| \n" +// + "infoMessage.msg.getServerName = |" + infoMessage.msg.getServerName() + "| \n" +// + "infoMessage.msg.getErrorMessage = |" + infoMessage.msg.getErrorMessage() + "| \n" +// ); // Under some circumstances the server cannot produce the cursored result set // that we requested, but produces a client-side (default) result set instead. @@ -1648,13 +1657,53 @@ 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 = getServerMessageHandler(); +//System.out.println(" -- SQLServerStatement.getServerMessageHandler(): " + msgHandler); + if (msgHandler != null) + { + // Let the message handler decide if the error should be unchanged/down-graded or ignored + ISQLServerMessage msgType = msgHandler.messageHandler(infoMessage); + + // Ignored + if (msgType == null) { + return true; + } + + // Up-graded to a "SQLException" + if (msgType != null && msgType instanceof SQLServerError) { + SQLServerError databaseError = (SQLServerError)msgType; + + // Set/Add the error message to the "super" + setDatabaseError(databaseError); + + // Should we also abort current execution, if we have any? + // Or should we just continue as "normal" + // TODO: Abort in some way? + +//System.out.println(" <<<<<< SQLServerStatement.msgHandler.upgraded.info-2-error: databaseError=" + databaseError); + return true; + } + + // Still a "info message", just set infoMessage and the code in the below section will create the Warnings + if (msgType != null && msgType instanceof SQLServerInfoMessage) { + infoMessage = (SQLServerInfoMessage)msgType; + } + } + + // Create the SQLWarning and add them to the Warning chain +// SQLWarning warning = new SQLWarning( +// infoToken.msg.getErrorMessage(), SQLServerException.generateStateCode(connection, +// infoToken.msg.getErrorNumber(), infoToken.msg.getErrorState()), +// infoToken.msg.getErrorNumber()); + SQLWarning warning = new SQLServerWarning(infoMessage.msg); if (sqlWarnings == null) { sqlWarnings = new Vector<>(); @@ -1686,15 +1735,21 @@ boolean onInfo(TDSReader tdsReader) throws SQLServerException { // Figure out the next result. NextResult nextResult = new NextResult(); +//System.out.println(" ???????????????? nextResult="+nextResult); // Signal to not read all token other than TDS_MSG if reading only warnings TDSParser.parse(resultsReader(), nextResult, !clearFlag); +//System.out.println(" ???????????????? nextResult.getDatabaseError()="+nextResult.getDatabaseError()); +//if (null != nextResult.getDatabaseError()) +// (new Exception("DUMMY Exception. to get stacktrace")).printStackTrace(); // Check for errors first. if (null != nextResult.getDatabaseError()) { SQLServerException.makeFromDatabaseError(connection, null, nextResult.getDatabaseError().getErrorMessage(), nextResult.getDatabaseError(), false); } +//if (null != nextResult.getDatabaseError()) +// System.out.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: We should not get here if we had ERRORS"); // If we didn't clear current ResultSet, we wanted to read only warnings. Return back from here. if (!clearFlag) @@ -2530,6 +2585,27 @@ SQLServerColumnEncryptionKeyStoreProvider getColumnEncryptionKeyStoreProvider( lock.unlock(); } } + + /** Holds a local message handler on the statement level, if null we will try to get one from the connection */ + private ISQLServerMessageHandler serverMessageHandler; + + @Override + public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler msgHandler) + { + ISQLServerMessageHandler installedMessageHandler = this.serverMessageHandler; + this.serverMessageHandler = msgHandler; + return installedMessageHandler; + } + + @Override + public ISQLServerMessageHandler getServerMessageHandler() + { + ISQLServerMessageHandler msgHandler = this.serverMessageHandler; + if (msgHandler == null) { + msgHandler = connection.getServerMessageHandler(); + } + return msgHandler; + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java index a26133f69..2e7a63969 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java @@ -196,6 +196,14 @@ class TDSTokenHandler { final SQLServerError getDatabaseError() { return databaseError; } + public void setDatabaseError(SQLServerError databaseError) + { + if (this.databaseError == null) { + this.databaseError = databaseError; + } else { + this.databaseError.addError(databaseError); + } + } TDSTokenHandler(String logContext) { this.logContext = logContext; @@ -247,13 +255,46 @@ 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); + +//System.out.println("tdsParser.onError(): From TDS\n" +// + "tmpDatabaseError.getErrorNumber = |" + tmpDatabaseError.getErrorNumber() + "| \n" +// + "tmpDatabaseError.getErrorSeverity = |" + tmpDatabaseError.getErrorSeverity() + "| \n" +// + "tmpDatabaseError.getErrorState = |" + tmpDatabaseError.getErrorState() + "| \n" +// + "tmpDatabaseError.getLineNumber = |" + tmpDatabaseError.getLineNumber() + "| \n" +// + "tmpDatabaseError.getProcedureName = |" + tmpDatabaseError.getProcedureName() + "| \n" +// + "tmpDatabaseError.getServerName = |" + tmpDatabaseError.getServerName() + "| \n" +// + "tmpDatabaseError.getErrorMessage = |" + tmpDatabaseError.getErrorMessage() + "| \n" +// ); +// + ISQLServerMessageHandler msgHandler = tdsReader.getConnection().getServerMessageHandler(); + if (msgHandler != null) + { + // Let the message handler decide if the error should be unchanged/down-graded or ignored + ISQLServerMessage msgType = msgHandler.messageHandler(tmpDatabaseError); + + // Ignored + if (msgType == null) { + return true; + } + + // Down-graded to a SQLWarning + if (msgType != null && msgType instanceof SQLServerInfoMessage) { + SQLServerInfoMessage infoMessage = (SQLServerInfoMessage)msgType; + + // Add the warning to the Connection objects warnings chain + SQLServerWarning warning = new SQLServerWarning(infoMessage.getSQLServerMessage()); + SQLServerConnection conn = tdsReader.getConnection(); + conn.addWarning(warning); + + return true; + } } + // set/add the database error + setDatabaseError(tmpDatabaseError); + return true; } From f4c996bdaa8c2899e58e915025f65e9053b30fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20Schwarz?= Date: Sat, 11 Nov 2023 18:57:53 +0100 Subject: [PATCH 04/12] Removed get/set ServerMessageHandler from ISQLServerStatement and SQLServerStatement Simplified some code and formatted it to meet requirements Added test cases --- .../sqlserver/jdbc/ISQLServerMessage.java | 33 +- .../jdbc/ISQLServerMessageHandler.java | 34 +- .../sqlserver/jdbc/ISQLServerStatement.java | 16 - .../sqlserver/jdbc/SQLServerStatement.java | 64 +-- .../sqlserver/jdbc/SQLServerWarning.java | 18 +- .../microsoft/sqlserver/jdbc/tdsparser.java | 32 +- .../jdbc/exception/ChainedExceptionTest.java | 139 +++++ .../jdbc/msghandler/MsgHandlerTest.java | 500 ++++++++++++++++++ 8 files changed, 710 insertions(+), 126 deletions(-) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/exception/ChainedExceptionTest.java create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java index f422f9fef..055e812b2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessage.java @@ -9,20 +9,12 @@ public interface ISQLServerMessage { /** - * Returns ISQLServerMessage containing detailed info about SQL Server Message as received from SQL Server. + * Returns SQLServerError containing detailed info about SQL Server Message as received from SQL Server. * - * @return ISQLServerMessage + * @return SQLServerError */ -// public ISQLServerMessage getSQLServerMessage(); public SQLServerError getSQLServerMessage(); -// /** -// * Set SQLServerError which holds detailed info about SQL Server Message. -// * -// * @return SQLServerError -// */ -// public void setSQLServerMessage(SQLServerError msg); - /** * Returns error message as received from SQL Server * @@ -74,9 +66,28 @@ public interface ISQLServerMessage public long getLineNumber(); /** - * Creates a SQLServerException or SQLServerWarning from this SQLServerMessage + * Creates a SQLServerException or SQLServerWarning from this SQLServerMessage
* @return + *

    + *
  • SQLServerException if it's a SQLServerError object
  • + *
  • SQLServerWarning if it's a SQLServerInfoMessage object
  • + *
*/ 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; + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java index 17c304019..28a4412d4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java @@ -48,21 +48,29 @@ public interface ISQLServerMessageHandler * * Example code: *
-     * public StreamPacket messageHandler(StreamPacket databaseErrorOrWarning)
-     * {
-     * FIXME: CHANGE THE BELOW CODE TO SOMETHING BETTER
-     * TODO: SQLServerError and needs SQLServerInfoMessage needs to extend a new class "SQLServerMessage" or similar... which makes it easier ...
-     *     System.out.println("--------------------messageHandler received: " + databaseErrorOrWarning);
-     *     if (databaseErrorOrWarning instanceof SQLServerError) {
-     *         System.out.println("--------------------DOWNGRADE-------------------: " + databaseErrorOrWarning);
-     *         databaseErrorOrWarning = databaseErrorOrWarning = errorMsg.toSQLServerInfoMessage();
-     *     }
-     *     return databaseErrorOrWarning;
-     * }
+     *  public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning)
+     *  {
+     *      ISQLServerMessage retObj = srvErrorOrWarning;
+     *
+     *      if (srvErrorOrWarning.isErrorMessage()) {
+     *
+     *          // Downgrade: 2601 -- Cannot insert duplicate key row...
+     *          if (2601 == srvErrorOrWarning.getErrorNumber()) {
+     *              retObj = srvErrorOrWarning.getSQLServerMessage().toSQLServerInfoMessage();
+     *          }
+     *
+     *          // Discard: 3701 -- Cannot drop the table ...
+     *          if (3701 == srvErrorOrWarning.getErrorNumber()) {
+     *              retObj = null;
+     *          }
+     *      }
+     *
+     *      return retObj;
+     *  }
     
      * 
* - * @param databaseErrorOrWarning + * @param srvErrorOrWarning * @return *
    *
  • unchanged same object as passed in.
    @@ -83,5 +91,5 @@ public interface ISQLServerMessageHandler *
  • *
*/ - ISQLServerMessage messageHandler(ISQLServerMessage databaseErrorOrWarning); + ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java index aea3f1cfa..b0b3b4e20 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java @@ -59,20 +59,4 @@ public interface ISQLServerStatement extends java.sql.Statement, Serializable { */ void setCancelQueryTimeout(int seconds) throws SQLServerException; - /** - * Set a message handler for the Statement. - * - * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)} - * @return Previously installed message handler on this Statement - */ - public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler msgHandler); - - /** - * Get Currently installed message handler for the Statement. If no message handler is installed, - * we will use any installed message handler on the connection. - * - * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)} - * @return - */ - public ISQLServerMessageHandler getServerMessageHandler(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 915c3e7d9..143f04b62 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -1581,7 +1581,6 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { } } } -//System.out.println(">>>>>>>>>>> SQLServerStatement::getNextResult.onDone(): doneToken.isError()=" + doneToken.isError()); // 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()) @@ -1635,15 +1634,6 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException { boolean onInfo(TDSReader tdsReader) throws SQLServerException { SQLServerInfoMessage infoMessage = new SQLServerInfoMessage(); infoMessage.setFromTDS(tdsReader); -//System.out.println("------------------ SQLServerStatement:NextResult.onInfo() \n" -// + "infoMessage.msg.getErrorNumber = |" + infoMessage.msg.getErrorNumber() + "| \n" -// + "infoMessage.msg.getErrorSeverity = |" + infoMessage.msg.getErrorSeverity() + "| \n" -// + "infoMessage.msg.getErrorState = |" + infoMessage.msg.getErrorState() + "| \n" -// + "infoMessage.msg.getLineNumber = |" + infoMessage.msg.getLineNumber() + "| \n" -// + "infoMessage.msg.getProcedureName = |" + infoMessage.msg.getProcedureName() + "| \n" -// + "infoMessage.msg.getServerName = |" + infoMessage.msg.getServerName() + "| \n" -// + "infoMessage.msg.getErrorMessage = |" + infoMessage.msg.getErrorMessage() + "| \n" -// ); // Under some circumstances the server cannot produce the cursored result set // that we requested, but produces a client-side (default) result set instead. @@ -1665,10 +1655,8 @@ boolean onInfo(TDSReader tdsReader) throws SQLServerException { // - discard // - upgrade to Error // - or simply pass on - ISQLServerMessageHandler msgHandler = getServerMessageHandler(); -//System.out.println(" -- SQLServerStatement.getServerMessageHandler(): " + msgHandler); - if (msgHandler != null) - { + ISQLServerMessageHandler msgHandler = ((SQLServerConnection)getConnection()).getServerMessageHandler(); + if (msgHandler != null) { // Let the message handler decide if the error should be unchanged/down-graded or ignored ISQLServerMessage msgType = msgHandler.messageHandler(infoMessage); @@ -1679,30 +1667,21 @@ boolean onInfo(TDSReader tdsReader) throws SQLServerException { // Up-graded to a "SQLException" if (msgType != null && msgType instanceof SQLServerError) { - SQLServerError databaseError = (SQLServerError)msgType; - - // Set/Add the error message to the "super" - setDatabaseError(databaseError); - - // Should we also abort current execution, if we have any? - // Or should we just continue as "normal" - // TODO: Abort in some way? - -//System.out.println(" <<<<<< SQLServerStatement.msgHandler.upgraded.info-2-error: databaseError=" + databaseError); + SQLServerError databaseError = (SQLServerError)msgType; + + // Set/Add the error message to the "super" + setDatabaseError(databaseError); + return true; } // Still a "info message", just set infoMessage and the code in the below section will create the Warnings if (msgType != null && msgType instanceof SQLServerInfoMessage) { - infoMessage = (SQLServerInfoMessage)msgType; + infoMessage = (SQLServerInfoMessage)msgType; } } // Create the SQLWarning and add them to the Warning chain -// SQLWarning warning = new SQLWarning( -// infoToken.msg.getErrorMessage(), SQLServerException.generateStateCode(connection, -// infoToken.msg.getErrorNumber(), infoToken.msg.getErrorState()), -// infoToken.msg.getErrorNumber()); SQLWarning warning = new SQLServerWarning(infoMessage.msg); if (sqlWarnings == null) { @@ -1735,21 +1714,15 @@ boolean onInfo(TDSReader tdsReader) throws SQLServerException { // Figure out the next result. NextResult nextResult = new NextResult(); -//System.out.println(" ???????????????? nextResult="+nextResult); // Signal to not read all token other than TDS_MSG if reading only warnings TDSParser.parse(resultsReader(), nextResult, !clearFlag); -//System.out.println(" ???????????????? nextResult.getDatabaseError()="+nextResult.getDatabaseError()); -//if (null != nextResult.getDatabaseError()) -// (new Exception("DUMMY Exception. to get stacktrace")).printStackTrace(); // Check for errors first. if (null != nextResult.getDatabaseError()) { SQLServerException.makeFromDatabaseError(connection, null, nextResult.getDatabaseError().getErrorMessage(), nextResult.getDatabaseError(), false); } -//if (null != nextResult.getDatabaseError()) -// System.out.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: We should not get here if we had ERRORS"); // If we didn't clear current ResultSet, we wanted to read only warnings. Return back from here. if (!clearFlag) @@ -2585,27 +2558,6 @@ SQLServerColumnEncryptionKeyStoreProvider getColumnEncryptionKeyStoreProvider( lock.unlock(); } } - - /** Holds a local message handler on the statement level, if null we will try to get one from the connection */ - private ISQLServerMessageHandler serverMessageHandler; - - @Override - public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler msgHandler) - { - ISQLServerMessageHandler installedMessageHandler = this.serverMessageHandler; - this.serverMessageHandler = msgHandler; - return installedMessageHandler; - } - - @Override - public ISQLServerMessageHandler getServerMessageHandler() - { - ISQLServerMessageHandler msgHandler = this.serverMessageHandler; - if (msgHandler == null) { - msgHandler = connection.getServerMessageHandler(); - } - return msgHandler; - } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java index 81a99d94a..34e6285c3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java @@ -14,15 +14,17 @@ public class SQLServerWarning /** SQL server error */ private SQLServerError sqlServerError; - public SQLServerWarning(SQLServerError sqlServerError) - { - super(sqlServerError.getErrorMessage(), - SQLServerException.generateStateCode(null, sqlServerError.getErrorNumber(), sqlServerError.getErrorState()), - sqlServerError.getErrorNumber(), - null); + /* + * 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; - } + this.sqlServerError = sqlServerError; + } /** * Returns SQLServerError object containing detailed info about exception as received from SQL Server. This API diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java index 2e7a63969..3b8eddec3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java @@ -196,8 +196,8 @@ class TDSTokenHandler { final SQLServerError getDatabaseError() { return databaseError; } - public void setDatabaseError(SQLServerError databaseError) - { + + public void setDatabaseError(SQLServerError databaseError) { if (this.databaseError == null) { this.databaseError = databaseError; } else { @@ -255,38 +255,26 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException { } boolean onError(TDSReader tdsReader) throws SQLServerException { - SQLServerError tmpDatabaseError = new SQLServerError(); + SQLServerError tmpDatabaseError = new SQLServerError(); tmpDatabaseError.setFromTDS(tdsReader); -//System.out.println("tdsParser.onError(): From TDS\n" -// + "tmpDatabaseError.getErrorNumber = |" + tmpDatabaseError.getErrorNumber() + "| \n" -// + "tmpDatabaseError.getErrorSeverity = |" + tmpDatabaseError.getErrorSeverity() + "| \n" -// + "tmpDatabaseError.getErrorState = |" + tmpDatabaseError.getErrorState() + "| \n" -// + "tmpDatabaseError.getLineNumber = |" + tmpDatabaseError.getLineNumber() + "| \n" -// + "tmpDatabaseError.getProcedureName = |" + tmpDatabaseError.getProcedureName() + "| \n" -// + "tmpDatabaseError.getServerName = |" + tmpDatabaseError.getServerName() + "| \n" -// + "tmpDatabaseError.getErrorMessage = |" + tmpDatabaseError.getErrorMessage() + "| \n" -// ); -// ISQLServerMessageHandler msgHandler = tdsReader.getConnection().getServerMessageHandler(); - if (msgHandler != null) - { - // Let the message handler decide if the error should be unchanged/down-graded or ignored + if (msgHandler != null) { + // Let the message handler decide if the error should be unchanged/down-graded or ignored ISQLServerMessage msgType = msgHandler.messageHandler(tmpDatabaseError); // Ignored if (msgType == null) { - return true; + return true; } // Down-graded to a SQLWarning if (msgType != null && msgType instanceof SQLServerInfoMessage) { - SQLServerInfoMessage infoMessage = (SQLServerInfoMessage)msgType; + SQLServerInfoMessage infoMessage = (SQLServerInfoMessage)msgType; - // Add the warning to the Connection objects warnings chain - SQLServerWarning warning = new SQLServerWarning(infoMessage.getSQLServerMessage()); - SQLServerConnection conn = tdsReader.getConnection(); - conn.addWarning(warning); + // Add the warning to the Connection objects warnings chain + SQLServerWarning warning = new SQLServerWarning(infoMessage.getSQLServerMessage()); + tdsReader.getConnection().addWarning(warning); return true; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/exception/ChainedExceptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/exception/ChainedExceptionTest.java new file mode 100644 index 000000000..030f83a5c --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/exception/ChainedExceptionTest.java @@ -0,0 +1,139 @@ +/* + * 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.exception; + +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.SQLServerException; +import com.microsoft.sqlserver.jdbc.TestResource; +import com.microsoft.sqlserver.testframework.AbstractTest; + + +@RunWith(JUnitPlatform.class) +public class ChainedExceptionTest extends AbstractTest { + + @BeforeAll + public static void setupTests() throws Exception { + setConnection(); + } + + /** + * Small helper to read/loop all TDS packages from the "wire" + *

+ * hence stmnt.execute() and stmnt.executeUpdate() do not fully read the TDS stream, hence no Exceptions in some cases... + * @param conn + * @param sql + * @return + * @throws SQLException + */ + private static int execSql(Connection conn, String sql) + throws SQLException { + + try (Statement stmnt = conn.createStatement()) { + + int rowsRead = 0; + int rowsAffected = 0; + boolean hasRs = stmnt.execute(sql); + + do { + if(hasRs) { + ResultSet rs = stmnt.getResultSet(); + + while(rs.next()) { + // Just read the row... don't care about data + rowsRead++; + } + + // Check for warnings + // If warnings found, add them to the LIST + for (SQLWarning sqlw = rs.getWarnings(); sqlw != null; sqlw = sqlw.getNextWarning()) { + fail("No SQLWarning should be expected here: at ResultSet level."); + } + + rs.close(); + } + else { + rowsAffected = stmnt.getUpdateCount(); + } + + // Check if we have more resultsets + hasRs = stmnt.getMoreResults(); + } + while (hasRs || rowsAffected != -1); + + // Check for warnings + for (SQLWarning sqlw = stmnt.getWarnings(); sqlw != null; sqlw = sqlw.getNextWarning()) { + fail("No SQLWarning should be expected here: at Statement level."); + } + for (SQLWarning sqlw = conn.getWarnings(); sqlw != null; sqlw = sqlw.getNextWarning()) { + fail("No SQLWarning should be expected here: at Connection level."); + } + + return rowsRead + rowsAffected; + } + } + + + + @Test + public void testTwoExceptions() throws Exception { + // The below should yield the following Server Messages: + // 1 : Msg 5074, Level 16, State 1: The object 'DF__#chained_exc__c1__AB25243A' is dependent on column 'c1'. + // 1 : Msg 4922, Level 16, State 9: ALTER TABLE ALTER COLUMN c1 failed because one or more objects access this column. + String sql = "CREATE TABLE #chained_exception_test_x1(c1 INT DEFAULT(0)); \n" + + "ALTER TABLE #chained_exception_test_x1 ALTER COLUMN c1 VARCHAR(10); \n" + + "DROP TABLE IF EXISTS #chained_exception_test_x1; \n"; + try (Connection conn = getConnection()) { + + // NOTE: stmnt.execute() or executeUpdate() wont work in here since it do not read through the full TDS stream +// execSql(con, sql); + + try (Statement stmnt = conn.createStatement()) { + stmnt.executeUpdate("CREATE TABLE #chained_exception_test_x1(c1 INT DEFAULT(0))"); + stmnt.executeUpdate("ALTER TABLE #chained_exception_test_x1 ALTER COLUMN c1 VARCHAR(10)"); + stmnt.executeUpdate("DROP TABLE IF EXISTS #chained_exception_test_x1"); + } + + fail(TestResource.getResource("R_expectedFailPassed")); + + } catch (SQLException ex) { + + // Check the SQLException and the chain + int exCount = 0; + int firstMsgNum = ex.getErrorCode(); + int lastMsgNum = -1; + + while (ex != null) { + exCount++; + + System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>> ex[" + exCount + "].getErrorCode()=" + ex.getErrorCode() + ", Severity=" + ((SQLServerException)ex).getSQLServerError().getErrorSeverity()+ ", Text=|" + ex.getMessage() + "|."); + lastMsgNum = ex.getErrorCode(); + + ex = ex.getNextException(); + } + + // Exception Count should be: 2 + assertEquals(2, exCount, "Number of SQLExceptions in the SQLException chain"); + + // Check first Msg: 5074 + assertEquals(5074, firstMsgNum, "First SQL Server Message"); + + // Check last Msg: 4922 + assertEquals(4922, lastMsgNum, "Last SQL Server Message"); + } + } +} diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java new file mode 100644 index 000000000..9937f04f8 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java @@ -0,0 +1,500 @@ +/* + * 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.msghandler; + +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.sql.Types; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.ISQLServerMessage; +import com.microsoft.sqlserver.jdbc.ISQLServerMessageHandler; +import com.microsoft.sqlserver.jdbc.SQLServerConnection; +import com.microsoft.sqlserver.jdbc.TestResource; +import com.microsoft.sqlserver.testframework.AbstractTest; + + +@RunWith(JUnitPlatform.class) +public class MsgHandlerTest extends AbstractTest { + + @BeforeAll + public static void setupTests() throws Exception { + setConnection(); + } + + + /** + * Helper method to count number of SQLWarnings in a chain + * @param str - Debug String, so we can evaluate from where we called it... + * @param sqlw - The SQL Warning chain. (can be null) + * @return A count of warnings + */ + private static int getWarningCount(String str, SQLWarning sqlw) + { + int count = 0; + while(sqlw != null) { + count++; + //System.out.println("DEBUG: getWarningCount(): [" + str + "] SQLWarning: Error=" + sqlw.getErrorCode() + ", Severity=" + ((SQLServerWarning)sqlw).getSQLServerError().getErrorSeverity() + ", Text=|" + sqlw.getMessage() + "|."); + sqlw = sqlw.getNextWarning(); + } + return count; + } + + + /** + * Test message handler with normal Statement + *

    + *
  • Insert duplicate row -- Mapped to Info Message
  • + *
  • Drop table that do not exist -- Mapped to ignore
  • + *
+ */ + @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 + *
    + *
  • Insert duplicate row -- Mapped to Info Message
  • + *
  • Drop table that do not exist -- Mapped to ignore
  • + *
+ */ + @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); + + // NOTE: stmnt.execute() or executeUpdate() wont work in here since it do not read through the full TDS stream +// execSql(con, sql); + + 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 + *
    + *
  • Insert duplicate row -- Mapped to Info Message
  • + *
  • Drop table that do not exist -- Mapped to ignore
  • + *
+ */ + @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 + *

+ * 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 feedbackMsgTs = new LinkedHashMap<>(); + + @Override + public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning) + { + numOfCalls++; + + if (50_000 == srvErrorOrWarning.getErrorNumber()) { + //System.out.println("DEBUG: testMsgHandlerWithProcedureFeedback.messageHandler(): FEEDBACK: " + srvErrorOrWarning.getErrorMessage()); + // Remember when the message was received + feedbackMsgTs.put(srvErrorOrWarning.getErrorMessage(), System.currentTimeMillis()); + } + + return srvErrorOrWarning; + } + } + TestMsgHandler testMsgHandler = new TestMsgHandler(); + + // Create a massage handler + conn.setServerMessageHandler(testMsgHandler); + + int doSqlLoopCount = 4; + // SQL to create procedure + String sqlCreateProc = "" + + "CREATE PROCEDURE #msghandler_feeback_proc \n" + + "AS \n" + + "BEGIN \n" + + " DECLARE @loop_cnt INT = " + doSqlLoopCount + " \n" + + " DECLARE @feedback VARCHAR(255) \n" + + " \n" + + " WHILE (@loop_cnt > 0) \n" + + " BEGIN \n" + + " WAITFOR DELAY '00:00:01' \n" + + " \n" + + " SET @feedback = 'In proc, still looping... waiting for loop_count to reach 0. loop_count is now at: ' + convert(varchar(10), @loop_cnt) \n" + + " RAISERROR(@feedback, 0, 1) WITH NOWAIT \n" + + " \n" + + " SET @loop_cnt = @loop_cnt - 1 \n" + + " END \n" + + " \n" + + " RETURN @loop_cnt \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_feeback_proc }")) { + cstmnt.registerOutParameter(1, Types.INTEGER); + cstmnt.execute(); + int procReturnCode = cstmnt.getInt(1); + + assertEquals(0, procReturnCode, "Unexpected ReturnCode from the temp procedure."); + + assertEquals(0, getWarningCount("conn" , conn .getWarnings()), "Unexpected Number Of SQLWarnings from 'exec proc', at Connection."); + assertEquals(doSqlLoopCount, getWarningCount("cstmnt", cstmnt.getWarnings()), "Unexpected Number Of SQLWarnings from 'exec proc', at CallableStatement."); + } + + // numOfCalls to the message handler should be: # + assertEquals(doSqlLoopCount, testMsgHandler.numOfCalls, "Number of message calls to the message handler."); + + // Loop all received messages and check that they are within a second (+-200ms) + long prevTime = 0; + for (Entry entry : testMsgHandler.feedbackMsgTs.entrySet()) { + if (prevTime == 0) { + prevTime = entry.getValue(); + continue; + } + + long msDiff = entry.getValue() - prevTime; + if (msDiff < 800 || msDiff > 1200) { + fail("Received Messages is to far apart. They should be approx 1000 ms. msDiff=" + msDiff + " Message=|" + entry.getKey() + "|."); + } + + prevTime = entry.getValue(); + } + + } catch (SQLException ex) { + fail(TestResource.getResource("R_unexpectedErrorMessage")); + } + } + +} From e98188b4e9fdd08f1e6263922e25259021913e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20Schwarz?= Date: Sun, 12 Nov 2023 17:54:55 +0100 Subject: [PATCH 05/12] smaller simplifications --- .../sqlserver/jdbc/SQLServerConnection.java | 5 +++++ .../sqlserver/jdbc/SQLServerStatement.java | 20 +++++++++---------- .../microsoft/sqlserver/jdbc/tdsparser.java | 17 ++++++---------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index e59340d42..b37ee00cc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -4741,6 +4741,11 @@ void addWarning(SQLWarning sqlWarning) { } } + // Any changes to SQLWarnings should be synchronized. + void addWarning(ISQLServerMessage sqlServerMessage) { + addWarning(new SQLServerWarning(sqlServerMessage.getSQLServerMessage())); + } + @Override public void clearWarnings() throws SQLServerException { warningSynchronization.lock(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 143f04b62..4d957c0ba 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -1657,27 +1657,25 @@ boolean onInfo(TDSReader tdsReader) throws SQLServerException { // - or simply pass on ISQLServerMessageHandler msgHandler = ((SQLServerConnection)getConnection()).getServerMessageHandler(); if (msgHandler != null) { - // Let the message handler decide if the error should be unchanged/down-graded or ignored - ISQLServerMessage msgType = msgHandler.messageHandler(infoMessage); + + // Let the message handler decide if the error should be unchanged, up/down-graded or ignored + ISQLServerMessage srvMessage = msgHandler.messageHandler(infoMessage); // Ignored - if (msgType == null) { + if (srvMessage == null) { return true; } - // Up-graded to a "SQLException" - if (msgType != null && msgType instanceof SQLServerError) { - SQLServerError databaseError = (SQLServerError)msgType; - + // The message handler changed it to an "Error Message" + if (srvMessage.isErrorMessage()) { // Set/Add the error message to the "super" - setDatabaseError(databaseError); - + addDatabaseError( (SQLServerError)srvMessage ); return true; } // Still a "info message", just set infoMessage and the code in the below section will create the Warnings - if (msgType != null && msgType instanceof SQLServerInfoMessage) { - infoMessage = (SQLServerInfoMessage)msgType; + if (srvMessage.isInfoMessage()) { + infoMessage = (SQLServerInfoMessage)srvMessage; } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java index 3b8eddec3..dbbef7797 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java @@ -197,7 +197,7 @@ final SQLServerError getDatabaseError() { return databaseError; } - public void setDatabaseError(SQLServerError databaseError) { + public void addDatabaseError(SQLServerError databaseError) { if (this.databaseError == null) { this.databaseError = databaseError; } else { @@ -261,27 +261,22 @@ boolean onError(TDSReader tdsReader) throws SQLServerException { ISQLServerMessageHandler msgHandler = tdsReader.getConnection().getServerMessageHandler(); if (msgHandler != null) { // Let the message handler decide if the error should be unchanged/down-graded or ignored - ISQLServerMessage msgType = msgHandler.messageHandler(tmpDatabaseError); + ISQLServerMessage srvMessage = msgHandler.messageHandler(tmpDatabaseError); // Ignored - if (msgType == null) { + if (srvMessage == null) { return true; } // Down-graded to a SQLWarning - if (msgType != null && msgType instanceof SQLServerInfoMessage) { - SQLServerInfoMessage infoMessage = (SQLServerInfoMessage)msgType; - - // Add the warning to the Connection objects warnings chain - SQLServerWarning warning = new SQLServerWarning(infoMessage.getSQLServerMessage()); - tdsReader.getConnection().addWarning(warning); - + if (srvMessage.isInfoMessage()) { + tdsReader.getConnection().addWarning(srvMessage); return true; } } // set/add the database error - setDatabaseError(tmpDatabaseError); + addDatabaseError(tmpDatabaseError); return true; } From e81caf3dc7429fef5f0df765b4dac171f12915ef Mon Sep 17 00:00:00 2001 From: Goran Schwarz Date: Sun, 12 Nov 2023 22:16:10 +0100 Subject: [PATCH 06/12] fixed Copyright in one file! Also fixed smaller code format mishaps --- .../jdbc/ISQLServerMessageHandler.java | 27 ++++------------- .../sqlserver/jdbc/SQLServerConnection.java | 29 ------------------- .../sqlserver/jdbc/SQLServerException.java | 10 +++---- .../microsoft/sqlserver/jdbc/tdsparser.java | 2 +- 4 files changed, 10 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java index 28a4412d4..1e7511f06 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java @@ -1,24 +1,7 @@ -/******************************************************************************* - * Copyright (C) 2010-2019 Goran Schwarz - * - * This file is part of DbxTune - * DbxTune is a family of sub-products *Tune, hence the Dbx - * Here are some of the tools: AseTune, IqTune, RsTune, RaxTune, HanaTune, - * SqlServerTune, PostgresTune, MySqlTune, MariaDbTune, Db2Tune, ... - * - * DbxTune is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * DbxTune is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with DbxTune. If not, see . - ******************************************************************************/ +/* + * 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; public interface ISQLServerMessageHandler @@ -30,7 +13,7 @@ public interface ISQLServerMessageHandler *

  • "message feedback"
    * Display Server messages from a long running SQL Statement
    * Like RAISERROR ('Progress message...', 0, 1) WITH NOWAIT
    - * Or Status message from a running backup...
    + * Or Status messages from a running backup...
    *
  • *
  • "Universal" error logging
    * Your error-message handler can contain the logic for handling all error logging. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index b37ee00cc..ddbf1f291 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -8295,35 +8295,6 @@ public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler @Override public ISQLServerMessageHandler getServerMessageHandler() { -// // THE BELOW SHOULD BE REMOVED -// // IT'S JUST AT DUMMY IMPLEMENTATION DURING DEVELOPMENT -// if (this.messageHandler == null) -// { -// this.messageHandler = new ISQLServerMessageHandler() -// { -// @Override -// public ISQLServerMessage messageHandler(ISQLServerMessage databaseErrorOrWarning) -// { -// System.out.println("--------------------messageHandler received: " + databaseErrorOrWarning); -// if (databaseErrorOrWarning instanceof SQLServerError) { -// SQLServerError errorMsg = (SQLServerError)databaseErrorOrWarning; -// System.out.println("--------------------DOWNGRADE-------------------: " + databaseErrorOrWarning); -// databaseErrorOrWarning = errorMsg.toSQLServerInfoMessage(); -// System.out.println("--------------------DOWNGRADED--to--------------: " + databaseErrorOrWarning); -// } -// if (databaseErrorOrWarning instanceof SQLServerInfoMessage) { -// SQLServerInfoMessage infoMsg = (SQLServerInfoMessage)databaseErrorOrWarning; -// if (infoMsg.getSQLServerMessage().getErrorNumber() == 50000) -// { -// System.out.println("--------------------UPGRADE-------------------: " + databaseErrorOrWarning); -// databaseErrorOrWarning = infoMsg.toSQLServerError(16); -// System.out.println("--------------------UPGRADED--to--------------: " + databaseErrorOrWarning); -// } -// } -// return databaseErrorOrWarning; -// } -// }; -// } return this.serverMessageHandler; } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java index e6494067c..19509d581 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java @@ -273,10 +273,8 @@ static void makeFromDatabaseError(SQLServerConnection con, Object obj, String er // Add any extra messages to the SQLException error chain List errorChain = sqlServerError.getErrorChain(); - if (errorChain != null) - { - for (SQLServerError srvError : errorChain) - { + if (errorChain != null) { + for (SQLServerError srvError : errorChain) { String state2 = generateStateCode(con, srvError.getErrorNumber(), srvError.getErrorState()); SQLServerException chainException = new SQLServerException(obj, @@ -285,9 +283,9 @@ static void makeFromDatabaseError(SQLServerConnection con, Object obj, String er chainException.setDriverErrorCode(DRIVER_ERROR_FROM_DATABASE); theException.setNextException(chainException); - } + } } - + // Close the connection if we get a severity 20 or higher error class (nClass is severity of error). if ((sqlServerError.getErrorSeverity() >= 20) && (null != con)) { con.notifyPooledConnection(theException); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java index dbbef7797..1a79a43e3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java @@ -201,7 +201,7 @@ public void addDatabaseError(SQLServerError databaseError) { if (this.databaseError == null) { this.databaseError = databaseError; } else { - this.databaseError.addError(databaseError); + this.databaseError.addError(databaseError); } } From 7a0edc3de6e83285b693240a3cdabfe0d0d8effe Mon Sep 17 00:00:00 2001 From: Goran Schwarz Date: Sun, 12 Nov 2023 22:29:37 +0100 Subject: [PATCH 07/12] Fixed code formatting and removed some stuff from the test cases --- .../jdbc/exception/ChainedExceptionTest.java | 94 ++----------- .../jdbc/msghandler/MsgHandlerTest.java | 125 +++++++++--------- 2 files changed, 74 insertions(+), 145 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/exception/ChainedExceptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/exception/ChainedExceptionTest.java index 030f83a5c..9e6b1d4b7 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/exception/ChainedExceptionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/exception/ChainedExceptionTest.java @@ -8,9 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.sql.Connection; -import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLWarning; import java.sql.Statement; import org.junit.jupiter.api.BeforeAll; @@ -18,7 +16,6 @@ import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; -import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.TestResource; import com.microsoft.sqlserver.testframework.AbstractTest; @@ -31,82 +28,18 @@ public static void setupTests() throws Exception { setConnection(); } - /** - * Small helper to read/loop all TDS packages from the "wire" - *

    - * hence stmnt.execute() and stmnt.executeUpdate() do not fully read the TDS stream, hence no Exceptions in some cases... - * @param conn - * @param sql - * @return - * @throws SQLException - */ - private static int execSql(Connection conn, String sql) - throws SQLException { - - try (Statement stmnt = conn.createStatement()) { - - int rowsRead = 0; - int rowsAffected = 0; - boolean hasRs = stmnt.execute(sql); - - do { - if(hasRs) { - ResultSet rs = stmnt.getResultSet(); - - while(rs.next()) { - // Just read the row... don't care about data - rowsRead++; - } - - // Check for warnings - // If warnings found, add them to the LIST - for (SQLWarning sqlw = rs.getWarnings(); sqlw != null; sqlw = sqlw.getNextWarning()) { - fail("No SQLWarning should be expected here: at ResultSet level."); - } - - rs.close(); - } - else { - rowsAffected = stmnt.getUpdateCount(); - } - - // Check if we have more resultsets - hasRs = stmnt.getMoreResults(); - } - while (hasRs || rowsAffected != -1); - - // Check for warnings - for (SQLWarning sqlw = stmnt.getWarnings(); sqlw != null; sqlw = sqlw.getNextWarning()) { - fail("No SQLWarning should be expected here: at Statement level."); - } - for (SQLWarning sqlw = conn.getWarnings(); sqlw != null; sqlw = sqlw.getNextWarning()) { - fail("No SQLWarning should be expected here: at Connection level."); - } - - return rowsRead + rowsAffected; - } - } - - - @Test public void testTwoExceptions() throws Exception { - // The below should yield the following Server Messages: - // 1 : Msg 5074, Level 16, State 1: The object 'DF__#chained_exc__c1__AB25243A' is dependent on column 'c1'. - // 1 : Msg 4922, Level 16, State 9: ALTER TABLE ALTER COLUMN c1 failed because one or more objects access this column. - String sql = "CREATE TABLE #chained_exception_test_x1(c1 INT DEFAULT(0)); \n" - + "ALTER TABLE #chained_exception_test_x1 ALTER COLUMN c1 VARCHAR(10); \n" - + "DROP TABLE IF EXISTS #chained_exception_test_x1; \n"; try (Connection conn = getConnection()) { - // NOTE: stmnt.execute() or executeUpdate() wont work in here since it do not read through the full TDS stream -// execSql(con, sql); - - try (Statement stmnt = conn.createStatement()) { - stmnt.executeUpdate("CREATE TABLE #chained_exception_test_x1(c1 INT DEFAULT(0))"); - stmnt.executeUpdate("ALTER TABLE #chained_exception_test_x1 ALTER COLUMN c1 VARCHAR(10)"); - stmnt.executeUpdate("DROP TABLE IF EXISTS #chained_exception_test_x1"); - } + // The below should yield the following Server Messages: + // 1 : Msg 5074, Level 16, State 1: The object 'DF__#chained_exc__c1__AB25243A' is dependent on column 'c1'. + // 1 : Msg 4922, Level 16, State 9: ALTER TABLE ALTER COLUMN c1 failed because one or more objects access this column. + try (Statement stmnt = conn.createStatement()) { + stmnt.executeUpdate("CREATE TABLE #chained_exception_test_x1(c1 INT DEFAULT(0))"); + stmnt.executeUpdate("ALTER TABLE #chained_exception_test_x1 ALTER COLUMN c1 VARCHAR(10)"); + stmnt.executeUpdate("DROP TABLE IF EXISTS #chained_exception_test_x1"); + } fail(TestResource.getResource("R_expectedFailPassed")); @@ -120,20 +53,19 @@ public void testTwoExceptions() throws Exception { while (ex != null) { exCount++; - System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>> ex[" + exCount + "].getErrorCode()=" + ex.getErrorCode() + ", Severity=" + ((SQLServerException)ex).getSQLServerError().getErrorSeverity()+ ", Text=|" + ex.getMessage() + "|."); lastMsgNum = ex.getErrorCode(); ex = ex.getNextException(); } // Exception Count should be: 2 - assertEquals(2, exCount, "Number of SQLExceptions in the SQLException chain"); + assertEquals(2, exCount, "Number of SQLExceptions in the SQLException chain"); - // Check first Msg: 5074 - assertEquals(5074, firstMsgNum, "First SQL Server Message"); + // Check first Msg: 5074 + assertEquals(5074, firstMsgNum, "First SQL Server Message"); - // Check last Msg: 4922 - assertEquals(4922, lastMsgNum, "Last SQL Server Message"); + // Check last Msg: 4922 + assertEquals(4922, lastMsgNum, "Last SQL Server Message"); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java index 9937f04f8..3cdd7c1b5 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java @@ -212,46 +212,43 @@ public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning) // Create a massage handler conn.setServerMessageHandler(testMsgHandler); - // NOTE: stmnt.execute() or executeUpdate() wont work in here since it do not read through the full TDS stream -// execSql(con, sql); - try (Statement stmnt = conn.createStatement()) { - stmnt.executeUpdate("CREATE TABLE #msghandler_tmp_table(id int, c1 varchar(255))"); + 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)"); + 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(); + 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(); + 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"); + 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"); + 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."); } @@ -330,55 +327,55 @@ public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning) // 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 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."); + 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); + 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."); + 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 @@ -419,8 +416,8 @@ public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning) if (50_000 == srvErrorOrWarning.getErrorNumber()) { //System.out.println("DEBUG: testMsgHandlerWithProcedureFeedback.messageHandler(): FEEDBACK: " + srvErrorOrWarning.getErrorMessage()); - // Remember when the message was received - feedbackMsgTs.put(srvErrorOrWarning.getErrorMessage(), System.currentTimeMillis()); + // Remember when the message was received + feedbackMsgTs.put(srvErrorOrWarning.getErrorMessage(), System.currentTimeMillis()); } return srvErrorOrWarning; @@ -456,21 +453,21 @@ public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning) // 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."); + 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_feeback_proc }")) { - cstmnt.registerOutParameter(1, Types.INTEGER); - cstmnt.execute(); - int procReturnCode = cstmnt.getInt(1); + cstmnt.registerOutParameter(1, Types.INTEGER); + cstmnt.execute(); + int procReturnCode = cstmnt.getInt(1); assertEquals(0, procReturnCode, "Unexpected ReturnCode from the temp procedure."); - assertEquals(0, getWarningCount("conn" , conn .getWarnings()), "Unexpected Number Of SQLWarnings from 'exec proc', at Connection."); - assertEquals(doSqlLoopCount, getWarningCount("cstmnt", cstmnt.getWarnings()), "Unexpected Number Of SQLWarnings from 'exec proc', at CallableStatement."); + assertEquals(0, getWarningCount("conn" , conn .getWarnings()), "Unexpected Number Of SQLWarnings from 'exec proc', at Connection."); + assertEquals(doSqlLoopCount, getWarningCount("cstmnt", cstmnt.getWarnings()), "Unexpected Number Of SQLWarnings from 'exec proc', at CallableStatement."); } // numOfCalls to the message handler should be: # @@ -480,17 +477,17 @@ public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning) long prevTime = 0; for (Entry entry : testMsgHandler.feedbackMsgTs.entrySet()) { if (prevTime == 0) { - prevTime = entry.getValue(); + prevTime = entry.getValue(); continue; } long msDiff = entry.getValue() - prevTime; if (msDiff < 800 || msDiff > 1200) { - fail("Received Messages is to far apart. They should be approx 1000 ms. msDiff=" + msDiff + " Message=|" + entry.getKey() + "|."); + fail("Received Messages is to far apart. They should be approx 1000 ms. msDiff=" + msDiff + " Message=|" + entry.getKey() + "|."); } prevTime = entry.getValue(); - } + } } catch (SQLException ex) { fail(TestResource.getResource("R_unexpectedErrorMessage")); From 3ddac9170470336243ccd51247fb9833c003b87b Mon Sep 17 00:00:00 2001 From: Goran Schwarz Date: Mon, 13 Nov 2023 17:27:29 +0100 Subject: [PATCH 08/12] removed public for my get/setMessageHandler from interface ISQLServerConnection --- .../com/microsoft/sqlserver/jdbc/ISQLServerConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java index 184a729eb..a73d12bc5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java @@ -487,11 +487,11 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold, * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)} * @return */ - public ISQLServerMessageHandler getServerMessageHandler(); + ISQLServerMessageHandler getServerMessageHandler(); /** * Set message handler on the connection * @see {@link ISQLServerMessageHandler#messageHandler(ISQLServerMessage)} */ - public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler); + ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler); } From 8f6fa89687f189d3c39aaa7ce708002ba4c0405f Mon Sep 17 00:00:00 2001 From: Goran Schwarz Date: Mon, 13 Nov 2023 18:52:44 +0100 Subject: [PATCH 09/12] Code formatting --- .../java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java | 1 - .../java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java index b0b3b4e20..fbc078f37 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java @@ -58,5 +58,4 @@ public interface ISQLServerStatement extends java.sql.Statement, Serializable { * if any error occurs */ void setCancelQueryTimeout(int seconds) throws SQLServerException; - } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index ddbf1f291..9ed3e683b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -8293,7 +8293,7 @@ public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler * @return Get Currently installed message handler on the connection */ @Override - public ISQLServerMessageHandler getServerMessageHandler() + public ISQLServerMessageHandler getServerMessageHandler() { return this.serverMessageHandler; } From 9e984fdf08cb82d9dfd2d1a19927922c38f15c91 Mon Sep 17 00:00:00 2001 From: Goran Schwarz Date: Mon, 13 Nov 2023 23:49:08 +0100 Subject: [PATCH 10/12] Changed a cast in 'SQLServerStatement' from 'SQLServerConnection' to 'ISQLServerConnection' this might solve my strange problem! Also added get/setServerMessageHandler to getVerifiedMethodNames() in RequestBoundaryMethodsTest.java --- .../java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java | 2 +- .../sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 4d957c0ba..b14f40e9c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -1655,7 +1655,7 @@ boolean onInfo(TDSReader tdsReader) throws SQLServerException { // - discard // - upgrade to Error // - or simply pass on - ISQLServerMessageHandler msgHandler = ((SQLServerConnection)getConnection()).getServerMessageHandler(); + ISQLServerMessageHandler msgHandler = ((ISQLServerConnection)getConnection()).getServerMessageHandler(); if (msgHandler != null) { // Let the message handler decide if the error should be unchanged, up/down-graded or ignored 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 da2d4d73f..b98f4a143 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java @@ -509,6 +509,8 @@ private List getVerifiedMethodNames() { verifiedMethodNames.add("setMsiTokenCacheTtl"); verifiedMethodNames.add("getAccessTokenCallbackClass"); verifiedMethodNames.add("setAccessTokenCallbackClass"); + verifiedMethodNames.add("getServerMessageHandler"); + verifiedMethodNames.add("setServerMessageHandler"); return verifiedMethodNames; } } From 4ba63c64e436a24f00de4b423bea9b3f9f92fc7e Mon Sep 17 00:00:00 2001 From: Jeffery Wasty Date: Thu, 16 Nov 2023 09:54:49 -0800 Subject: [PATCH 11/12] Update SQLServerConnectionPoolProxy.java --- .../sqlserver/jdbc/SQLServerConnectionPoolProxy.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java index a10b05653..0fdc69e3f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java @@ -693,15 +693,14 @@ public void setAccessTokenCallbackClass(String accessTokenCallbackClass) { } @Override - public ISQLServerMessageHandler getServerMessageHandler() - { + public ISQLServerMessageHandler getServerMessageHandler() { return wrappedConnection.getServerMessageHandler(); } @Override - public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler) - { + public ISQLServerMessageHandler setServerMessageHandler(ISQLServerMessageHandler messageHandler) { return wrappedConnection.setServerMessageHandler(messageHandler); + } /** * Returns the current value for 'calcBigDecimalScale'. From 157394a73c95440f49f525893205e718f8d9d084 Mon Sep 17 00:00:00 2001 From: Goran Schwarz Date: Sat, 9 Mar 2024 18:00:08 +0100 Subject: [PATCH 12/12] Made some changes requested by reviewer: lilgreenbird --- .../jdbc/ISQLServerMessageHandler.java | 41 +++++++++++++++---- .../sqlserver/jdbc/SQLServerConnection.java | 2 + .../sqlserver/jdbc/SQLServerInfoMessage.java | 12 ++++++ .../sqlserver/jdbc/SQLServerWarning.java | 11 ++++- .../MessageHandlerTest.java} | 4 +- 5 files changed, 59 insertions(+), 11 deletions(-) rename src/test/java/com/microsoft/sqlserver/jdbc/{msghandler/MsgHandlerTest.java => messageHandler/MessageHandlerTest.java} (97%) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java index 1e7511f06..74d7d7518 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerMessageHandler.java @@ -4,6 +4,31 @@ */ 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: + *

      + *
    • "message feedback"
      + * Display Server messages from a long running SQL Statement
      + * Like RAISERROR ('Progress message...', 0, 1) WITH NOWAIT
      + * Or Status messages from a running backup...
      + *
    • + *
    • "Universal" error logging
      + * Your error-message handler can contain the logic for handling all error logging. + *
    • + *
    • "Universal" error handling
      + * Error-handling logic can be placed in your error-message handler, instead of being repeated throughout your application. + *
    • + *
    • Remapping of error-message severity, based on application requirements
      + * 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. + *
    • + *
    + *

    + * For example code, see {@link #messageHandler(ISQLServerMessage)} + */ public interface ISQLServerMessageHandler { /** @@ -31,19 +56,19 @@ public interface ISQLServerMessageHandler * * Example code: *

    -     *  public ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning)
    +     *  public ISQLServerMessage messageHandler(ISQLServerMessage serverErrorOrWarning)
          *  {
    -     *      ISQLServerMessage retObj = srvErrorOrWarning;
    +     *      ISQLServerMessage retObj = serverErrorOrWarning;
          *
    -     *      if (srvErrorOrWarning.isErrorMessage()) {
    +     *      if (serverErrorOrWarning.isErrorMessage()) {
          *
          *          // Downgrade: 2601 -- Cannot insert duplicate key row...
    -     *          if (2601 == srvErrorOrWarning.getErrorNumber()) {
    -     *              retObj = srvErrorOrWarning.getSQLServerMessage().toSQLServerInfoMessage();
    +     *          if (2601 == serverErrorOrWarning.getErrorNumber()) {
    +     *              retObj = serverErrorOrWarning.getSQLServerMessage().toSQLServerInfoMessage();
          *          }
          *
          *          // Discard: 3701 -- Cannot drop the table ...
    -     *          if (3701 == srvErrorOrWarning.getErrorNumber()) {
    +     *          if (3701 == serverErrorOrWarning.getErrorNumber()) {
          *              retObj = null;
          *          }
          *      }
    @@ -53,7 +78,7 @@ public interface ISQLServerMessageHandler
         
          * 
    * - * @param srvErrorOrWarning + * @param serverErrorOrWarning * @return *
      *
    • unchanged same object as passed in.
      @@ -74,5 +99,5 @@ public interface ISQLServerMessageHandler *
    • *
    */ - ISQLServerMessage messageHandler(ISQLServerMessage srvErrorOrWarning); + ISQLServerMessage messageHandler(ISQLServerMessage serverErrorOrWarning); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 9ed3e683b..4d69df428 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -4728,6 +4728,7 @@ 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 { @@ -4742,6 +4743,7 @@ void addWarning(SQLWarning sqlWarning) { } // 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())); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java index c1552d9d3..8b6579491 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerInfoMessage.java @@ -7,6 +7,17 @@ import java.sql.SQLException; +/** + * 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.
    + * Like: In what procedure was the message produced. + *

    + * 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(); @@ -19,6 +30,7 @@ public final class SQLServerInfoMessage extends StreamPacket implements ISQLServ this.msg = msg; } + @Override void setFromTDS(TDSReader tdsReader) throws SQLServerException { if (TDS.TDS_MSG != tdsReader.readUnsignedByte()) assert false; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java index 34e6285c3..81d588bd9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerWarning.java @@ -6,10 +6,19 @@ 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.
    + * Like: In what procedure was the message produced. + */ public class SQLServerWarning extends SQLWarning { - private static final long serialVersionUID = -5212432397705929142L; + private static final long serialVersionUID = -5212432397705929142L; /** SQL server error */ private SQLServerError sqlServerError; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/messageHandler/MessageHandlerTest.java similarity index 97% rename from src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java rename to src/test/java/com/microsoft/sqlserver/jdbc/messageHandler/MessageHandlerTest.java index 3cdd7c1b5..94042a7d8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/msghandler/MsgHandlerTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/messageHandler/MessageHandlerTest.java @@ -2,7 +2,7 @@ * 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.msghandler; +package com.microsoft.sqlserver.jdbc.messageHandler; import static org.junit.Assert.fail; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -32,7 +32,7 @@ @RunWith(JUnitPlatform.class) -public class MsgHandlerTest extends AbstractTest { +public class MessageHandlerTest extends AbstractTest { @BeforeAll public static void setupTests() throws Exception {