⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sqlparser.java

📁 jtds的源码 是你学习java的好东西
💻 JAVA
📖 第 1 页 / 共 3 页
字号:
// jTDS JDBC Driver for Microsoft SQL Server and Sybase
// Copyright (C) 2004 The jTDS Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
package net.sourceforge.jtds.jdbc;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;

import net.sourceforge.jtds.jdbc.cache.SimpleLRUCache;
import net.sourceforge.jtds.jdbc.cache.SQLCacheKey;

/**
 * Process JDBC escape strings and parameter markers in the SQL string.
 * <p>
 * This code recognizes the following escapes:
 * <ol>
 * <li>Date      {d 'yyyy-mm-dd'}
 * <li>Time      {t 'hh:mm:ss'}
 * <li>Timestamp {ts 'yyyy-mm-dd hh:mm:ss.nnn'}
 * <li>ESCAPE    {escape 'x'}
 * <li>Function  {fn xxxx([arg,arg...])}
 * NB The concat(arg, arg) operator is converted to (arg + arg)
 * <li>OuterJoin {oj .....}
 * <li>Call      {?=call proc [arg, arg...]}
 * or        {call proc [arg, arg...]}
 * </ol>
 * Notes:
 * <ol>
 * <li>This code is designed to be as efficient as possible and as
 * result the validation done here is limited.
 * <li>SQL comments are parsed correctly thanks to code supplied by
 * Joel Fouse.
 * </ol>
 *
 * @author Mike Hutchinson
 * @version $Id: SQLParser.java,v 1.29 2007/07/08 17:28:23 bheineman Exp $
 */
class SQLParser {
    /**
     * Serialized version of a parsed SQL query (the value stored in the cache
     * for a parsed SQL).
     * <p/>
     * Holds the parsed SQL query and the names, positions and return value and
     * unicode flags for the parameters.
     */
    private static class CachedSQLQuery {
        final String[]  parsedSql;
        final String[]  paramNames;
        final int[]     paramMarkerPos;
        final boolean[] paramIsRetVal;
        final boolean[] paramIsUnicode;

        CachedSQLQuery(String[] parsedSql, ArrayList params) {
            this.parsedSql = parsedSql;

            if (params != null) {
                final int size = params.size();
                paramNames     = new String[size];
                paramMarkerPos = new int[size];
                paramIsRetVal  = new boolean[size];
                paramIsUnicode = new boolean[size];

                for (int i = 0; i < size; i++) {
                    ParamInfo paramInfo = (ParamInfo) params.get(i);
                    paramNames[i]     = paramInfo.name;
                    paramMarkerPos[i] = paramInfo.markerPos;
                    paramIsRetVal[i]  = paramInfo.isRetVal;
                    paramIsUnicode[i] = paramInfo.isUnicode;
                }
            } else {
                paramNames = null;
                paramMarkerPos = null;
                paramIsRetVal = null;
                paramIsUnicode = null;
            }
        }
    }

    /** LRU cache of previously parsed SQL */
    private static SimpleLRUCache cache;

    /** Original SQL string */
    private final String sql;
    /** Input buffer with SQL statement. */
    private final char[] in;
    /** Current position in input buffer. */
    private int s;
    /** Length of input buffer. */
    private final int len;
    /** Output buffer to contain parsed SQL. */
    private final char[] out;
    /** Current position in output buffer. */
    private int d;
    /**
     * Parameter list to be populated or <code>null</code> if no parameters
     * are expected.
     */
    private final ArrayList params;
    /** Current expected terminator character. */
    private char terminator;
    /** Procedure name in call escape. */
    private String procName;
    /** First SQL keyword or identifier in statement. */
    private String keyWord;
    /** First table name in from clause */
    private String tableName;
    /** Connection object for server specific parsing. */
    private final ConnectionJDBC2 connection;

    /**
     * Parse the SQL statement processing JDBC escapes and parameter markers.
     *
     * @param extractTable
     *            true to return the first table name in the FROM clause of a select
     * @return The processed SQL statement, any procedure name, the first SQL
     *         keyword and (optionally) the first table name as
     *         elements 0 1, 2 and 3 of the returned <code>String[]</code>.
     * @throws SQLException if a parse error occurs
     */
    static String[] parse(String sql, ArrayList paramList,
                          ConnectionJDBC2 connection, boolean extractTable)
            throws SQLException {
    	// Don't cache extract table parse requests, just process it
    	if (extractTable) {
    		SQLParser parser = new SQLParser(sql, paramList, connection);
    		return parser.parse(extractTable);
    	}

        SimpleLRUCache cache = getCache(connection);

        SQLCacheKey cacheKey = new SQLCacheKey(sql, connection);

        // By not synchronizing on the cache, we're admitting that the possibility of multiple
        // parses of the same statement can occur.  However, it is 1) unlikely under normal
        // usage, and 2) harmless to the cache.  By avoiding a synchronization block around
        // the get()-parse()-put(), we reduce the contention greatly in the nominal case.
        CachedSQLQuery cachedQuery = (CachedSQLQuery) cache.get(cacheKey);
        if (cachedQuery == null) {
            // Parse and cache SQL
            SQLParser parser = new SQLParser(sql, paramList, connection);
            cachedQuery = new CachedSQLQuery(parser.parse(extractTable),
                    paramList);
            cache.put(cacheKey, cachedQuery);
        } else {
            // Create full ParamInfo objects out of cached object
            final int length = (cachedQuery.paramNames == null)
                    ? 0 : cachedQuery.paramNames.length;
            for (int i = 0; i < length; i++) {
                ParamInfo paramInfo = new ParamInfo(cachedQuery.paramNames[i],
                                                    cachedQuery.paramMarkerPos[i],
                                                    cachedQuery.paramIsRetVal[i],
                                                    cachedQuery.paramIsUnicode[i]);
                paramList.add(paramInfo);
            }
        }
        return cachedQuery.parsedSql;
    }

    // --------------------------- Private Methods --------------------------------

    /**
     * Retrieves the statement cache, creating it if required.
     *
     * @return the cache as a <code>SimpleLRUCache</code>
     */
    private synchronized static SimpleLRUCache getCache(ConnectionJDBC2 connection) {
        if (cache == null) {
            int maxStatements = connection.getMaxStatements();
            maxStatements = Math.max(0, maxStatements);
            maxStatements = Math.min(1000, maxStatements);
            cache = new SimpleLRUCache(maxStatements);
        }
        return cache;
    }


    /** Lookup table to test if character is part of an identifier. */
    private static boolean identifierChar[] = {
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false,
            false, false, false, true,  true,  false, false, false,
            false, false, false, false, false, false, false, false,
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  false, false, false, false, false, false,
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  false, false, false, false, true,
            false, true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  true,  true,  true,  true,  true,
            true,  true,  true,  false, false, false, false, false
    };

    /**
     * Determines if character could be part of an SQL identifier.
     * <p/>
     * Characters > 127 are assumed to be unicode letters in other
     * languages than english which is reasonable in this application.
     * @param ch the character to test.
     * @return <code>boolean</code> true if ch in A-Z a-z 0-9 @ $ # _.
     */
    private static boolean isIdentifier(int ch) {
        return ch > 127 || identifierChar[ch];
    }

    /**
     * Constructs a new parser object to process the supplied SQL.
     *
     * @param sqlIn     the SQL statement to parse
     * @param paramList the parameter list array to populate or
     *                  <code>null</code> if no parameters are expected
     * @param connection the parent Connection object
     */
    private SQLParser(String sqlIn, ArrayList paramList, ConnectionJDBC2 connection) {
        sql = sqlIn;
        in  = sql.toCharArray();
        len = in.length;
        out = new char[len + 256]; // Allow extra for curdate/curtime
        params = paramList;
        procName = "";

        this.connection = connection;
    }

    /**
     * Inserts a String literal in the output buffer.
     *
     * @param txt The text to insert.
     */
    private void copyLiteral(String txt) throws SQLException {
        final int len = txt.length();

        for (int i = 0; i < len; i++) {
            final char c = txt.charAt(i);

            if (c == '?') {
                if (params == null) {
                    throw new SQLException(
                            Messages.get("error.parsesql.unexpectedparam",
                                    String.valueOf(s)),
                            "2A000");
                }
                // param marker embedded in escape
                ParamInfo pi = new ParamInfo(d, connection.getUseUnicode());
                params.add(pi);
            }

            out[d++] = c;
        }
    }

    /**
     * Copies over an embedded string literal unchanged.
     */
    private void copyString() {
        char saveTc = terminator;
        char tc = in[s];

        if (tc == '[') {
            tc = ']';
        }

        terminator = tc;

        out[d++] = in[s++];

        while (in[s] != tc) {
            out[d++] = in[s++];
        }

        out[d++] = in[s++];

        terminator = saveTc;
    }

    /**
     * Copies over possible SQL keyword eg 'SELECT'
     */
    private String copyKeyWord() {
        int start = d;

        while (s < len && isIdentifier(in[s])) {
            out[d++] = in[s++];
        }

        return String.valueOf(out, start, d - start).toLowerCase();
    }

    /**
     * Builds a new parameter item.
     *
     * @param name Optional parameter name or null.
     * @param pos The parameter marker position in the output buffer.
     */
    private void copyParam(String name, int pos) throws SQLException {
        if (params == null) {
            throw new SQLException(
                    Messages.get("error.parsesql.unexpectedparam",
                            String.valueOf(s)),
                    "2A000");
        }

        ParamInfo pi = new ParamInfo(pos, connection.getUseUnicode());
        pi.name = name;

        if (pos >= 0) {
            out[d++] = in[s++];
        } else {
            pi.isRetVal = true;
            s++;
        }

        params.add(pi);
    }

    /**
     * Copies an embedded stored procedure identifier over to the output buffer.
     *
     * @return The identifier as a <code>String</code>.
     */
    private String copyProcName() throws SQLException {
        int start = d;

        do {
            if (in[s] == '"' || in[s] == '[') {
                copyString();
            } else {
                char c = in[s++];

                while (isIdentifier(c) || c == ';') {
                    out[d++] = c;
                    c = in[s++];
                }

                s--;
            }

            if (in[s] == '.') {
                while (in[s] == '.') {
                    out[d++] = in[s++];
                }
            } else {
                break;
            }
        } while (true);

        if (d == start) {
            // Procedure name expected but found something else
            throw new SQLException(
                    Messages.get("error.parsesql.syntax",
                            "call",
                            String.valueOf(s)),
                    "22025");
        }

        return new String(out, start, d - start);
    }

    /**
     * Copies an embedded parameter name to the output buffer.
     *
     * @return The identifier as a <code>String</code>.
     */
    private String copyParamName() {
        int start = d;
        char c = in[s++];

        while (isIdentifier(c)) {
            out[d++] = c;
            c = in[s++];

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -