001package gudusoft.gsqlparser.parser;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.TCustomLexer;
006import gudusoft.gsqlparser.TCustomParser;
007import gudusoft.gsqlparser.TCustomSqlStatement;
008import gudusoft.gsqlparser.TLexerSqlite;
009import gudusoft.gsqlparser.TParserSqlite;
010import gudusoft.gsqlparser.TSourceToken;
011import gudusoft.gsqlparser.TSourceTokenList;
012import gudusoft.gsqlparser.TStatementList;
013import gudusoft.gsqlparser.TSyntaxError;
014import gudusoft.gsqlparser.EFindSqlStateType;
015import gudusoft.gsqlparser.ETokenType;
016import gudusoft.gsqlparser.ETokenStatus;
017import gudusoft.gsqlparser.ESqlStatementType;
018import gudusoft.gsqlparser.EErrorType;
019import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
020import gudusoft.gsqlparser.sqlcmds.ISqlCmds;
021import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
022import gudusoft.gsqlparser.stmt.TCommonBlock;
023import gudusoft.gsqlparser.compiler.TContext;
024import gudusoft.gsqlparser.sqlenv.TSQLEnv;
025import gudusoft.gsqlparser.compiler.TGlobalScope;
026import gudusoft.gsqlparser.compiler.TFrame;
027
028import java.util.ArrayList;
029import java.util.List;
030import java.util.Stack;
031
032/**
033 * SQLite database SQL parser implementation.
034 *
035 * <p>This parser handles SQLite-specific SQL syntax including:
036 * <ul>
037 *   <li>Standard SQL DML/DDL (SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER)</li>
038 *   <li>SQLite-specific statements (PRAGMA, ATTACH, DETACH, VACUUM, REINDEX)</li>
039 *   <li>SQLite expression syntax and type affinity</li>
040 * </ul>
041 *
042 * <p><b>Design Notes:</b>
043 * <ul>
044 *   <li>Extends {@link AbstractSqlParser}</li>
045 *   <li>Based on PostgreSQL grammar (SQLite follows "What would PostgreSQL do?")</li>
046 *   <li>No PL/pgSQL support (SQLite has no stored procedures)</li>
047 *   <li>No dollar-quoting support</li>
048 *   <li>Delimiter character: ';' for SQL statements</li>
049 * </ul>
050 *
051 * @see SqlParser
052 * @see AbstractSqlParser
053 * @see TLexerSqlite
054 * @see TParserSqlite
055 * @since 3.2.0.0
056 */
057public class SqliteSqlParser extends AbstractSqlParser {
058
059    // ========== Lexer and Parser Instances ==========
060
061    /** The SQLite lexer used for tokenization (public for TGSqlParser.getFlexer()) */
062    public TLexerSqlite flexer;
063    private TParserSqlite fparser;
064
065    // ========== Constructor ==========
066
067    /**
068     * Construct SQLite SQL parser.
069     * <p>
070     * Configures the parser for SQLite database with default delimiter: semicolon (;)
071     */
072    public SqliteSqlParser() {
073        super(EDbVendor.dbvsqlite);
074
075        // Set delimiter character
076        this.delimiterChar = ';';
077        this.defaultDelimiterStr = ";";
078
079        // Create lexer once - will be reused for all parsing operations
080        this.flexer = new TLexerSqlite();
081        this.flexer.delimiterchar = this.delimiterChar;
082        this.flexer.defaultDelimiterStr = this.defaultDelimiterStr;
083
084        // CRITICAL: Set lexer for inherited getanewsourcetoken() method
085        this.lexer = this.flexer;
086
087        // Create parser once - will be reused for all parsing operations
088        this.fparser = new TParserSqlite(null);
089        this.fparser.lexer = this.flexer;
090    }
091
092    // ========== AbstractSqlParser Abstract Methods Implementation ==========
093
094    @Override
095    protected TCustomLexer getLexer(ParserContext context) {
096        return this.flexer;
097    }
098
099    @Override
100    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
101        this.fparser.sourcetokenlist = tokens;
102        return this.fparser;
103    }
104
105    @Override
106    protected void tokenizeVendorSql() {
107        dosqlitetexttotokenlist();
108    }
109
110    @Override
111    protected void setupVendorParsersForExtraction() {
112        this.fparser.sqlcmds = this.sqlcmds;
113        this.fparser.sourcetokenlist = this.sourcetokenlist;
114    }
115
116    @Override
117    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
118        dosqlitegetrawsqlstatements(builder);
119    }
120
121    @Override
122    protected TStatementList performParsing(ParserContext context,
123                                           TCustomParser parser,
124                                           TCustomParser secondaryParser,
125                                           TSourceTokenList tokens,
126                                           TStatementList rawStatements) {
127        this.sourcetokenlist = tokens;
128        this.parserContext = context;
129        this.sqlstatements = rawStatements;
130
131        // Initialize global context for statement parsing
132        initializeGlobalContext();
133
134        // Parse each statement
135        for (int i = 0; i < sqlstatements.size(); i++) {
136            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
137            stmt.setFrameStack(frameStack);
138            int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
139
140            if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
141                copyErrorsFromStatement(stmt);
142            }
143        }
144
145        // Clean up frame stack
146        if (globalFrame != null) {
147            globalFrame.popMeFromStack(frameStack);
148        }
149
150        return this.sqlstatements;
151    }
152
153    // ========== SQLite-Specific Tokenization ==========
154
155    /**
156     * Perform SQLite-specific tokenization.
157     * <p>
158     * Simplified from PostgreSQL tokenization - no SQL*Plus commands,
159     * no dollar quoting, no PL/pgSQL.
160     */
161    private void dosqlitetexttotokenlist() {
162        TSourceToken prevst = null;
163        TSourceToken asourcetoken, lcprevst;
164        int yychar;
165
166        asourcetoken = getanewsourcetoken();
167        if (asourcetoken == null) return;
168        yychar = asourcetoken.tokencode;
169
170        while (yychar > 0) {
171            sourcetokenlist.add(asourcetoken);
172
173            switch (yychar) {
174                case TBaseType.cmtdoublehyphen:
175                case TBaseType.cmtslashstar:
176                case TBaseType.lexspace: {
177                    break;
178                }
179
180                case TBaseType.lexnewline: {
181                    break;
182                }
183
184                default: {
185                    // Solid token - keyword handling
186                    if (prevst != null) {
187                        if (prevst.tokencode == TBaseType.rrw_inner) {
188                            if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) {
189                                prevst.tokencode = TBaseType.ident;
190                            }
191                        }
192
193                        if ((prevst.tokencode == TBaseType.rrw_not)
194                                && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) {
195                            prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE");
196                        }
197                    }
198
199                    if (asourcetoken.tokencode == TBaseType.rrw_inner) {
200                        prevst = asourcetoken;
201                    } else if (asourcetoken.tokencode == TBaseType.rrw_not) {
202                        prevst = asourcetoken;
203                    } else {
204                        prevst = null;
205                    }
206
207                    // Additional transformations
208                    if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD"))
209                            || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) {
210                        lcprevst = getprevsolidtoken(asourcetoken);
211                        if (lcprevst != null) {
212                            if (lcprevst.tokencode == TBaseType.rrw_for)
213                                lcprevst.tokencode = TBaseType.rw_for1;
214                        }
215                    }
216
217                    if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) {
218                        TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2);
219                        if (stKeep != null) {
220                            stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank;
221                        }
222                    }
223
224                    if (asourcetoken.tokencode == TBaseType.JSON_EXIST) {
225                        TSourceToken stPercent = asourcetoken.searchToken('=', -1);
226                        if (stPercent != null) {
227                            asourcetoken.tokencode = TBaseType.ident;
228                        }
229                    }
230
231                    if (asourcetoken.tokencode == TBaseType.rrw_update) {
232                        TSourceToken stDo = asourcetoken.searchToken(TBaseType.rrw_do, -1);
233                        if (stDo != null) {
234                            asourcetoken.tokencode = TBaseType.rrw_postgresql_do_update;
235                        }
236                    }
237
238                    break;
239                }
240            }
241
242            // Get next token
243            asourcetoken = getanewsourcetoken();
244            if (asourcetoken != null) {
245                yychar = asourcetoken.tokencode;
246            } else {
247                yychar = 0;
248            }
249
250            if ((yychar == 0) && (prevst != null)) {
251                if (prevst.tokencode == TBaseType.rrw_inner) {
252                    prevst.tokencode = TBaseType.ident;
253                }
254            }
255        }
256    }
257
258    /**
259     * Get previous non-whitespace token.
260     */
261    private TSourceToken getprevsolidtoken(TSourceToken ptoken) {
262        TSourceToken ret = null;
263        TSourceTokenList lctokenlist = ptoken.container;
264
265        if (lctokenlist != null) {
266            if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) {
267                if (!(
268                        (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace)
269                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn)
270                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment)
271                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment)
272                )) {
273                    ret = lctokenlist.get(ptoken.posinlist - 1);
274                } else {
275                    ret = lctokenlist.nextsolidtoken(ptoken.posinlist - 1, -1, false);
276                }
277            }
278        }
279        return ret;
280    }
281
282    // ========== SQLite-Specific Raw Statement Extraction ==========
283
284    /**
285     * Extract raw SQLite SQL statements from tokenized source.
286     * <p>
287     * Simplified from PostgreSQL - no stored procedure states,
288     * no dollar-quoting, no PL/pgSQL blocks.
289     */
290    private void dosqlitegetrawsqlstatements(SqlParseResult.Builder builder) {
291        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
292        if (!TBaseType.assigned(sourcetokenlist)) {
293            builder.sqlStatements(this.sqlstatements);
294            builder.errorCode(1);
295            builder.errorMessage("No source token list available");
296            return;
297        }
298
299        TCustomSqlStatement gcurrentsqlstatement = null;
300        EFindSqlStateType gst = EFindSqlStateType.stnormal;
301        TSourceToken lcprevsolidtoken = null, ast = null;
302        boolean waitingDelimiter = false;
303
304        for (int i = 0; i < sourcetokenlist.size(); i++) {
305
306            if ((ast != null) && (ast.issolidtoken()))
307                lcprevsolidtoken = ast;
308
309            ast = sourcetokenlist.get(i);
310            sourcetokenlist.curpos = i;
311
312            // Token transformations during raw statement extraction
313            performRawStatementTokenTransformations(ast);
314
315            switch (gst) {
316                case sterror: {
317                    if (ast.tokentype == ETokenType.ttsemicolon) {
318                        appendToken(gcurrentsqlstatement, ast);
319                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
320                        gst = EFindSqlStateType.stnormal;
321                    } else {
322                        appendToken(gcurrentsqlstatement, ast);
323                    }
324                    break;
325                }
326
327                case stnormal: {
328                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
329                            || (ast.tokencode == TBaseType.cmtslashstar)
330                            || (ast.tokencode == TBaseType.lexspace)
331                            || (ast.tokencode == TBaseType.lexnewline)
332                            || (ast.tokentype == ETokenType.ttsemicolon)) {
333                        if (gcurrentsqlstatement != null) {
334                            appendToken(gcurrentsqlstatement, ast);
335                        }
336
337                        if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
338                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
339                                ast.tokentype = ETokenType.ttsimplecomment;
340                                ast.tokencode = TBaseType.cmtdoublehyphen;
341                            }
342                        }
343
344                        continue;
345                    }
346
347                    // Find a token to start sql mode
348                    gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
349
350                    if (gcurrentsqlstatement != null) {
351                        if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger) {
352                            gst = EFindSqlStateType.ststoredprocedure;
353                        } else {
354                            gst = EFindSqlStateType.stsql;
355                        }
356                        appendToken(gcurrentsqlstatement, ast);
357                    } else {
358                        // Error token found
359                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo,
360                                (ast.columnNo < 0 ? 0 : ast.columnNo),
361                                "Error when tokenize", EErrorType.spwarning,
362                                TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
363
364                        ast.tokentype = ETokenType.tttokenlizererrortoken;
365                        gst = EFindSqlStateType.sterror;
366
367                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
368                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
369                        appendToken(gcurrentsqlstatement, ast);
370                    }
371
372                    break;
373                }
374
375                case stsql: {
376                    if (ast.tokentype == ETokenType.ttsemicolon) {
377                        gst = EFindSqlStateType.stnormal;
378                        appendToken(gcurrentsqlstatement, ast);
379                        gcurrentsqlstatement.semicolonended = ast;
380                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
381                        continue;
382                    }
383
384                    if (ast.tokencode == TBaseType.cmtdoublehyphen) {
385                        if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) {
386                            gst = EFindSqlStateType.stnormal;
387                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
388                            continue;
389                        }
390                    }
391
392                    appendToken(gcurrentsqlstatement, ast);
393                    break;
394                }
395
396                case ststoredprocedure: {
397                    // CREATE TRIGGER state: handle BEGIN...END blocks
398                    // When BEGIN is encountered, set waitingDelimiter so semicolons
399                    // inside the trigger body don't split the statement.
400                    if (ast.tokencode == TBaseType.rrw_begin) {
401                        waitingDelimiter = true;
402                    }
403
404                    if (waitingDelimiter) {
405                        // Inside BEGIN...END block: only end on END; pattern
406                        if (ast.tokentype == ETokenType.ttsemicolon) {
407                            TSourceToken lcprevtoken = ast.container.nextsolidtoken(ast, -1, false);
408                            if (lcprevtoken != null && lcprevtoken.tokencode == TBaseType.rrw_end) {
409                                // END; found - complete the CREATE TRIGGER statement
410                                gst = EFindSqlStateType.stnormal;
411                                waitingDelimiter = false;
412                                gcurrentsqlstatement.semicolonended = ast;
413                                appendToken(gcurrentsqlstatement, ast);
414                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
415                                continue;
416                            }
417                        }
418                        appendToken(gcurrentsqlstatement, ast);
419                    } else {
420                        // Before BEGIN: normal semicolon handling (for EXECUTE PROCEDURE style)
421                        if (ast.tokentype == ETokenType.ttsemicolon) {
422                            gst = EFindSqlStateType.stnormal;
423                            appendToken(gcurrentsqlstatement, ast);
424                            gcurrentsqlstatement.semicolonended = ast;
425                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
426                            continue;
427                        }
428                        appendToken(gcurrentsqlstatement, ast);
429                    }
430                    break;
431                }
432
433                default:
434                    break;
435            }
436        }
437
438        // Last statement
439        if ((gcurrentsqlstatement != null) &&
440                ((gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.sterror)
441                        || (gst == EFindSqlStateType.ststoredprocedure))) {
442            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder);
443        }
444
445        // Populate builder with results
446        builder.sqlStatements(this.sqlstatements);
447        builder.syntaxErrors(syntaxErrors instanceof ArrayList ?
448                (ArrayList<TSyntaxError>) syntaxErrors : new ArrayList<>(syntaxErrors));
449        builder.errorCode(syntaxErrors.isEmpty() ? 0 : syntaxErrors.size());
450    }
451
452    /**
453     * Handle token transformations during raw statement extraction.
454     */
455    private void performRawStatementTokenTransformations(TSourceToken ast) {
456        if (ast.tokencode == TBaseType.JSON_EXIST) {
457            TSourceToken stConstant = ast.searchToken(TBaseType.sconst, 1);
458            if (stConstant == null) {
459                ast.tokencode = TBaseType.ident;
460            }
461        } else if (ast.tokencode == TBaseType.rrw_postgresql_POSITION) {
462            TSourceToken st1 = ast.nextSolidToken();
463            if (st1 != null) {
464                if (st1.tokencode == '(') {
465                    ast.tokencode = TBaseType.rrw_postgresql_POSITION_FUNCTION;
466                }
467            }
468        } else if (ast.tokencode == TBaseType.rrw_postgresql_ordinality) {
469            TSourceToken lcprevst = getprevsolidtoken(ast);
470            if (lcprevst != null) {
471                if (lcprevst.tokencode == TBaseType.rrw_with) {
472                    TSourceToken lcnextst = ast.nextSolidToken();
473                    if ((lcnextst != null) && (lcnextst.tokencode == TBaseType.rrw_as)) {
474                        // Don't change with to rrw_postgresql_with_lookahead
475                    } else {
476                        lcprevst.tokencode = TBaseType.rrw_postgresql_with_lookahead;
477                    }
478                }
479            }
480        } else if (ast.tokencode == TBaseType.rrw_postgresql_filter) {
481            TSourceToken st1 = ast.nextSolidToken();
482            if (st1 != null) {
483                if (st1.tokencode != '(') {
484                    ast.tokencode = TBaseType.ident;
485                }
486            }
487        } else if (ast.tokencode == TBaseType.rrw_values) {
488            TSourceToken stParen = ast.searchToken('(', 1);
489            if (stParen != null) {
490                TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist);
491                if (stInsert != null) {
492                    TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist);
493                    if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) {
494                        // Don't treat values(1) as insert values
495                    } else {
496                        TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist);
497                        if ((stFrom != null) && (stFrom.posinlist > stInsert.posinlist)) {
498                            // Don't treat values after from keyword as an insert values
499                        } else {
500                            ast.tokencode = TBaseType.rrw_sqlite_insert_values;
501                        }
502                    }
503                }
504            }
505        }
506    }
507
508    private void appendToken(TCustomSqlStatement statement, TSourceToken token) {
509        if (statement == null || token == null) {
510            return;
511        }
512        token.stmt = statement;
513        statement.sourcetokenlist.add(token);
514    }
515
516    @Override
517    public String toString() {
518        return "SqliteSqlParser{vendor=" + vendor + "}";
519    }
520}