getStatementInfo method

TSQLStatementInfo getStatementInfo(
  1. String aSQL
)

Implementation

TSQLStatementInfo getStatementInfo(String aSQL) {
  // TParsePart / TPhraseSeparator / TKeyword (local types)
  const ppStart = 0, ppWith = 1, ppSelect = 2, ppTableName = 3,
      ppFrom = 4, ppWhere = 5, ppBogus = 8;
  const sepNone = 0, sepWhiteSpace = 1, sepComma = 2, sepComment = 3,
      sepParentheses = 4, sepDoubleQuote = 5, sepEnd = 6;
  const keywordNames = [
    'WITH', "SELECT", "INSERT", "UPDATE", "DELETE", "FROM", "JOIN",
    "WHERE", "GROUP", "ORDER", "UNION", "ROWS", "LIMIT", ""
  ];
  const kwWITH = 0, kwSELECT = 1, kwINSERT = 2, kwUPDATE = 3,
      kwDELETE = 4, kwFROM = 5, kwJOIN = 6, kwWHERE = 7, kwGROUP = 8,
      kwORDER = 9, kwUNION = 10, kwROWS = 11, kwLIMIT = 12,
      kwUnknown = 13;

  final result = TSQLStatementInfo();
  var parsePart = ppStart;

  final len = aSQL.length;
  String charAt(int i) => i >= len ? '\x00' : aSQL[i];

  var currentP = -1;
  var phraseP = 0;
  var pStatementPart = 0;
  var breakLoop = false;

  do {
    currentP++;
    var savedP = currentP;
    int separator;

    final c = charAt(currentP);
    if (c == ' ' || (c.codeUnitAt(0) >= 9 && c.codeUnitAt(0) <= 13)) {
      separator = sepWhiteSpace;
    } else if (c == ',') {
      separator = sepComma;
    } else if (c == '\x00' || c == ';') {
      separator = sepEnd;
    } else if (c == '(') {
      separator = sepParentheses;
      // skip everything between brackets, since it could be a
      // sub-select, and further nothing between brackets could be
      // interesting for the parser. (original comment kept as-is)
      var bracketCount = 1;
      do {
        currentP++;
        if (charAt(currentP) == '(') {
          bracketCount++;
        } else if (charAt(currentP) == ')') {
          bracketCount--;
        }
      } while (charAt(currentP) != '\x00' && bracketCount != 0);
      if (charAt(currentP) != '\x00') currentP++;
    } else if (c == '"' || c == '`') {
      final p = TIntRef(currentP);
      if (skipComments(aSQL, p,
          connOptions.contains(TConnOption.sqEscapeSlash),
          connOptions.contains(TConnOption.sqEscapeRepeat))) {
        separator = sepDoubleQuote;
        currentP = p.value;
      } else {
        separator = sepNone;
      }
    } else {
      final p = TIntRef(currentP);
      if (skipComments(aSQL, p,
          connOptions.contains(TConnOption.sqEscapeSlash),
          connOptions.contains(TConnOption.sqEscapeRepeat))) {
        separator = sepComment;
        currentP = p.value;
      } else {
        separator = sepNone;
      }
    }

    if (separator != sepNone) {
      if (currentP > savedP && savedP > phraseP) {
        currentP = savedP; // there is something before comment or left
        // parenthesis or double quote (original comment kept as-is)
      }

      if ((separator == sepWhiteSpace || separator == sepComment) &&
          savedP == phraseP) {
        phraseP = currentP; // skip comments (but not parentheses) and
        // white spaces (original comment kept as-is)
      }

      if (currentP - phraseP > 0 || separator == sepEnd) {
        final s = aSQL.substring(
            phraseP, currentP > len ? len : currentP);

        var keyword = kwUnknown;
        for (var k = 0; k < keywordNames.length; k++) {
          if (s.toUpperCase() == keywordNames[k]) {
            keyword = k;
            break;
          }
        }

        switch (parsePart) {
          case ppStart:
            result.statementType = strToStatementType(s);
            if (keyword == kwWITH) {
              parsePart = ppWith;
            } else if (keyword == kwSELECT) {
              parsePart = ppSelect;
            } else {
              breakLoop = true;
            }
            break;
          case ppWith:
            // WITH [RECURSIVE] CTE_name [(column_names)] AS
            // (CTE_query_definition) [, ...]
            //  { SELECT | INSERT | UPDATE | DELETE } ... (original comment kept as-is)
            switch (keyword) {
              case kwSELECT:
                result.statementType = TStatementType.stSelect;
                break;
              case kwINSERT:
                result.statementType = TStatementType.stInsert;
                break;
              case kwUPDATE:
                result.statementType = TStatementType.stUpdate;
                break;
              case kwDELETE:
                result.statementType = TStatementType.stDelete;
                break;
            }
            if (result.statementType != TStatementType.stUnknown) {
              breakLoop = true;
            }
            break;
          case ppSelect:
            if (keyword == kwFROM) {
              parsePart = ppTableName;
            }
            break;
          case ppTableName:
            // Meta-data requests are never updateable and select
            // statements from more than one table and/or derived
            // tables are also not updateable (original comment kept as-is)
            if (separator == sepWhiteSpace ||
                separator == sepComment ||
                separator == sepDoubleQuote ||
                separator == sepEnd) {
              result.tableName = result.tableName + s;
              result.updateable = true;
            }
            // compound delimited classifier like:
            // "schema name"."table name" (original comment kept as-is)
            final nc = charAt(currentP);
            if (nc != '.' && nc != '"') {
              parsePart = ppFrom;
            }
            break;
          case ppFrom:
            if ((keyword == kwWHERE ||
                    keyword == kwGROUP ||
                    keyword == kwORDER ||
                    keyword == kwLIMIT ||
                    keyword == kwROWS) ||
                separator == sepEnd) {
              if (keyword == kwWHERE) {
                parsePart = ppWhere;
              } else if (keyword == kwGROUP || keyword == kwORDER) {
                parsePart = ppBogus;
              } else {
                parsePart = ppBogus;
              }
              result.whereStartPos = phraseP + 1; // PhraseP-PSQL+1
              pStatementPart = currentP;
            } else if (keyword == kwJOIN ||
                separator == sepComma ||
                separator == sepParentheses) {
              // joined table or user_defined_function (...) (original comment kept as-is)
              result.tableName = '';
              result.updateable = false;
            }
            break;
          case ppWhere:
            if ((keyword == kwGROUP ||
                    keyword == kwORDER ||
                    keyword == kwLIMIT ||
                    keyword == kwROWS) ||
                separator == sepEnd) {
              parsePart = ppBogus;
              result.whereStartPos = pStatementPart;
              if (separator == sepEnd) {
                result.whereStopPos = currentP + 1;
              } else {
                result.whereStopPos = phraseP + 1;
              }
            } else if (keyword == kwUNION) {
              parsePart = ppBogus;
              result.updateable = false;
            }
            break;
        }
      }
      if (separator == sepComment ||
          separator == sepParentheses ||
          separator == sepDoubleQuote) {
        currentP--;
      }
      phraseP = currentP + 1;
    }
  } while (!breakLoop && charAt(currentP) != '\x00');
  return result;
}