HSQLDBParser.java

/*-
 * ========================LICENSE_START=================================
 * flyway-database-hsqldb
 * ========================================================================
 * Copyright (C) 2010 - 2026 Red Gate Software Ltd
 * ========================================================================
 * Licensed 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.
 * =========================LICENSE_END==================================
 */
package org.flywaydb.database.hsqldb;

import org.flywaydb.core.api.configuration.Configuration;
import org.flywaydb.core.internal.parser.*;

import java.io.IOException;
import java.util.*;

public class HSQLDBParser extends Parser {
    /**
     * List of objects which can be dropped with IF EXISTS
     */
    private static final List<String> CONDITIONALLY_CREATABLE_OBJECTS = Arrays.asList("COLUMN",
        "CONSTRAINT",
        "FUNCTION",
        "INDEX",
        "PROCEDURE",
        "SCHEMA",
        "SEQUENCE",
        "TABLE",
        "VIEW");

    public HSQLDBParser(final Configuration configuration, final ParsingContext parsingContext) {
        super(configuration, parsingContext, 2);
    }

    @Override
    protected Set<String> getValidKeywords() {
        return new HashSet<>(Arrays.asList("ABS",
            "ALL",
            "ALLOCATE",
            "ALTER",
            "AND",
            "ANY",
            "ARE",
            "ARRAY",
            "AS",
            "ASENSITIVE",
            "ASYMMETRIC",
            "AT",
            "ATOMIC",
            "AUTHORIZATION",
            "AVG",
            "BEGIN",
            "BETWEEN",
            "BIGINT",
            "BINARY",
            "BLOB",
            "BOOLEAN",
            "BOTH",
            "BY",
            "CALL",
            "CALLED",
            "CARDINALITY",
            "CASCADED",
            "CASE",
            "CAST",
            "CEIL",
            "CEILING",
            "CHAR",
            "CHAR_LENGTH",
            "CHARACTER",
            "CHARACTER_LENGTH",
            "CHECK",
            "CLOB",
            "CLOSE",
            "COALESCE",
            "COLLATE",
            "COLLECT",
            "COLUMN",
            "COMMIT",
            "COMPARABLE",
            "CONDITION",
            "CONNECT",
            "CONSTRAINT",
            "CONVERT",
            "CORR",
            "CORRESPONDING",
            "COUNT",
            "COVAR_POP",
            "COVAR_SAMP",
            "CREATE",
            "CROSS",
            "CUBE",
            "CUME_DIST",
            "CURRENT",
            "CURRENT_CATALOG",
            "CURRENT_DATE",
            "CURRENT_DEFAULT_TRANSFORM_GROUP",
            "CURRENT_PATH",
            "CURRENT_ROLE",
            "CURRENT_SCHEMA",
            "CURRENT_TIME",
            "CURRENT_TIMESTAMP",
            "CURRENT_TRANSFORM_GROUP_FOR_TYPE",
            "CURRENT_USER",
            "CURSOR",
            "CYCLE",
            "DATE",
            "DAY",
            "DEALLOCATE",
            "DEC",
            "DECIMAL",
            "DECLARE",
            "DEFAULT",
            "DELETE",
            "DENSE_RANK",
            "DEREF",
            "DESCRIBE",
            "DETERMINISTIC",
            "DISCONNECT",
            "DISTINCT",
            "DO",
            "DOUBLE",
            "DROP",
            "DYNAMIC",
            "EACH",
            "ELEMENT",
            "ELSE",
            "ELSEIF",
            "END",
            "END_EXEC",
            "ESCAPE",
            "EVERY",
            "EXCEPT",
            "EXEC",
            "EXECUTE",
            "EXISTS",
            "EXIT",
            "EXP",
            "EXTERNAL",
            "EXTRACT",
            "FALSE",
            "FETCH",
            "FILTER",
            "FIRST_VALUE",
            "FLOAT",
            "FLOOR",
            "FOR",
            "FOREIGN",
            "FREE",
            "FROM",
            "FULL",
            "FUNCTION",
            "FUSION",
            "GET",
            "GLOBAL",
            "GRANT",
            "GROUP",
            "GROUPING",
            "HANDLER",
            "HAVING",
            "HOLD",
            "HOUR",
            "IDENTITY",
            "IF",
            "IN",
            "INDEX",
            "INDICATOR",
            "INNER",
            "INOUT",
            "INSENSITIVE",
            "INSERT",
            "INT",
            "INTEGER",
            "INTERSECT",
            "INTERSECTION",
            "INTERVAL",
            "INTO",
            "IS",
            "ITERATE",
            "JOIN",
            "LAG",
            "LANGUAGE",
            "LARGE",
            "LAST_VALUE",
            "LATERAL",
            "LEAD",
            "LEADING",
            "LEAVE",
            "LEFT",
            "LIKE",
            "LIKE_REGEX",
            "LN",
            "LOCAL",
            "LOCALTIME",
            "LOCALTIMESTAMP",
            "LOOP",
            "LOWER",
            "MATCH",
            "MAX",
            "MAX_CARDINALITY",
            "MEMBER",
            "MERGE",
            "METHOD",
            "MIN",
            "MINUTE",
            "MOD",
            "MODIFIES",
            "MODULE",
            "MONTH",
            "MULTISET",
            "NATIONAL",
            "NATURAL",
            "NCHAR",
            "NCLOB",
            "NEW",
            "NO",
            "NONE",
            "NORMALIZE",
            "NOT",
            "NTH_VALUE",
            "NTILE",
            "NULL",
            "NULLIF",
            "NUMERIC",
            "OCCURRENCES_REGEX",
            "OCTET_LENGTH",
            "OF",
            "OFFSET",
            "OLD",
            "ON",
            "ONLY",
            "OPEN",
            "OR",
            "ORDER",
            "OUT",
            "OUTER",
            "OVER",
            "OVERLAPS",
            "OVERLAY",
            "PARAMETER",
            "PARTITION",
            "PERCENT_RANK",
            "PERCENTILE_CONT",
            "PERCENTILE_DISC",
            "PERIOD",
            "POSITION",
            "POSITION_REGEX",
            "POWER",
            "PRECISION",
            "PREPARE",
            "PRIMARY",
            "PROCEDURE",
            "RANGE",
            "RANK",
            "READS",
            "REAL",
            "RECURSIVE",
            "REF",
            "REFERENCES",
            "REFERENCING",
            "REGR_AVGX",
            "REGR_AVGY",
            "REGR_COUNT",
            "REGR_INTERCEPT",
            "REGR_R2",
            "REGR_SLOPE",
            "REGR_SXX",
            "REGR_SXY",
            "REGR_SYY",
            "RELEASE",
            "REPEAT",
            "RESIGNAL",
            "RESULT",
            "RETURN",
            "RETURNS",
            "REVOKE",
            "RIGHT",
            "ROLLBACK",
            "ROLLUP",
            "ROW",
            "ROW_NUMBER",
            "ROWS",
            "SAVEPOINT",
            "SCHEMA",
            "SCOPE",
            "SCROLL",
            "SEARCH",
            "SECOND",
            "SELECT",
            "SENSITIVE",
            "SEQUENCE",
            "SESSION_USER",
            "SET",
            "SIGNAL",
            "SIMILAR",
            "SMALLINT",
            "SOME",
            "SPECIFIC",
            "SPECIFICTYPE",
            "SQL",
            "SQLEXCEPTION",
            "SQLSTATE",
            "SQLWARNING",
            "SQRT",
            "STACKED",
            "START",
            "STATIC",
            "STDDEV_POP",
            "STDDEV_SAMP",
            "SUBMULTISET",
            "SUBSTRING",
            "SUBSTRING_REGEX",
            "SUM",
            "SYMMETRIC",
            "SYSTEM",
            "SYSTEM_USER",
            "TABLE",
            "TABLESAMPLE",
            "THEN",
            "TIME",
            "TIMESTAMP",
            "TIMEZONE_HOUR",
            "TIMEZONE_MINUTE",
            "TO",
            "TRAILING",
            "TRANSLATE",
            "TRANSLATE_REGEX",
            "TRANSLATION",
            "TREAT",
            "TRIGGER",
            "TRIM",
            "TRIM_ARRAY",
            "TRUE",
            "TRUNCATE",
            "UESCAPE",
            "UNDO",
            "UNION",
            "UNIQUE",
            "UNKNOWN",
            "UNNEST",
            "UNTIL",
            "UPDATE",
            "UPPER",
            "USER",
            "USING",
            "VALUE",
            "VALUES",
            "VAR_POP",
            "VAR_SAMP",
            "VARBINARY",
            "VARCHAR",
            "VARYING",
            "VIEW",
            "WHEN",
            "WHENEVER",
            "WHERE",
            "WIDTH_BUCKET",
            "WINDOW",
            "WITH",
            "WITHIN",
            "WITHOUT",
            "WHILE",
            "YEAR"));
    }

    @Override
    protected int getLastKeywordIndex(final List<Token> tokens, final int endIndex) {
        for (int i = endIndex - 1; i >= 0; i--) {
            final String tokenText = tokens.get(i).getText();
            if (tokenText != null && getValidKeywords().contains(tokenText.toUpperCase(Locale.ENGLISH))) {
                return i;
            }
        }
        return super.getLastKeywordIndex(tokens, endIndex);
    }

    @Override
    protected boolean shouldAdjustBlockDepth(final ParserContext context, final List<Token> tokens, final Token token) {
        final String tokenText = token.getText();
        if (tokenText != null
            && getValidKeywords().contains(tokenText.toUpperCase(Locale.ENGLISH))
            && token.getParensDepth() == 0) {
            return true;
        }
        return super.shouldAdjustBlockDepth(context, tokens, token);
    }

    @Override
    protected void adjustBlockDepth(final ParserContext context,
        final List<Token> tokens,
        final Token keyword,
        final PeekingReader reader) throws IOException {
        final int lastKeywordIndex = getLastKeywordIndex(tokens);
        final Token previousKeyword = lastKeywordIndex >= 0 ? tokens.get(lastKeywordIndex) : null;
        final String keywordText = keyword.getText();
        final String previousKeywordText = previousKeyword != null ? previousKeyword.getText()
            .toUpperCase(Locale.ENGLISH) : "";

        if ("BEGIN".equalsIgnoreCase(keywordText) || ((("IF".equalsIgnoreCase(keywordText)
            && !CONDITIONALLY_CREATABLE_OBJECTS.contains(previousKeywordText))
            // excludes the IF in eg. CREATE TABLE IF EXISTS
            || "FOR".equalsIgnoreCase(keywordText) || "CASE".equalsIgnoreCase(keywordText) || "WHILE".equalsIgnoreCase(
            keywordText) || "LOOP".equalsIgnoreCase(keywordText) || "REPEAT".equalsIgnoreCase(keywordText))
            && previousKeyword != null
            && !"END".equalsIgnoreCase(previousKeywordText)
            && !"CURSOR".equalsIgnoreCase(previousKeywordText))) {  // DECLARE CURSOR FOR SELECT ... has no END
            context.increaseBlockDepth(keywordText);
        } else if (("EACH".equalsIgnoreCase(keywordText) || "SQLEXCEPTION".equalsIgnoreCase(keywordText))
            && previousKeyword != null
            && "FOR".equalsIgnoreCase(previousKeywordText)
            && context.getBlockDepth() > 0) {
            context.decreaseBlockDepth();
        } else if ("END".equalsIgnoreCase(keywordText) && context.getBlockDepth() > 0) {
            context.decreaseBlockDepth();
        }
    }
}