All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.sqlserver.jdbc.tdsparser Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
/*
 * 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy