blob: 894aebb64f5c60baf39ded9032304e98dc632312 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.ignite.internal.sql;
import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
import org.apache.ignite.internal.sql.command.SqlQualifiedName;
import org.apache.ignite.internal.util.typedef.F;
import static org.apache.ignite.internal.sql.SqlKeyword.EXISTS;
import static org.apache.ignite.internal.sql.SqlKeyword.IF;
import static org.apache.ignite.internal.sql.SqlKeyword.NOT;
/**
* Parser utility methods.
*/
public class SqlParserUtils {
/**
* Parse IF EXISTS statement.
*
* @param lex Lexer.
* @return {@code True} if statement is found.
*/
public static boolean parseIfExists(SqlLexer lex) {
SqlLexerToken token = lex.lookAhead();
if (matchesKeyword(token, IF)) {
lex.shift();
skipIfMatchesKeyword(lex, EXISTS);
return true;
}
return false;
}
/**
* Parse IF NOT EXISTS statement.
*
* @param lex Lexer.
* @return {@code True} if statement is found.
*/
public static boolean parseIfNotExists(SqlLexer lex) {
SqlLexerToken token = lex.lookAhead();
if (matchesKeyword(token, IF)) {
lex.shift();
skipIfMatchesKeyword(lex, NOT);
skipIfMatchesKeyword(lex, EXISTS);
return true;
}
return false;
}
/**
* Skip comma or right parenthesis.
*
* @param lex Lexer.
* @return {@code True} if right parenthesis is found.
*/
public static boolean skipCommaOrRightParenthesis(SqlLexer lex) {
if (lex.shift()) {
switch (lex.tokenType()) {
case COMMA:
return false;
case PARENTHESIS_RIGHT:
return true;
}
}
throw errorUnexpectedToken(lex, ",", ")");
}
/**
* Parse integer value (positive or negative).
*
* @param lex Lexer.
* @return Integer value.
*/
public static int parseInt(SqlLexer lex) {
int sign = 1;
if (lex.lookAhead().tokenType() == SqlLexerTokenType.MINUS) {
sign = -1;
lex.shift();
}
if (lex.shift() && lex.tokenType() == SqlLexerTokenType.DEFAULT) {
try {
long val = sign * Long.parseLong(lex.token());
if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE)
return (int)val;
// Fall through.
}
catch (NumberFormatException e) {
// Fall through.
}
}
throw errorUnexpectedToken(lex, "[integer]");
}
/**
* Parse boolean parameter value based on presence of tokens 1, 0, ON, OFF. Not that this is not
* and is not intended to be routine for parsing a boolean literal from TRUE/FALSE.
* @param lex Lexer.
* @return Boolean parameter value.
*/
public static boolean parseBoolean(SqlLexer lex) {
if (lex.shift() && lex.tokenType() == SqlLexerTokenType.DEFAULT) {
switch (lex.token()) {
case SqlKeyword.ON:
case "1":
return true;
case SqlKeyword.OFF:
case "0":
return false;
}
}
throw errorUnexpectedToken(lex, SqlKeyword.ON, SqlKeyword.OFF, "1", "0");
}
/**
* Process name.
*
* @param lex Lexer.
* @param additionalExpTokens Additional expected tokens in case of error.
* @return Name.
*/
public static String parseIdentifier(SqlLexer lex, String... additionalExpTokens) {
if (lex.shift() && isValidIdentifier(lex))
return lex.token();
throw errorUnexpectedToken(lex, "[identifier]", additionalExpTokens);
}
/**
* Process name.
*
* @param lex Lexer.
* @param additionalExpTokens Additional expected tokens in case of error.
* @return Name.
*/
public static String parseUsername(SqlLexer lex, String... additionalExpTokens) {
if (lex.shift() && isValidIdentifier(lex))
return lex.token();
throw errorUnexpectedToken(lex, "[username identifier]", additionalExpTokens);
}
/**
* Process name.
*
* @param lex Lexer.
* @param additionalExpTokens Additional expected tokens in case of error.
* @return Name.
*/
public static String parseString(SqlLexer lex, String... additionalExpTokens) {
if (lex.shift() && lex.tokenType() == SqlLexerTokenType.STRING)
return lex.token();
throw errorUnexpectedToken(lex, "[string]", additionalExpTokens);
}
/**
* Process qualified name.
*
* @param lex Lexer.
* @param additionalExpTokens Additional expected tokens in case of error.
* @return Qualified name.
*/
public static SqlQualifiedName parseQualifiedIdentifier(SqlLexer lex, String... additionalExpTokens) {
if (lex.shift() && isValidIdentifier(lex)) {
SqlQualifiedName res = new SqlQualifiedName();
String first = lex.token();
SqlLexerToken nextTok = lex.lookAhead();
if (nextTok.tokenType() == SqlLexerTokenType.DOT) {
lex.shift();
String second = parseIdentifier(lex);
return res.schemaName(first).name(second);
}
else
return res.name(first);
}
throw errorUnexpectedToken(lex, "[qualified identifier]", additionalExpTokens);
}
/**
* Check if token is identifier.
*
* @param token Token.
* @return {@code True} if we are standing on possible identifier.
*/
public static boolean isValidIdentifier(SqlLexerToken token) {
switch (token.tokenType()) {
case DEFAULT:
char c = token.tokenFirstChar();
return ((c >= 'A' && c <= 'Z') || c == '_') && !SqlKeyword.isKeyword(token.token());
case QUOTED:
return true;
default:
return false;
}
}
/**
* Check if current lexer token matches expected.
*
* @param token Token..
* @param expKeyword Expected keyword.
* @return {@code True} if matches.
*/
public static boolean matchesKeyword(SqlLexerToken token, String expKeyword) {
return token.tokenType() == SqlLexerTokenType.DEFAULT && expKeyword.equals(token.token());
}
/**
* Skip token if it matches expected keyword.
*
* @param lex Lexer.
* @param expKeyword Expected keyword.
*/
public static void skipIfMatchesKeyword(SqlLexer lex, String expKeyword) {
if (lex.shift() && matchesKeyword(lex, expKeyword))
return;
throw errorUnexpectedToken(lex, expKeyword);
}
/**
* Skip token if it matches expected keyword by using lookahead.
* If next token is not what we expect, no shift is done.
*
* @param lex Lexer.
* @param expKeyword Expected keyword.
* @return {@code true} In case token mathes, {@code false} otherwise.
*/
public static boolean skipIfMatchesOptionalKeyword(SqlLexer lex, String expKeyword) {
SqlLexerToken nextTok = lex.lookAhead();
if (matchesKeyword(nextTok, expKeyword)) {
lex.shift();
return true;
}
return false;
}
/**
* Skip next token if it matches expected type.
*
* @param lex Lexer.
* @param tokenTyp Expected token type.
*/
public static void skipIfMatches(SqlLexer lex, SqlLexerTokenType tokenTyp) {
if (lex.shift() && F.eq(lex.tokenType(), tokenTyp))
return;
throw errorUnexpectedToken(lex, tokenTyp.asString());
}
/**
* Create parse exception referring to current lexer position.
*
* @param token Token.
* @param msg Message.
* @return Exception.
*/
public static SqlParseException error(SqlLexerToken token, String msg) {
return error0(token, IgniteQueryErrorCode.PARSING, msg);
}
/**
* Create parse exception referring to current lexer position.
*
* @param token Token.
* @param code Error code.
* @param msg Message.
* @return Exception.
*/
private static SqlParseException error0(SqlLexerToken token, int code, String msg) {
return new SqlParseException(token.sql(), token.tokenPosition(), code, msg);
}
/**
* Create generic parse exception due to unexpected token.
*
* @param token Token.
* @return Exception.
*/
public static SqlParseException errorUnexpectedToken(SqlLexerToken token) {
return errorUnexpectedToken0(token);
}
/**
* Throw unsupported token exception if passed keyword is found.
*
* @param token Token.
* @param keyword Keyword.
*/
public static void errorUnsupportedIfMatchesKeyword(SqlLexerToken token, String keyword) {
if (matchesKeyword(token, keyword))
throw errorUnsupported(token);
}
/**
* Throw unsupported token exception if one of passed keywords is found.
*
* @param token Token.
* @param keywords Keywords.
*/
public static void errorUnsupportedIfMatchesKeyword(SqlLexerToken token, String... keywords) {
if (F.isEmpty(keywords))
return;
for (String keyword : keywords)
errorUnsupportedIfMatchesKeyword(token, keyword);
}
/**
* Error on unsupported keyword.
*
* @param token Token.
* @return Error.
*/
public static SqlParseException errorUnsupported(SqlLexerToken token) {
return error0(token, IgniteQueryErrorCode.UNSUPPORTED_OPERATION,
"Unsupported keyword: \"" + token.token() + "\"");
}
/**
* Create generic parse exception due to unexpected token.
*
* @param lex Lexer.
* @param expToken Expected token.
* @return Exception.
*/
public static SqlParseException errorUnexpectedToken(SqlLexer lex, String expToken) {
return errorUnexpectedToken0(lex, expToken);
}
/**
* Create generic parse exception due to unexpected token.
*
* @param token Token.
* @param firstExpToken First expected token.
* @param expTokens Additional expected tokens (if any).
* @return Exception.
*/
public static SqlParseException errorUnexpectedToken(SqlLexerToken token, String firstExpToken,
String... expTokens) {
if (F.isEmpty(expTokens))
return errorUnexpectedToken0(token, firstExpToken);
else {
String[] expTokens0 = new String[expTokens.length + 1];
expTokens0[0] = firstExpToken;
System.arraycopy(expTokens, 0, expTokens0, 1, expTokens.length);
throw errorUnexpectedToken0(token, expTokens0);
}
}
/**
* Create generic parse exception due to unexpected token.
*
* @param token Token.
* @param expTokens Expected tokens (if any).
* @return Exception.
*/
@SuppressWarnings("StringConcatenationInsideStringBufferAppend")
private static SqlParseException errorUnexpectedToken0(SqlLexerToken token, String... expTokens) {
String token0 = token.token();
StringBuilder msg = new StringBuilder(
token0 == null ? "Unexpected end of command" : "Unexpected token: \"" + token0 + "\"");
if (!F.isEmpty(expTokens)) {
msg.append(" (expected: ");
boolean first = true;
for (String expToken : expTokens) {
if (first)
first = false;
else
msg.append(", ");
msg.append("\"" + expToken + "\"");
}
msg.append(")");
}
throw error(token, msg.toString());
}
/**
* Private constructor.
*/
private SqlParserUtils() {
// No-op.
}
}