private void pushToStack(Collection<Integer> sourceCache) {
        PLStartSymbol currentSymbol = recognizeStartSymbol(sourceCache);
        if (Objects.nonNull(currentSymbol)
                && (currentSymbol == PLStartSymbol.WHILE || currentSymbol == PLStartSymbol.FOR)) {
            whileForLoopFlag = true;
        }
        if (Objects.nonNull(currentSymbol) && whileForLoopFlag && currentSymbol == PLStartSymbol.LOOP) {
            whileForLoopFlag = false;
            return;
        }
        // declare does not need to push into stack
        // because declare ... begin ... end block can be recognized by begin ... end
        // declare does not have an explicit ending
        if (Objects.nonNull(currentSymbol) && currentSymbol != PLStartSymbol.DECLARE) {
            SubPLLevel subPLLevel = new SubPLLevel(currentSymbol);
            // begin symbol does not need to push into stack when its wrapped in [create] function / procedure /
            // trigger / package [body] / type [body]
            // because in this case, it is always like `create function ... begin ... end` which can be
            // recognized by create function ... end
            if (!subPLStack.empty() && currentSymbol == PLStartSymbol.BEGIN) {
                switch (subPLStack.peek().startSymbol) {
                    case FUNCTION:
                    case PROCEDURE:
                    case TRIGGER:
                    case PACKAGE:
                    case PACKAGE_BODY:
                    case CREATE_FUNCTION:
                    case CREATE_PROCEDURE:
                    case CREATE_TRIGGER:
                    case CREATE_PACKAGE:
                    case CREATE_PACKAGE_BODY:
                    case CREATE_TYPE:
                    case CREATE_TYPE_BODY:
                        return;
                }
            } else if (!subPLStack.empty() && subPLStack.peek().startSymbol == PLStartSymbol.CREATE_TYPE
                    && (currentSymbol == PLStartSymbol.FUNCTION || currentSymbol == PLStartSymbol.PROCEDURE)) {
                // inherit matchMemberOrStatic from parent `CREATE_TYPE` sentence to sub Function or procedure
                // declare
                subPLLevel.matchMemberOrStatic = subPLStack.peek().matchMemberOrStatic;
                subPLStack.peek().matchMemberOrStatic = false;
            }
            subPLStack.push(subPLLevel);
        }
    }
<pre>
sub PL block start rule is different from the first PL block start
cache token types are stored in a queue because when we run into a new symbol,
we always add to the tail of queue and try to remove head of queue when it it full

when trying to match ALL_PL_START_PATTERNS, we always try to match the longer start pattern,
for example, match `create type body` but not `create type`
</pre>
chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlSplitter.java