All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.scalar.db.sql.util.CommandStatementParser Maven / Gradle / Ivy

There is a newer version: 3.14.0
Show newest version
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; } }