/** * Copyright 2011-2013 Akiban Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * SQL Parser. * */ // TODO: The Derby exception handling coordinated localized messages // and SQLSTATE values, which will be needed, but in the context of // the new engine. package com.akiban.sql.parser; import com.akiban.sql.StandardException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public class SQLParser implements SQLParserContext { private String sqlText; private List<ParameterNode> parameterList; private boolean returnParameterFlag; private Map printedObjectsMap; private int generatedColumnNameIndex; private StringCharStream charStream = null; private SQLGrammarTokenManager tokenManager = null; private SQLGrammar parser = null; private int maxStringLiteralLength = 65535; /* Identifiers (Constraint, Cursor, Function/Procedure, Index, * Trigger, Column, Schema, Savepoint, Table and View names) are * limited to 128. */ private int maxIdentifierLength = 128; // TODO: Needs much more thought. private String messageLocale = null; // TODO: For now, has most MySQL stuff turned on. private Set<SQLParserFeature> features = EnumSet.of(SQLParserFeature.GROUPING, SQLParserFeature.DIV_OPERATOR, SQLParserFeature.GEO_INDEX_DEF_FUNC, SQLParserFeature.MYSQL_HINTS, SQLParserFeature.MYSQL_INTERVAL, SQLParserFeature.MYSQL_LEFT_RIGHT_FUNC, SQLParserFeature.UNSIGNED, SQLParserFeature.INFIX_MOD); NodeFactory nodeFactory; /** Make a new parser. * Parser can be reused. */ public SQLParser() { nodeFactory = new NodeFactoryImpl(); } /** Return the SQL string this parser just parsed. */ public String getSQLText() { return sqlText; } /** Return the parameters to the parsed statement. */ public List<ParameterNode> getParameterList() { return parameterList; } /** * Looks up an unnamed parameter given its parameter number. * * @param paramNumber Number of parameter in unnamedparameter list. * * @return corresponding unnamed parameter. * */ public ParameterNode lookupUnnamedParameter(int paramNumber) { return parameterList.get(paramNumber); } /** Normal external parser entry. */ public StatementNode parseStatement(String sqlText) throws StandardException { reinit(sqlText); try { return parser.parseStatement(sqlText, parameterList); } catch (ParseException ex) { throw new SQLParserException(standardizeEol(ex.getMessage()), ex, tokenErrorPosition(ex.currentToken, sqlText)); } catch (TokenMgrError ex) { // Throw away the cached parser. parser = null; if (ex.errorCode == TokenMgrError.LEXICAL_ERROR) throw new SQLParserException(ex.getMessage(), ex, lineColumnErrorPosition(ex.errorLine, ex.errorColumn, sqlText)); else throw new StandardException(ex); } } /** Parse multiple statements delimited by semicolons. */ public List<StatementNode> parseStatements(String sqlText) throws StandardException { reinit(sqlText); try { return parser.parseStatements(sqlText); } catch (ParseException ex) { throw new SQLParserException(standardizeEol(ex.getMessage()), ex, tokenErrorPosition(ex.currentToken, sqlText)); } catch (TokenMgrError ex) { // Throw away the cached parser. parser = null; if (ex.errorCode == TokenMgrError.LEXICAL_ERROR) throw new SQLParserException(ex.getMessage(), ex, lineColumnErrorPosition(ex.errorLine, ex.errorColumn, sqlText)); else throw new StandardException(ex); } } /** Undo ParseException.initialise()'s eol handling. * Want something platform independent. */ private static String standardizeEol(String msg) { String eol = System.getProperty("line.separator", "\n"); if (eol.equals("\n")) return msg; else return msg.replaceAll(eol, "\n"); } /** Translate position of token into linear position. */ private static int tokenErrorPosition(Token token, String sql) { if (token == null) return 0; return lineColumnErrorPosition(token.next.beginLine, token.next.beginColumn, sql); } /** Translate line position into linear position. */ private static int lineColumnErrorPosition(int line, int column, String sql) { if (line <= 0) return 0; int position = 0; while (line-- > 1) { position = sql.indexOf('\n', position); if (position < 0) return 0; position++; } position += column; return position; } protected void reinit(String sqlText) throws StandardException { this.sqlText = sqlText; if (charStream == null) { charStream = new StringCharStream(sqlText); } else { charStream.ReInit(sqlText); } if (tokenManager == null) { tokenManager = new SQLGrammarTokenManager(null, charStream); } else { tokenManager.ReInit(charStream); } if (parser == null) { parser = new SQLGrammar(tokenManager); parser.setParserContext(this); } else { parser.ReInit(tokenManager); } tokenManager.parser = parser; parameterList = new ArrayList<ParameterNode>(); returnParameterFlag = false; printedObjectsMap = null; generatedColumnNameIndex = 1; } /** Get maximum length of a string literal. */ public int getMaxStringLiteralLength() { return maxStringLiteralLength; } /** Set maximum length of a string literal. */ public void setMaxStringLiteralLength(int maxLength) { maxStringLiteralLength = maxLength; } /** Check that string literal is not too long. */ public void checkStringLiteralLengthLimit(String image) throws StandardException { if (image.length() > maxStringLiteralLength) { throw new StandardException("String literal too long"); } } /** Get maximum length of an identifier. */ public int getMaxIdentifierLength() { return maxIdentifierLength; } /** Set maximum length of an identifier. */ public void setMaxIdentifierLength(int maxLength) { maxIdentifierLength = maxLength; } /** * Check that identifier is not too long. */ public void checkIdentifierLengthLimit(String identifier) throws StandardException { if (identifier.length() > maxIdentifierLength) throw new StandardException("Identifier too long: '" + identifier + "'"); } public void setReturnParameterFlag() { returnParameterFlag = true; } public String getMessageLocale() { return messageLocale; } public void setMessageLocale(String locale) { messageLocale = locale; } /** Get a node factory. */ public NodeFactory getNodeFactory() { return nodeFactory; } /** Set the node factory. */ public void setNodeFactory(NodeFactory nodeFactory) { this.nodeFactory = nodeFactory; } /** * Return a map of AST nodes that have already been printed during a * compiler phase, so as to be able to avoid printing a node more than once. * @see QueryTreeNode#treePrint(int) * @return the map */ public Map getPrintedObjectsMap() { if (printedObjectsMap == null) printedObjectsMap = new HashMap(); return printedObjectsMap; } public String generateColumnName() { return "_SQL_COL_" + generatedColumnNameIndex++; } public Set<SQLParserFeature> getFeatures() { return features; } public boolean hasFeature(SQLParserFeature feature) { return features.contains(feature); } public IdentifierCase getIdentifierCase() { return IdentifierCase.LOWER; } }