com.microsoft.sqlserver.jdbc.tdsparser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC Driver for SQL Server.
/*
* 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.util.logging.Level;
import java.util.logging.Logger;
/**
* The top level TDS parser class.
*/
final class TDSParser {
private TDSParser() {
throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported"));
}
/** TDS protocol diagnostics logger */
private static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.TOKEN");
/*
* Parses a TDS token stream from a reader using the supplied token handler. Parsing requires the ability to peek
* one byte ahead into the token stream to determine the token type of the next token in the stream. When the token
* type has been determined, the token handler is called to process the token (or not). Parsing continues until the
* token handler says to stop by returning false from one of the token handling methods.
*/
static void parse(TDSReader tdsReader, String logContext) throws SQLServerException {
parse(tdsReader, new TDSTokenHandler(logContext));
}
/**
* Default parse method to parse all tokens in TDS stream.
*
* @param tdsReader
* @param tdsTokenHandler
* @throws SQLServerException
*/
static void parse(TDSReader tdsReader, TDSTokenHandler tdsTokenHandler) throws SQLServerException {
parse(tdsReader, tdsTokenHandler, false);
}
/**
* Underlying parse method to parse all tokens in TDS stream. Also accepts 'readOnlyWarningFlag' to parse only
* SQLWarnings received in TDS_MSG tokens.
*
* @param tdsReader
* @param tdsTokenHandler
* @param readOnlyWarningsFlag
* - true if only TDS_MSG tokens need to be parsed in TDS Stream. false - to parse all tokens in TDS Stream.
* @throws SQLServerException
*/
static void parse(TDSReader tdsReader, TDSTokenHandler tdsTokenHandler,
boolean readOnlyWarningsFlag) throws SQLServerException {
final boolean isLogging = logger.isLoggable(Level.FINEST);
// Process TDS tokens from the token stream until we're told to stop.
boolean parsing = true;
// If TDS_LOGIN_ACK is received verify for TDS_FEATURE_EXTENSION_ACK packet
boolean isLoginAck = false;
boolean isFeatureExtAck = false;
while (parsing) {
int tdsTokenType = tdsReader.peekTokenType();
if (isLogging) {
logger.finest(tdsReader.toString() + ": " + tdsTokenHandler.logContext + ": Processing "
+ ((-1 == tdsTokenType) ? "EOF" : TDS.getTokenName(tdsTokenType)));
}
if (readOnlyWarningsFlag && TDS.TDS_MSG != tdsTokenType) {
return;
}
switch (tdsTokenType) {
case TDS.TDS_SSPI:
parsing = tdsTokenHandler.onSSPI(tdsReader);
break;
case TDS.TDS_LOGIN_ACK:
isLoginAck = true;
parsing = tdsTokenHandler.onLoginAck(tdsReader);
break;
case TDS.TDS_FEATURE_EXTENSION_ACK:
isFeatureExtAck = true;
tdsReader.getConnection().processFeatureExtAck(tdsReader);
parsing = true;
break;
case TDS.TDS_ENV_CHG:
parsing = tdsTokenHandler.onEnvChange(tdsReader);
break;
case TDS.TDS_SESSION_STATE:
parsing = tdsTokenHandler.onSessionState(tdsReader);
break;
case TDS.TDS_RET_STAT:
parsing = tdsTokenHandler.onRetStatus(tdsReader);
break;
case TDS.TDS_RETURN_VALUE:
parsing = tdsTokenHandler.onRetValue(tdsReader);
break;
case TDS.TDS_DONEINPROC:
case TDS.TDS_DONEPROC:
case TDS.TDS_DONE:
tdsReader.getCommand().checkForInterrupt();
parsing = tdsTokenHandler.onDone(tdsReader);
break;
case TDS.TDS_ERR:
parsing = tdsTokenHandler.onError(tdsReader);
break;
case TDS.TDS_MSG:
parsing = tdsTokenHandler.onInfo(tdsReader);
break;
case TDS.TDS_ORDER:
parsing = tdsTokenHandler.onOrder(tdsReader);
break;
case TDS.TDS_COLMETADATA:
parsing = tdsTokenHandler.onColMetaData(tdsReader);
break;
case TDS.TDS_ROW:
parsing = tdsTokenHandler.onRow(tdsReader);
break;
case TDS.TDS_NBCROW:
parsing = tdsTokenHandler.onNBCRow(tdsReader);
break;
case TDS.TDS_COLINFO:
parsing = tdsTokenHandler.onColInfo(tdsReader);
break;
case TDS.TDS_TABNAME:
parsing = tdsTokenHandler.onTabName(tdsReader);
break;
case TDS.TDS_FEDAUTHINFO:
parsing = tdsTokenHandler.onFedAuthInfo(tdsReader);
break;
case TDS.TDS_SQLDATACLASSIFICATION:
parsing = tdsTokenHandler.onDataClassification(tdsReader);
break;
case -1:
tdsReader.getCommand().onTokenEOF();
tdsTokenHandler.onEOF(tdsReader);
parsing = false;
break;
default:
throwUnexpectedTokenException(tdsReader, tdsTokenHandler.logContext);
break;
}
}
// if TDS_FEATURE_EXTENSION_ACK is not received verify if TDS_FEATURE_EXT_AE was sent
if (isLoginAck && !isFeatureExtAck)
tdsReader.tryProcessFeatureExtAck(isFeatureExtAck);
}
/* Handle unexpected tokens - throw an exception */
static void throwUnexpectedTokenException(TDSReader tdsReader, String logContext) throws SQLServerException {
if (logger.isLoggable(Level.SEVERE))
logger.severe(tdsReader.toString() + ": " + logContext + ": Encountered unexpected "
+ TDS.getTokenName(tdsReader.peekTokenType()));
tdsReader.throwInvalidTDSToken(TDS.getTokenName(tdsReader.peekTokenType()));
}
/* Ignore a length-prefixed token */
static void ignoreLengthPrefixedToken(TDSReader tdsReader) throws SQLServerException {
tdsReader.readUnsignedByte(); // token type
int envValueLength = tdsReader.readUnsignedShort();
byte[] envValueData = new byte[envValueLength];
tdsReader.readBytes(envValueData, 0, envValueLength);
}
}
/**
* A default TDS token handler with some meaningful default processing. Other token handlers should subclass from this
* one to override the defaults and provide specialized functionality.
*
* ENVCHANGE_TOKEN Processes the ENVCHANGE
*
* RETURN_STATUS_TOKEN Ignores the returned value
*
* DONE_TOKEN DONEPROC_TOKEN DONEINPROC_TOKEN Ignores the returned value
*
* ERROR_TOKEN Remember the error and throw a SQLServerException with that error on EOF
*
* INFO_TOKEN ORDER_TOKEN COLINFO_TOKEN (not COLMETADATA_TOKEN) TABNAME_TOKEN Ignore the token
*
* EOF Throw a database exception with text from the last error token
*
* All other tokens Throw a TDS protocol error exception
*/
class TDSTokenHandler {
final String logContext;
private SQLServerError databaseError;
/** TDS protocol diagnostics logger */
private static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.TOKEN");
final SQLServerError getDatabaseError() {
return databaseError;
}
public void addDatabaseError(SQLServerError databaseError) {
if (this.databaseError == null) {
this.databaseError = databaseError;
} else {
this.databaseError.addError(databaseError);
}
}
TDSTokenHandler(String logContext) {
this.logContext = logContext;
}
boolean onSSPI(TDSReader tdsReader) throws SQLServerException {
TDSParser.throwUnexpectedTokenException(tdsReader, logContext);
return false;
}
boolean onLoginAck(TDSReader tdsReader) throws SQLServerException {
TDSParser.throwUnexpectedTokenException(tdsReader, logContext);
return false;
}
boolean onFeatureExtensionAck(TDSReader tdsReader) throws SQLServerException {
TDSParser.throwUnexpectedTokenException(tdsReader, logContext);
return false;
}
boolean onEnvChange(TDSReader tdsReader) throws SQLServerException {
tdsReader.getConnection().processEnvChange(tdsReader);
return true;
}
boolean onSessionState(TDSReader tdsReader) throws SQLServerException {
tdsReader.getConnection().processSessionState(tdsReader);
return true;
}
boolean onRetStatus(TDSReader tdsReader) throws SQLServerException {
(new StreamRetStatus()).setFromTDS(tdsReader);
return true;
}
boolean onRetValue(TDSReader tdsReader) throws SQLServerException {
// Very unlikely to return true. If we do, it was because any return values in the
// tds response were never read after the RPC. If they were never read, it's safe to skip
// them here
if (this.logContext.equals("ExecDoneHandler")) {
Parameter param = new Parameter(false);
param.skipRetValStatus(tdsReader);
param.skipValue(tdsReader, true);
return true;
}
TDSParser.throwUnexpectedTokenException(tdsReader, logContext);
return false;
}
boolean onDone(TDSReader tdsReader) throws SQLServerException {
StreamDone doneToken = new StreamDone();
doneToken.setFromTDS(tdsReader);
if (doneToken.isFinal()) {
// Response is completely processed hence decrement unprocessed response count.
tdsReader.getConnection().getSessionRecovery().decrementUnprocessedResponseCount();
}
return true;
}
boolean onError(TDSReader tdsReader) throws SQLServerException {
SQLServerError tmpDatabaseError = new SQLServerError();
tmpDatabaseError.setFromTDS(tdsReader);
ISQLServerMessageHandler msgHandler = tdsReader.getConnection().getServerMessageHandler();
if (msgHandler != null) {
// Let the message handler decide if the error should be unchanged/down-graded or ignored
ISQLServerMessage srvMessage = msgHandler.messageHandler(tmpDatabaseError);
// Ignored
if (srvMessage == null) {
return true;
}
// Down-graded to a SQLWarning
if (srvMessage.isInfoMessage()) {
tdsReader.getConnection().addWarning(srvMessage);
return true;
}
}
// set/add the database error
addDatabaseError(tmpDatabaseError);
return true;
}
boolean onInfo(TDSReader tdsReader) throws SQLServerException {
TDSParser.ignoreLengthPrefixedToken(tdsReader);
return true;
}
boolean onOrder(TDSReader tdsReader) throws SQLServerException {
TDSParser.ignoreLengthPrefixedToken(tdsReader);
return true;
}
boolean onColMetaData(TDSReader tdsReader) throws SQLServerException {
/*
* SHOWPLAN or something else that produces extra metadata might be ON. Log info instead of throwing an exception, warn
* and discard the extra column meta data
*/
if (logger.isLoggable(Level.INFO))
logger.info(tdsReader.toString() + ": " + logContext
+ ": Discarding extra metadata which can be a result of SHOWPLAN settings: "
+ TDS.getTokenName(tdsReader.peekTokenType()));
(new StreamColumns(false)).setFromTDS(tdsReader);
return false;
}
boolean onRow(TDSReader tdsReader) throws SQLServerException {
TDSParser.throwUnexpectedTokenException(tdsReader, logContext);
return false;
}
boolean onNBCRow(TDSReader tdsReader) throws SQLServerException {
TDSParser.throwUnexpectedTokenException(tdsReader, logContext);
return false;
}
boolean onColInfo(TDSReader tdsReader) throws SQLServerException {
TDSParser.ignoreLengthPrefixedToken(tdsReader);
return true;
}
boolean onTabName(TDSReader tdsReader) throws SQLServerException {
TDSParser.ignoreLengthPrefixedToken(tdsReader);
return true;
}
void onEOF(TDSReader tdsReader) throws SQLServerException {
if (null != getDatabaseError()) {
SQLServerException.makeFromDatabaseError(tdsReader.getConnection(), null,
getDatabaseError().getErrorMessage(), getDatabaseError(), false);
}
}
boolean onFedAuthInfo(TDSReader tdsReader) throws SQLServerException {
tdsReader.getConnection().processFedAuthInfo(tdsReader, this);
return true;
}
boolean onDataClassification(TDSReader tdsReader) throws SQLServerException {
TDSParser.throwUnexpectedTokenException(tdsReader, logContext);
return false;
}
}