/*
* Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software;Designed and Developed mainly by many Chinese
* opensource volunteers. you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 2 only, as published by the
* Free Software Foundation.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Any questions about this component can be directed to it's project Web address
* https://code.google.com/p/opencloudb/.
*
*/
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;
}
}