001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 the ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package eu.cqse.check.framework.shallowparser.languages.hanasqlscript;
018
019import static eu.cqse.check.framework.scanner.ETokenType.*;
020import static eu.cqse.check.framework.shallowparser.languages.hanasqlscript.HanaSQLScriptShallowParser.EHanaSQLScriptParserStates.METHOD_STATEMENTS;
021import static eu.cqse.check.framework.shallowparser.languages.hanasqlscript.HanaSQLScriptShallowParser.EHanaSQLScriptParserStates.OUTSIDE_METHODS;
022
023import java.util.EnumSet;
024
025import org.conqat.lib.commons.region.Region;
026
027import eu.cqse.check.framework.scanner.ETokenType;
028import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
029import eu.cqse.check.framework.shallowparser.framework.RecognizerBase;
030import eu.cqse.check.framework.shallowparser.framework.ShallowParserBase;
031
032/**
033 * Parser for SAP Hana SQLScript.
034 */
035public class HanaSQLScriptShallowParser
036                extends ShallowParserBase<HanaSQLScriptShallowParser.EHanaSQLScriptParserStates> {
037
038        /** States common to SQL Parsers. */
039        public static enum EHanaSQLScriptParserStates {
040                /**
041                 * The highest level state. Outside procedures, functions or triggers.
042                 * Applies to SQL files without these method structures.
043                 */
044                OUTSIDE_METHODS,
045
046                /**
047                 * Simple and block statements inside a trigger, procedure or function.
048                 */
049                METHOD_STATEMENTS
050        }
051
052        /**
053         * In Hana SQLScript language, double-quoted keywords may be used as
054         * identifiers
055         */
056        private static final EnumSet<ETokenType> HANA_SQLSCRIPT_IDENTIFIERS = EnumSet.of(IDENTIFIER, ABS, ALL, ALLOCATE,
057                        ALTER, AND, ANY, ARE, ARRAY, ARRAY_AGG, ARRAY_MAX_CARDINALITY, AS, ASENSITIVE, ASYMMETRIC, AT, ATOMIC,
058                        AUTHORIZATION, AVG, BEGIN, BEGIN_FRAME, BEGIN_PARTITION, BETWEEN, BIGINT, BINARY, BLOB, BOOLEAN, BOTH, BY,
059                        CALL, CALLED, CARDINALITY, CASCADED, CASE, CAST, CEIL, CEILING, CHAR, CHAR_LENGTH, CHARACTER,
060                        CHARACTER_LENGTH, CHECK, CLASSIFIER, CLOB, CLOSE, COALESCE, COLLATE, COLLECT, COLUMN, COMMIT, CONDITION,
061                        CONNECT, CONSTRAINT, CONTAINS, CONVERT, CORR, CORRESPONDING, COUNT, COVAR_POP, COVAR_SAMP, CREATE, CROSS,
062                        CUBE, CUME_DIST, CURRENT, CURRENT_CATALOG, CURRENT_DATE, CURRENT_DEFAULT_TRANSFORM_GROUP, CURRENT_PATH,
063                        CURRENT_ROLE, CURRENT_ROW, CURRENT_SCHEMA, CURRENT_TIME, CURRENT_TIMESTAMP,
064                        CURRENT_TRANSFORM_GROUP_FOR_TYPE, CURRENT_USER, CURSOR, CYCLE, DATE, DAY, DEALLOCATE, DEC, DECIMAL, DECLARE,
065                        DEFAULT, DEFINE, DELETE, DENSE_RANK, DEREF, DESCRIBE, DETERMINISTIC, DISCONNECT, DISTINCT, DOUBLE, DROP,
066                        DYNAMIC, EACH, ELEMENT, ELSE, EMPTY, END, END_FRAME, END_PARTITION, EQUALS, ESCAPE, EVERY, EXCEPT, EXEC,
067                        EXECUTE, EXISTS, EXP, EXTERNAL, EXTRACT, FALSE, FETCH, FILTER, FIRST_VALUE, FLOAT, FLOOR, FOR, FOREIGN,
068                        FRAME_ROW, FREE, FROM, FULL, FUNCTION, FUSION, GET, GLOBAL, GRANT, GROUP, GROUPING, GROUPS, HAVING, HOLD,
069                        HOUR, IDENTITY, IN, INDICATOR, INITIAL, INNER, INOUT, INSENSITIVE, INSERT, INT, INTEGER, INTERSECT,
070                        INTERSECTION, INTERVAL, INTO, IS, JOIN, LAG, LANGUAGE, LARGE, LAST_VALUE, LATERAL, LEAD, LEADING, LEFT,
071                        LIKE, LIKE_REGEX, LN, LOCAL, LOCALTIME, LOCALTIMESTAMP, LOWER, MATCH, MATCH_NUMBER, MATCH_RECOGNIZE,
072                        MATCHES, MAX, MEMBER, MERGE, METHOD, MIN, MINUTE, MOD, MODIFIES, MODULE, MONTH, MULTISET, NATIONAL, NATURAL,
073                        NCHAR, NCLOB, NEW, NO, NONE, NORMALIZE, NOT, NTH_VALUE, NTILE, NULL, NULLIF, NUMERIC, OCTET_LENGTH,
074                        OCCURRENCES_REGEX, OF, OFFSET, OLD, OMIT, ON, ONE, ONLY, OPEN, OR, ORDER, OUT, OUTER, OVER, OVERLAPS,
075                        OVERLAY, PARAMETER, PARTITION, PATTERN, PER, PERCENT, PERCENT_RANK, PERCENTILE_CONT, PERCENTILE_DISC,
076                        PERIOD, PORTION, POSITION, POSITION_REGEX, POWER, PRECEDES, PRECISION, PREPARE, PRIMARY, PROCEDURE, RANGE,
077                        RANK, READS, REAL, RECOVER, RECURSIVE, REF, REFERENCES, REFERENCING, REGR_AVGX, REGR_AVGY, REGR_COUNT,
078                        REGR_INTERCEPT, REGR_R2, REGR_SLOPE, REGR_SXX, REGR_SXY, REGR_SYY, RELEASE, RESULT, RETURN, RETURNS, REVOKE,
079                        RIGHT, ROLLBACK, ROLLUP, ROW, ROW_NUMBER, ROWS, RUNNING, SAVEPOINT, SCOPE, SCROLL, SEARCH, SECOND, SEEK,
080                        SELECT, SENSITIVE, SESSION_USER, SET, SHOW, SKIP, SIMILAR, SMALLINT, SOME, SPECIFIC, SPECIFICTYPE, SQL,
081                        SQLEXCEPTION, SQLSTATE, SQLWARNING, SQRT, START, STATIC, STDDEV_POP, STDDEV_SAMP, SUBMULTISET, SUBSET,
082                        SUBSTRING, SUBSTRING_REGEX, SUCCEEDS, SUM, SYMMETRIC, SYSTEM, SYSTEM_TIME, SYSTEM_USER, TABLE, TABLESAMPLE,
083                        THEN, TIME, TIMESTAMP, TIMEZONE_HOUR, TIMEZONE_MINUTE, TO, TRAILING, TRANSLATE, TRANSLATE_REGEX,
084                        TRANSLATION, TREAT, TRIGGER, TRUNCATE, TRIM, TRIM_ARRAY, TRUE, UESCAPE, UNION, UNIQUE, UNKNOWN, UNLOAD,
085                        UNNEST, UPDATE, UPPER, UPSERT, USER, USING, VALUE, VALUES, VALUE_OF, VAR_POP, VAR_SAMP, VARBINARY,
086                        VARCHAR, VARYING, VERSIONING, WHEN, WHENEVER, WHERE, WIDTH_BUCKET, WINDOW, WITH, WITHIN, WITHOUT, YEAR);
087
088        /**
089         * Hana SQLScript Constructor
090         */
091        public HanaSQLScriptShallowParser() {
092                super(EHanaSQLScriptParserStates.class, EHanaSQLScriptParserStates.OUTSIDE_METHODS);
093
094                createMetaRules();
095                createMethodRules();
096                createStatementRules();
097        }
098
099        /**
100         * Create parser rules for generating meta shallow entities. Meta entities
101         * do not qualify as statements on their own.
102         */
103        private void createMetaRules() {
104                // SET Statements ...
105                // ... SET HISTORY SESSION
106                inState(OUTSIDE_METHODS).sequence(SET, HISTORY, SESSION, TO).createNode(EShallowEntityType.META, "set", 1)
107                                .skipTo(SEMICOLON).endNode();
108                // ... session variables
109                RecognizerBase<EHanaSQLScriptParserStates> otherSetStatementsMatch = inState(OUTSIDE_METHODS).sequence(SET,
110                                HANA_SQLSCRIPT_IDENTIFIERS);
111                RecognizerBase<EHanaSQLScriptParserStates> sessionVarMatch = otherSetStatementsMatch
112                                .repeated(HANA_SQLSCRIPT_IDENTIFIERS).sequence(EQUAL)
113                                .createNode(EShallowEntityType.ATTRIBUTE, "session variable", 1);
114                sessionVarMatch.skipTo(SEMICOLON).endNode();
115                RecognizerBase<EHanaSQLScriptParserStates> schemaAndOtherMatch = otherSetStatementsMatch
116                                .repeated(HANA_SQLSCRIPT_IDENTIFIERS).sequence(SEMICOLON).createNode(EShallowEntityType.META, "set", 1);
117                schemaAndOtherMatch.endNode();
118
119                createRulesForSQLStatements();
120        }
121
122        /**
123         * Create rules to match SQL statements that may be found in any state.
124         */
125        private void createRulesForSQLStatements() {
126                // SQL
127                inAnyState()
128                                .sequence(EnumSet.of(ALTER, COMMIT, DELETE, GRANT, LOCK, ROLLBACK, SAVEPOINT, SELECT, DROP, MERGE,
129                                                UPDATE, TRUNCATE, REVOKE, RENAME, LOAD, IMPORT, EXPORT, UPSERT, REPLACE, UNLOAD, BACKUP,
130                                                RECOVER))
131                                .createNode(EShallowEntityType.STATEMENT, "SQL", 0).skipTo(SEMICOLON).endNode();
132                // ... INSERT INTO statement
133                RecognizerBase<EHanaSQLScriptParserStates> insertMatch = inAnyState().sequence(INSERT, INTO)
134                                .createNode(EShallowEntityType.STATEMENT, "SQL", 0);
135                RecognizerBase<EHanaSQLScriptParserStates> matchInsertTableName = matchTableNameForRule(insertMatch);
136                matchInsertTableName.skipTo(SEMICOLON).endNode();
137                // ... transactions
138                inAnyState().sequence(SET, TRANSACTION).createNode(EShallowEntityType.STATEMENT, "SQL", 1).skipTo(SEMICOLON)
139                                .endNode();
140                // ... CREATE
141                createRuleForSQLCreate();
142        }
143
144        /**
145         * Creates rules for SQL CREATE statements.
146         */
147        private void createRuleForSQLCreate() {
148                RecognizerBase<EHanaSQLScriptParserStates> createMatch = inAnyState().sequence(CREATE);
149                createMatch.sequence(EnumSet.of(FULLTEXT, GRAPH, SCHEMA, SEQUENCE, STATISTICS, VIEW))
150                                .createNode(EShallowEntityType.STATEMENT, "SQL", 0).skipTo(SEMICOLON).endNode();
151
152                createMatch.optional(PUBLIC).sequence(SYNONYM).createNode(EShallowEntityType.STATEMENT, "SQL", 0)
153                                .skipTo(SEMICOLON).endNode();
154
155                createMatch.optional(UNIQUE).optional(EnumSet.of(BTREE, CPBTREE)).sequence(INDEX)
156                                .createNode(EShallowEntityType.STATEMENT, "SQL", 0).skipTo(SEMICOLON).endNode();
157
158                completeCreateTableRule(createMatch.optional(EnumSet.of(VIRTUAL, ROW, COLUMN)));
159                completeCreateTableRule(createMatch.optional(HISTORY, COLUMN));
160                completeCreateTableRule(createMatch.optional(EnumSet.of(LOCAL, GLOBAL), TEMPORARY).optional(COLUMN));
161
162        }
163
164        /**
165         * Completes the rule for matching SQL Create table statements for HANA
166         * SQLScript
167         */
168        private static void completeCreateTableRule(RecognizerBase<EHanaSQLScriptParserStates> state) {
169                RecognizerBase<EHanaSQLScriptParserStates> matchTable = state.sequence(TABLE)
170                                .createNode(EShallowEntityType.STATEMENT, "SQL", 0);
171                RecognizerBase<EHanaSQLScriptParserStates> matchTableName = matchTableNameForRule(matchTable);
172                matchTableName.skipTo(SEMICOLON).endNode();
173        }
174
175        /**
176         * For SQL Create and INSERT statements, matches the table name taking into
177         * account that temporary tables begin with a hash (#) symbol.
178         */
179        private static RecognizerBase<EHanaSQLScriptParserStates> matchTableNameForRule(
180                        RecognizerBase<EHanaSQLScriptParserStates> state) {
181                return state.optional(ETokenType.HASH).repeated(HANA_SQLSCRIPT_IDENTIFIERS, DOT)
182                                .sequence(HANA_SQLSCRIPT_IDENTIFIERS);
183        }
184
185        /**
186         * Create parser rules for generating function and procedure shallow
187         * entities.
188         */
189        private void createMethodRules() {
190                RecognizerBase<EHanaSQLScriptParserStates> methodStart = inState(OUTSIDE_METHODS).optional(CREATE).markStart()
191                                .sequence(EnumSet.of(FUNCTION, PROCEDURE, TRIGGER)).repeated(HANA_SQLSCRIPT_IDENTIFIERS, DOT)
192                                .sequence(HANA_SQLSCRIPT_IDENTIFIERS).createNode(EShallowEntityType.METHOD, 0, new Region(1, -1))
193                                .skipNested(LPAREN, RPAREN);
194                RecognizerBase<EHanaSQLScriptParserStates> triggerMatch = methodStart.skipTo(ON).sequence(IDENTIFIER)
195                                .skipBefore(BEGIN);
196                completeMethodMatch(triggerMatch);
197
198                RecognizerBase<EHanaSQLScriptParserStates> functionsAndProcsMatch = methodStart.skipBefore(AS).skipBefore(BEGIN)
199                                .optional(SEQUENTIAL, EXECUTION);
200                completeMethodMatch(functionsAndProcsMatch);
201        }
202
203        /**
204         * Execute last rule in order to complete the match for method structures:
205         * triggers, functions and procedures.
206         */
207        private static void completeMethodMatch(RecognizerBase<EHanaSQLScriptParserStates> match) {
208                match.sequence(BEGIN).parseUntil(METHOD_STATEMENTS).sequence(END).optional(SEMICOLON).endNode();
209        }
210
211        /**
212         * Create parser rules for generating shallow entities for simple and block
213         * statements inside a function or method.
214         */
215        private void createStatementRules() {
216                createRulesForVariablesConstants();
217                createRulesForIfElse();
218                createRulesForCaseElse();
219
220                // BEGIN...END block
221                RecognizerBase<EHanaSQLScriptParserStates> blockHeadMatch = inState(METHOD_STATEMENTS).sequence(BEGIN)
222                                .createNode(EShallowEntityType.STATEMENT, "block", 0);
223                completeAnonymousBlockMatch(blockHeadMatch.optional(AUTONOMOUS, TRANSACTION));
224                completeAnonymousBlockMatch(blockHeadMatch.optional(PARALLEL, EXECUTION));
225
226                // Loops
227                createRuleForLoops(WHILE, WHILE);
228                createRuleForLoops(FOR, FOR, IDENTIFIER, EnumSet.of(IN, AS));
229
230                // Assignment
231                Object assignmentOperators = EnumSet.of(EQUAL, ASSIGNMENT);
232                inState(METHOD_STATEMENTS).repeated(IDENTIFIER, DOT).sequence(IDENTIFIER).optional(LEFT_LABEL_BRACKET)
233                                .skipBefore(assignmentOperators).sequence(assignmentOperators)
234                                .createNode(EShallowEntityType.STATEMENT, "assignment", 0).skipTo(SEMICOLON).endNode();
235
236                // Internal procedure calls
237                inState(METHOD_STATEMENTS).sequence(CALL).repeated(HANA_SQLSCRIPT_IDENTIFIERS, DOT).markStart()
238                                .sequence(HANA_SQLSCRIPT_IDENTIFIERS, LPAREN).skipTo(RPAREN)
239                                .createNode(EShallowEntityType.STATEMENT, "procedure call", 0).endNode();
240
241                // Other statements
242                inState(METHOD_STATEMENTS)
243                                .sequence(EnumSet.of(EXEC, SIGNAL, RESIGNAL, OPEN, CLOSE, BREAK, FETCH, CONTINUE, EXECUTE, RETURN))
244                                .createNode(EShallowEntityType.STATEMENT, 0).skipTo(SEMICOLON).endNode();
245        }
246
247        /**
248         * Complete match for anonymous block
249         */
250        private static void completeAnonymousBlockMatch(RecognizerBase<EHanaSQLScriptParserStates> state) {
251                state.parseUntil(METHOD_STATEMENTS).sequence(END).skipTo(SEMICOLON).endNode();
252        }
253
254        /**
255         * Creates rule for matching FOR and WHILE SQL loop statements.
256         */
257        private void createRuleForLoops(Object endToken, Object... startTokens) {
258                inState(METHOD_STATEMENTS).sequence(startTokens).createNode(EShallowEntityType.STATEMENT, 0).skipTo(DO)
259                                .parseUntil(METHOD_STATEMENTS).sequence(END, endToken, SEMICOLON).endNode();
260        }
261
262        /** Create rules for matching IF...THEN...ELSE...IF statements */
263        private void createRulesForIfElse() {
264                // IF...THEN...ELSEIF...ELSE
265                RecognizerBase<EHanaSQLScriptParserStates> ifAlternative = inState(METHOD_STATEMENTS)
266                                .sequence(EnumSet.of(IF, ELSEIF)).createNode(EShallowEntityType.STATEMENT, 0)
267                                .skipToWithNesting(THEN, CASE, END).parseUntil(METHOD_STATEMENTS)
268                                .sequenceBefore(EnumSet.of(ELSEIF, ELSE, END));
269                ifAlternative.sequence(END, IF, SEMICOLON).endNode();
270                ifAlternative.endNodeWithContinuation();
271
272                // ELSE (both for if and case)
273                RecognizerBase<EHanaSQLScriptParserStates> elseMatcher = inState(METHOD_STATEMENTS).sequence(ELSE)
274                                .createNode(EShallowEntityType.STATEMENT, 0).parseUntil(METHOD_STATEMENTS);
275                elseMatcher.sequence(END).skipTo(SEMICOLON).endNode();
276                elseMatcher.sequenceBefore(END, CASE).endNode();
277        }
278
279        /** Create rules for matching CASE...WHEN...THEN...ELSE statements */
280        private void createRulesForCaseElse() {
281                // CASE
282                inState(METHOD_STATEMENTS).sequence(CASE, IDENTIFIER).createNode(EShallowEntityType.STATEMENT, 0)
283                                .parseUntil(METHOD_STATEMENTS).endNode();
284
285                // WHEN in case
286                inState(METHOD_STATEMENTS).sequence(WHEN).skipTo(THEN).createNode(EShallowEntityType.STATEMENT, "when", 1)
287                                .endNode();
288        }
289
290        /** Creates rules for matching variables, constants, cursors, e.t.c. */
291        private void createRulesForVariablesConstants() {
292                RecognizerBase<EHanaSQLScriptParserStates> declareMatch = inState(METHOD_STATEMENTS).sequence(DECLARE);
293                // Cursors.
294                declareMatch.sequence(CURSOR).skipTo(FOR).createNode(EShallowEntityType.ATTRIBUTE, "cursor", 2).sequence(SELECT)
295                                .skipTo(SEMICOLON).endNode();
296                // (exit) Exception handler, with an accompanying select statement or
297                // method block
298                RecognizerBase<EHanaSQLScriptParserStates> exitHandlerMatch = declareMatch.sequence(EXIT, HANDLER, FOR);
299                RecognizerBase<EHanaSQLScriptParserStates> exitHandlerForSQLExceptionAndConditionMatch = exitHandlerMatch
300                                .sequence(EnumSet.of(SQLEXCEPTION, IDENTIFIER));
301                RecognizerBase<EHanaSQLScriptParserStates> exitHandlerForSQLErrorMatch = exitHandlerMatch
302                                .sequence(SQL_ERROR_CODE, INTEGER_LITERAL);
303
304                completeRuleForExitHandlerBlocks(exitHandlerForSQLExceptionAndConditionMatch, -2);
305                completeRuleForExitHandlerBlocks(exitHandlerForSQLErrorMatch, -3);
306
307                exitHandlerForSQLExceptionAndConditionMatch.createNode(EShallowEntityType.ATTRIBUTE, "exit handler", -1)
308                                .optional(SELECT).skipTo(SEMICOLON).endNode();
309                exitHandlerForSQLErrorMatch.createNode(EShallowEntityType.ATTRIBUTE, "exit handler", -2).optional(SELECT)
310                                .skipTo(SEMICOLON).endNode();
311
312                // Conditions
313                declareMatch.sequence(IDENTIFIER, CONDITION).optional(FOR, IDENTIFIER)
314                                .createNode(EShallowEntityType.ATTRIBUTE, "condition variable", 1).endNode();
315                // ... simple, constant and condition variable declarations
316                declareMatch.sequence(IDENTIFIER).repeated(HANA_SQLSCRIPT_IDENTIFIERS)
317                                .createNode(EShallowEntityType.ATTRIBUTE, "variable", 1).skipTo(SEMICOLON).endNode();
318        }
319
320        /**
321         * Conclude rule for matching exit handler blocks
322         */
323        private static void completeRuleForExitHandlerBlocks(RecognizerBase<EHanaSQLScriptParserStates> state, int pos) {
324                state.sequence(BEGIN).createNode(EShallowEntityType.STATEMENT, "exit handler block", pos)
325                                .parseUntil(METHOD_STATEMENTS).sequence(END, SEMICOLON).endNode();
326        }
327
328}