001package gudusoft.gsqlparser.sqlcmds; 002 003import gudusoft.gsqlparser.*; 004import gudusoft.gsqlparser.stmt.*; 005 006/** 007 * Abstract base class for vendor-specific SQL command resolvers. 008 * Provides common functionality and the TSqlCmd/TSqlCmdList infrastructure 009 * that all vendor implementations share. 010 * 011 * @since 3.1.0.9 012 */ 013public abstract class AbstractSqlCmds implements ISqlCmds { 014 015 protected EDbVendor vendor; 016 protected volatile TSqlCmdList sqlCmdList; 017 protected final Object initLock = new Object(); 018 protected volatile boolean initialized = false; 019 020 /** 021 * Temporary field for tracking statement type during issql() processing. 022 * Used by both findcte() and issql() methods. 023 */ 024 protected ESqlStatementType gnewsqlstatementtype = ESqlStatementType.sstinvalid; 025 026 /** 027 * Constructor for vendor-specific implementation. 028 * @param vendor Database vendor this resolver handles 029 */ 030 protected AbstractSqlCmds(EDbVendor vendor) { 031 this.vendor = vendor; 032 } 033 034 @Override 035 public EDbVendor getVendor() { 036 return vendor; 037 } 038 039 040 @Override 041 public TSqlCmdList getSqlCmdList() { 042 ensureInitialized(); 043 return sqlCmdList; 044 } 045 046 /** 047 * Ensures command list is initialized using double-checked locking. 048 * This provides thread-safe lazy initialization. 049 */ 050 protected void ensureInitialized() { 051 if (!initialized) { 052 synchronized (initLock) { 053 if (!initialized) { 054 sqlCmdList = new TSqlCmdList(); 055 initializeCommands(); 056 initialized = true; 057 } 058 } 059 } 060 } 061 062 /** 063 * Initialize vendor-specific commands. 064 * Subclasses must implement this to populate sqlCmdList. 065 */ 066 protected abstract void initializeCommands(); 067 068 /** 069 * Helper method to add a command to the list. 070 * Simplifies command initialization in subclasses. 071 * 072 * @param token1 First token (keyword) 073 * @param token2 Second token pattern 074 * @param token3 Third token pattern 075 * @param token4 Fourth token pattern 076 * @param token5 Fifth token pattern 077 * @param token6 Sixth token pattern 078 * @param token7 Seventh token pattern 079 * @param token8 Eighth token pattern 080 * @param stmtType Statement type 081 */ 082 protected void addCmd(int token1, String token2, String token3, String token4, 083 String token5, String token6, String token7, String token8, 084 ESqlStatementType stmtType) { 085 TSqlCmd cmd = new TSqlCmd(); 086 cmd.token1 = token1; 087 cmd.token2 = token2; 088 cmd.token3 = token3; 089 cmd.token4 = token4; 090 cmd.token5 = token5; 091 cmd.token6 = token6; 092 cmd.token7 = token7; 093 cmd.token8 = token8; 094 cmd.sqlstatementtype = stmtType; 095 096 // Handle vendor-specific reserved words (token codes > TBaseType.rrw_abort) 097 // These need their string representation set explicitly 098 if (token1 > TBaseType.rrw_abort) { 099 String tokenStr = getToken1Str(token1); 100 if (tokenStr != null && !tokenStr.isEmpty()) { 101 cmd.token1Str = tokenStr; 102 } 103 } 104 105 sqlCmdList.add(cmd); 106 } 107 108 /** 109 * Get the string representation for vendor-specific token codes. 110 * Subclasses should override this method to provide mappings for 111 * vendor-specific reserved words (tokens > TBaseType.rrw_abort). 112 * 113 * @param token1 Token code 114 * @return String representation of the token, or null if not applicable 115 */ 116 protected String getToken1Str(int token1) { 117 // Default implementation returns null 118 // Subclasses override to provide vendor-specific mappings 119 return null; 120 } 121 122 /** 123 * Overloaded helper for simpler commands. 124 */ 125 protected void addCmd(int token1, ESqlStatementType stmtType) { 126 addCmd(token1, "", "", "", "", "", "", "", stmtType); 127 } 128 129 protected void addCmd(int token1, String token2, ESqlStatementType stmtType) { 130 addCmd(token1, token2, "", "", "", "", "", "", stmtType); 131 } 132 133 protected void addCmd(int token1, String token2, String token3, ESqlStatementType stmtType) { 134 addCmd(token1, token2, token3, "", "", "", "", "", stmtType); 135 } 136 137 protected void addCmd(int token1, String token2, String token3, String token4, ESqlStatementType stmtType) { 138 addCmd(token1, token2, token3, token4, "", "", "", "", stmtType); 139 } 140 141 protected void addCmd(int token1, String token2, String token3, String token4, String token5, ESqlStatementType stmtType) { 142 addCmd(token1, token2, token3, token4, token5, "", "", "", stmtType); 143 } 144 145 protected void addCmd(int token1, String token2, String token3, String token4, String token5, String token6, ESqlStatementType stmtType) { 146 addCmd(token1, token2, token3, token4, token5, token6, "", "", stmtType); 147 } 148 149 protected void addCmd(int token1, String token2, String token3, String token4, String token5, String token6, String token7, ESqlStatementType stmtType) { 150 addCmd(token1, token2, token3, token4, token5, token6, token7, "", stmtType); 151 } 152 153 /** 154 * Find command in the list starting from a specific token. 155 * This is the core search algorithm used by all vendors. 156 * 157 * @param pcst Starting token 158 * @param cmdList Command list to search 159 * @return Found command or null 160 */ 161 protected TSqlCmd finddbcmd(TSourceToken pcst, TSqlCmdList cmdList) { 162 if (pcst == null || cmdList == null) return null; 163 if (pcst.tokentype != ETokenType.ttkeyword) return null; 164 165 int startIndex = cmdList.getStartIndex(pcst.tokencode); 166 if (startIndex == -1) return null; 167 168 TSqlCmd bestMatch = null; 169 int maxTokensMatched = 0; 170 171 for (int i = startIndex; i < cmdList.size(); i++) { 172 TSqlCmd cmd = (TSqlCmd) cmdList.get(i); 173 174 // First token must match 175 if (cmd.token1 != pcst.tokencode) { 176 // If we've already found some matches, we can break since commands are grouped by first token 177 if (maxTokensMatched > 0) break; 178 continue; 179 } 180 181 int tokensMatched = 1; 182 TSourceToken lcst = pcst; 183 boolean matches = true; 184 185 // Check token2 186 if (!cmd.token2.isEmpty() && !cmd.token2.equals(" ")) { 187 lcst = lcst.nextSolidToken(); 188 if (!tokenMatches(lcst, cmd.token2)) { 189 matches = false; 190 } else { 191 tokensMatched = 2; 192 } 193 } 194 195 // Check token3 196 if (matches && !cmd.token3.isEmpty() && !cmd.token3.equals(" ")) { 197 lcst = lcst.nextSolidToken(); 198 if (!tokenMatches(lcst, cmd.token3)) { 199 matches = false; 200 } else { 201 tokensMatched = 3; 202 } 203 } 204 205 // Check token4 206 if (matches && !cmd.token4.isEmpty() && !cmd.token4.equals(" ")) { 207 lcst = lcst.nextSolidToken(); 208 if (!tokenMatches(lcst, cmd.token4)) { 209 matches = false; 210 } else { 211 tokensMatched = 4; 212 } 213 } 214 215 // Check token5 216 if (matches && !cmd.token5.isEmpty() && !cmd.token5.equals(" ")) { 217 lcst = lcst.nextSolidToken(); 218 if (!tokenMatches(lcst, cmd.token5)) { 219 matches = false; 220 } else { 221 tokensMatched = 5; 222 } 223 } 224 225 // Check token6 226 if (matches && !cmd.token6.isEmpty() && !cmd.token6.equals(" ")) { 227 lcst = lcst.nextSolidToken(); 228 if (!tokenMatches(lcst, cmd.token6)) { 229 matches = false; 230 } else { 231 tokensMatched = 6; 232 } 233 } 234 235 // Check token7 236 if (matches && !cmd.token7.isEmpty() && !cmd.token7.equals(" ")) { 237 lcst = lcst.nextSolidToken(); 238 if (!tokenMatches(lcst, cmd.token7)) { 239 matches = false; 240 } else { 241 tokensMatched = 7; 242 } 243 } 244 245 // Check token8 246 if (matches && !cmd.token8.isEmpty() && !cmd.token8.equals(" ")) { 247 lcst = lcst.nextSolidToken(); 248 if (!tokenMatches(lcst, cmd.token8)) { 249 matches = false; 250 } else { 251 tokensMatched = 8; 252 } 253 } 254 255 // If this command matches and has more tokens than our current best match, update it 256 if (matches && tokensMatched > maxTokensMatched) { 257 bestMatch = cmd; 258 maxTokensMatched = tokensMatched; 259 260 // If this command ends with an empty token (exact match), we can return immediately 261 // as no longer match is possible 262 if ((tokensMatched == 2 && cmd.token3.isEmpty()) || 263 (tokensMatched == 3 && cmd.token4.isEmpty()) || 264 (tokensMatched == 4 && cmd.token5.isEmpty()) || 265 (tokensMatched == 5 && cmd.token6.isEmpty()) || 266 (tokensMatched == 6 && cmd.token7.isEmpty()) || 267 (tokensMatched == 7 && cmd.token8.isEmpty()) || 268 (tokensMatched == 8)) { 269 return bestMatch; 270 } 271 } 272 } 273 274 return bestMatch; 275 } 276 277 /** 278 * Check if a token matches a pattern string. 279 * Pattern can be "*" (any), or a literal string. 280 */ 281 private boolean tokenMatches(TSourceToken token, String pattern) { 282 if (token == null) return false; 283 if (pattern.equals("*")) return true; 284 285 // Check literal match (case-insensitive) 286 return token.toString().equalsIgnoreCase(pattern); 287 } 288 289 @Override 290 public ESqlStatementType getStatementTypeForToken(TSourceToken token) { 291 ensureInitialized(); 292 TSqlCmd cmd = finddbcmd(token, sqlCmdList); 293 return (cmd != null) ? cmd.sqlstatementtype : ESqlStatementType.sstinvalid; 294 } 295 296 /** 297 * Detect Common Table Expression (CTE) for all database vendors. 298 * Handles WITH clauses before DELETE, INSERT, SELECT, UPDATE, MERGE. 299 * 300 * This is a shared method used by all vendor implementations since CTE 301 * syntax is similar across vendors (standardized by SQL:1999). 302 * 303 * @param ptoken The WITH token 304 * @return Statement object for the CTE query, or null if not a valid CTE 305 */ 306 protected TCustomSqlStatement findcte(TSourceToken ptoken) { 307 TCustomSqlStatement ret = null; 308 TSourceToken lctoken = null; 309 int lcnested = 0, k, j; 310 boolean inXmlNamespaces = false; 311 boolean isXmlNamespaces = false; 312 313 int lcpos = ptoken.posinlist; 314 TSourceTokenList lcsourcetokenlist = ptoken.container; 315 316 for (int i = lcpos + 1; i < lcsourcetokenlist.size(); i++) { 317 lctoken = lcsourcetokenlist.get(i); 318 319 // Handle XML namespaces (for SQL Server compatibility) 320 if (lctoken.tokencode == TBaseType.rrw_xmlnamespaces) { 321 inXmlNamespaces = true; 322 lcnested = 0; 323 continue; 324 } 325 326 if (inXmlNamespaces) { 327 if (lctoken.tokentype == ETokenType.ttleftparenthesis) lcnested++; 328 if (lctoken.tokentype == ETokenType.ttrightparenthesis) { 329 lcnested--; 330 if (lcnested == 0) { 331 inXmlNamespaces = false; 332 isXmlNamespaces = true; 333 } 334 } 335 continue; 336 } 337 338 // Look for AS keyword or after XML namespaces 339 if ((lctoken.tokencode == TBaseType.rrw_as) || isXmlNamespaces) { 340 lcnested = 0; 341 int startPos = i + 1; 342 if (isXmlNamespaces) startPos = i; 343 344 for (j = startPos; j < lcsourcetokenlist.size(); j++) { 345 lctoken = lcsourcetokenlist.get(j); 346 if (lctoken.isnonsolidtoken()) continue; 347 if (lctoken.tokentype == ETokenType.ttleftparenthesis) lcnested++; 348 if (lctoken.tokentype == ETokenType.ttrightparenthesis) lcnested--; 349 350 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_delete)) { 351 ret = new TDeleteSqlStatement(vendor); 352 ret.isctequery = true; 353 gnewsqlstatementtype = ESqlStatementType.sstdelete; 354 break; 355 } 356 357 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_merge)) { 358 ret = new TMergeSqlStatement(vendor); 359 ret.isctequery = true; 360 gnewsqlstatementtype = ESqlStatementType.sstmerge; 361 break; 362 } 363 364 if ((lcnested == 0) && ((lctoken.tokencode == TBaseType.rrw_insert) || (lctoken.tokencode == TBaseType.rrw_replace))) { 365 ret = new TInsertSqlStatement(vendor); 366 ret.isctequery = true; 367 gnewsqlstatementtype = (lctoken.tokencode == TBaseType.rrw_replace) ? ESqlStatementType.sstmysqlreplace : ESqlStatementType.sstinsert; 368 ret.sqlstatementtype = gnewsqlstatementtype; 369 ret.dummytag = 1; // select stmt in insert is permitted 370 371 for (k = lctoken.posinlist + 1; k < lcsourcetokenlist.size(); k++) { 372 if (lcsourcetokenlist.get(k).isnonsolidtoken()) continue; 373 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_values) break; 374 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_go) break; 375 if (lcsourcetokenlist.get(k).tokentype == ETokenType.ttsemicolon) break; 376 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_select) break; 377 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_teradata_sel) break; 378 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_execute) break; 379 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_exec) break; 380 } 381 if (k > lcsourcetokenlist.size() - 1) 382 k = lcsourcetokenlist.size() - 1; 383 384 for (int m = lctoken.posinlist + 1; m <= k; m++) { 385 lcsourcetokenlist.get(m).tokenstatus = ETokenStatus.tsignoredbygetrawstatement; 386 } 387 388 break; 389 } 390 391 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_values) && (vendor == EDbVendor.dbvpostgresql || vendor == EDbVendor.dbvmysql)) { 392 ret = new TSelectSqlStatement(vendor); 393 ret.isctequery = true; 394 gnewsqlstatementtype = ESqlStatementType.sstselect; 395 break; 396 } 397 398 // MySQL TABLE statement (TABLE t1 is equivalent to SELECT * FROM t1) 399 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_table) && (vendor == EDbVendor.dbvmysql)) { 400 ret = new TSelectSqlStatement(vendor); 401 ret.isctequery = true; 402 gnewsqlstatementtype = ESqlStatementType.sstselect; 403 break; 404 } 405 406 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_select)) { 407 ret = new TSelectSqlStatement(vendor); 408 ret.isctequery = true; 409 gnewsqlstatementtype = ESqlStatementType.sstselect; 410 break; 411 } 412 413 // Teradata SEL keyword (abbreviation for SELECT) 414 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_teradata_sel)) { 415 ret = new TSelectSqlStatement(vendor); 416 ret.isctequery = true; 417 gnewsqlstatementtype = ESqlStatementType.sstselect; 418 break; 419 } 420 421 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_update)) { 422 ret = new TUpdateSqlStatement(vendor); 423 ret.isctequery = true; 424 ret.dummytag = 1; // means set clause in update is not found yet 425 gnewsqlstatementtype = ESqlStatementType.sstupdate; 426 break; 427 } 428 429 if ((vendor == EDbVendor.dbvhive) && (lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_from)) { 430 TSourceToken cmdToken = lctoken.searchToken(TBaseType.rrw_insert, 3); 431 if (cmdToken != null) { 432 ret = new TInsertSqlStatement(vendor); 433 ret.isctequery = true; 434 gnewsqlstatementtype = ESqlStatementType.sstinsert; 435 } else { 436 ret = new TSelectSqlStatement(vendor); 437 ret.isctequery = true; 438 gnewsqlstatementtype = ESqlStatementType.ssthiveFromQuery; 439 } 440 break; 441 } 442 443 // BigQuery: CTE followed by FROM pipe syntax (WITH ... FROM t |> ...) 444 if ((vendor == EDbVendor.dbvbigquery) && (lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_from)) { 445 ret = new TUnknownSqlStatement(vendor); 446 ret.isctequery = true; 447 ret.sqlstatementtype = ESqlStatementType.sstselect; 448 gnewsqlstatementtype = ESqlStatementType.sstselect; 449 break; 450 } 451 } 452 453 // Mark CTE tokens as ignored for raw statement processing 454 if (ret != null) { 455 for (k = lcpos + 1; k <= j; k++) { 456 lcsourcetokenlist.get(k).tokenstatus = ETokenStatus.tsignoredbygetrawstatement; 457 } 458 break; 459 } 460 } 461 } 462 463 return ret; 464 } 465}