com.scalar.db.sql.util.CommandStatementParser Maven / Gradle / Ivy
package com.scalar.db.sql.util;
import com.scalar.db.sql.TransactionMode;
import com.scalar.db.sql.statement.CommandStatement;
import com.scalar.db.sql.statement.builder.StatementBuilder;
import java.util.List;
import java.util.Locale;
import javax.annotation.Nullable;
public final class CommandStatementParser {
private CommandStatementParser() {}
@Nullable
public static CommandStatement parse(String sql) {
// we specify 6 as the limit parameter in the tokenize method because we expected at most 5
// tokens for the command statements. So we don't need to tokenize the sql more than 6 tokens.
List tokens = Tokenizer.tokenize(sql, 6);
if (tokens.isEmpty() || tokens.size() > 5) {
return null;
}
// if the last element is a semicolon, remove it
int lastIndex = tokens.size() - 1;
if (tokens.get(lastIndex).equals(";")) {
tokens.remove(lastIndex);
}
if (tokens.isEmpty()) {
return null;
}
if (tokens.get(0).equalsIgnoreCase("BEGIN")) {
// the expected BEGIN command syntax is as follows:
// BEGIN
if (tokens.size() != 1) {
return null;
}
return StatementBuilder.begin().build();
} else if (tokens.get(0).equalsIgnoreCase("START")) {
// the expected START TRANSACTION command syntax is as follows:
// START TRANSACTION
if (tokens.size() != 2) {
return null;
}
if (!tokens.get(1).equalsIgnoreCase("TRANSACTION")) {
return null;
}
return StatementBuilder.startTransaction().build();
} else if (tokens.get(0).equalsIgnoreCase("JOIN")) {
// the expected JOIN command syntax is as follows:
// JOIN ''
if (tokens.size() != 2) {
return null;
}
// transaction ID
if (!isStringLiteral(tokens.get(1))) {
return null;
}
return StatementBuilder.join(getStringLiteralValue(tokens.get(1))).build();
} else if (tokens.get(0).equalsIgnoreCase("RESUME")) {
// the expected RESUME command syntax is as follows:
// RESUME ''
if (tokens.size() != 2) {
return null;
}
// transaction ID
if (!isStringLiteral(tokens.get(1))) {
return null;
}
return StatementBuilder.resume(getStringLiteralValue(tokens.get(1))).build();
} else if (tokens.get(0).equalsIgnoreCase("SUSPEND")) {
// the expected SUSPEND command syntax is as follows:
// SUSPEND
if (tokens.size() != 1) {
return null;
}
return StatementBuilder.suspend().build();
} else if (tokens.get(0).equalsIgnoreCase("PREPARE")) {
// the expected PREPARE command syntax is as follows:
// PREPARE
if (tokens.size() != 1) {
return null;
}
return StatementBuilder.prepare().build();
} else if (tokens.get(0).equalsIgnoreCase("VALIDATE")) {
// the expected VALIDATE command syntax is as follows:
// VALIDATE
if (tokens.size() != 1) {
return null;
}
return StatementBuilder.validate().build();
} else if (tokens.get(0).equalsIgnoreCase("COMMIT")) {
// the expected COMMIT command syntax is as follows:
// COMMIT
if (tokens.size() != 1) {
return null;
}
return StatementBuilder.commit().build();
} else if (tokens.get(0).equalsIgnoreCase("ROLLBACK")) {
// the expected ROLLBACK command syntax is as follows:
// ROLLBACK
if (tokens.size() != 1) {
return null;
}
return StatementBuilder.rollback().build();
} else if (tokens.get(0).equalsIgnoreCase("ABORT")) {
// the expected ABORT command syntax is as follows:
// ABORT
if (tokens.size() != 1) {
return null;
}
return StatementBuilder.abort().build();
} else if (tokens.get(0).equalsIgnoreCase("USE")) {
// the expected USE command syntax is as follows:
// USE
if (tokens.size() != 2) {
return null;
}
if (!isObjectName(tokens.get(1))) {
return null;
}
return StatementBuilder.use(getObjectNameValue(tokens.get(1))).build();
} else if (tokens.get(0).equalsIgnoreCase("SET")) {
// the expected SET MODE command syntax is as follows:
// SET MODE transaction_mode
// transaction_mode: TRANSACTION | TWO_PHASE_COMMIT_TRANSACTION
if (tokens.size() != 3) {
return null;
}
if (!tokens.get(1).equalsIgnoreCase("MODE")) {
return null;
}
if (!tokens.get(2).equalsIgnoreCase("TRANSACTION")
&& !tokens.get(2).equalsIgnoreCase("TWO_PHASE_COMMIT_TRANSACTION")) {
return null;
}
return StatementBuilder.setMode(
TransactionMode.valueOf(tokens.get(2).toUpperCase(Locale.ROOT)))
.build();
} else if (tokens.get(0).equalsIgnoreCase("SHOW")) {
if (tokens.size() < 2) {
return null;
}
if (tokens.get(1).equalsIgnoreCase("NAMESPACES")) {
// the expected SHOW NAMESPACES command syntax is as follows:
// SHOW NAMESPACES
if (tokens.size() != 2) {
return null;
}
return StatementBuilder.showNamespaces().build();
} else if (tokens.get(1).equalsIgnoreCase("TABLES")) {
// the expected SHOW TABLES command syntax is as follows:
// SHOW TABLES [FROM ]
if (tokens.size() != 2 && tokens.size() != 4) {
return null;
}
if (!tokens.get(1).equalsIgnoreCase("TABLES")) {
return null;
}
if (tokens.size() == 2) {
// SHOW TABLES
return StatementBuilder.showTables().build();
} else {
// SHOW TABLES FROM
if (!tokens.get(2).equalsIgnoreCase("FROM")) {
return null;
}
// the namespace name
if (!isObjectName(tokens.get(3))) {
return null;
}
return StatementBuilder.showTables().from(getObjectNameValue(tokens.get(3))).build();
}
}
return null;
} else if (tokens.get(0).equalsIgnoreCase("DESCRIBE")
|| tokens.get(0).equalsIgnoreCase("DESC")) {
// the expected SHOW TABLES command syntax is as follows:
// DESCRIBE [.]
// DESC [.]
if (tokens.size() != 2 && tokens.size() != 4) {
return null;
}
if (tokens.size() == 2) {
// without namespace
if (!isObjectName(tokens.get(1))) {
return null;
}
return StatementBuilder.describe(getObjectNameValue(tokens.get(1))).build();
} else {
// with namespace
if (!isObjectName(tokens.get(1))) {
return null;
}
if (!tokens.get(2).equals(".")) {
return null;
}
if (!isObjectName(tokens.get(3))) {
return null;
}
return StatementBuilder.describe(
getObjectNameValue(tokens.get(1)), getObjectNameValue(tokens.get(3)))
.build();
}
}
return null;
}
private static boolean isStringLiteral(String token) {
if (token.length() < 2) {
return false;
}
// The first and last characters are single quotes
return token.charAt(0) == '\'' && token.charAt(token.length() - 1) == '\'';
}
private static String getStringLiteralValue(String stringLiteral) {
assert isStringLiteral(stringLiteral);
// remove the single quotes at the beginning and the end
return stringLiteral.substring(1, stringLiteral.length() - 1);
}
private static boolean isObjectName(String token) {
if (token.isEmpty()) {
return false;
}
// If the first character is a double quote, the last character should also be a double quote
if (token.charAt(0) == '"') {
return token.charAt(token.length() - 1) == '"';
}
// The first character should match "[a-zA-Z]"
char firstChar = token.charAt(0);
if (!Character.isLowerCase(firstChar) && !Character.isUpperCase(firstChar)) {
return false;
}
// The rest of the characters should match "[A-Za-z0-9_$]"
for (int i = 1; i < token.length(); i++) {
char ch = token.charAt(i);
if (!Character.isLowerCase(ch)
&& !Character.isUpperCase(ch)
&& !Character.isDigit(ch)
&& ch != '_'
&& ch != '$') {
return false;
}
}
return true;
}
private static String getObjectNameValue(String objectName) {
assert isObjectName(objectName);
if (objectName.charAt(0) == '"') {
// remove the double quotes at the beginning and the end
return objectName.substring(1, objectName.length() - 1);
}
return objectName;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy