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}