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 stmtType Statement type 080 */ 081 protected void addCmd(int token1, String token2, String token3, String token4, 082 String token5, String token6, String token7, 083 ESqlStatementType stmtType) { 084 TSqlCmd cmd = new TSqlCmd(); 085 cmd.token1 = token1; 086 cmd.token2 = token2; 087 cmd.token3 = token3; 088 cmd.token4 = token4; 089 cmd.token5 = token5; 090 cmd.token6 = token6; 091 cmd.token7 = token7; 092 cmd.sqlstatementtype = stmtType; 093 094 // Handle vendor-specific reserved words (token codes > TBaseType.rrw_abort) 095 // These need their string representation set explicitly 096 if (token1 > TBaseType.rrw_abort) { 097 String tokenStr = getToken1Str(token1); 098 if (tokenStr != null && !tokenStr.isEmpty()) { 099 cmd.token1Str = tokenStr; 100 } 101 } 102 103 sqlCmdList.add(cmd); 104 } 105 106 /** 107 * Get the string representation for vendor-specific token codes. 108 * Subclasses should override this method to provide mappings for 109 * vendor-specific reserved words (tokens > TBaseType.rrw_abort). 110 * 111 * @param token1 Token code 112 * @return String representation of the token, or null if not applicable 113 */ 114 protected String getToken1Str(int token1) { 115 // Default implementation returns null 116 // Subclasses override to provide vendor-specific mappings 117 return null; 118 } 119 120 /** 121 * Overloaded helper for simpler commands. 122 */ 123 protected void addCmd(int token1, ESqlStatementType stmtType) { 124 addCmd(token1, "", "", "", "", "", "", stmtType); 125 } 126 127 protected void addCmd(int token1, String token2, ESqlStatementType stmtType) { 128 addCmd(token1, token2, "", "", "", "", "", stmtType); 129 } 130 131 protected void addCmd(int token1, String token2, String token3, ESqlStatementType stmtType) { 132 addCmd(token1, token2, token3, "", "", "", "", stmtType); 133 } 134 135 protected void addCmd(int token1, String token2, String token3, String token4, ESqlStatementType stmtType) { 136 addCmd(token1, token2, token3, token4, "", "", "", stmtType); 137 } 138 139 protected void addCmd(int token1, String token2, String token3, String token4, String token5, ESqlStatementType stmtType) { 140 addCmd(token1, token2, token3, token4, token5, "", "", stmtType); 141 } 142 143 protected void addCmd(int token1, String token2, String token3, String token4, String token5, String token6, ESqlStatementType stmtType) { 144 addCmd(token1, token2, token3, token4, token5, token6, "", stmtType); 145 } 146 147 /** 148 * Find command in the list starting from a specific token. 149 * This is the core search algorithm used by all vendors. 150 * 151 * @param pcst Starting token 152 * @param cmdList Command list to search 153 * @return Found command or null 154 */ 155 protected TSqlCmd finddbcmd(TSourceToken pcst, TSqlCmdList cmdList) { 156 if (pcst == null || cmdList == null) return null; 157 if (pcst.tokentype != ETokenType.ttkeyword) return null; 158 159 int startIndex = cmdList.getStartIndex(pcst.tokencode); 160 if (startIndex == -1) return null; 161 162 TSqlCmd bestMatch = null; 163 int maxTokensMatched = 0; 164 165 for (int i = startIndex; i < cmdList.size(); i++) { 166 TSqlCmd cmd = (TSqlCmd) cmdList.get(i); 167 168 // First token must match 169 if (cmd.token1 != pcst.tokencode) { 170 // If we've already found some matches, we can break since commands are grouped by first token 171 if (maxTokensMatched > 0) break; 172 continue; 173 } 174 175 int tokensMatched = 1; 176 TSourceToken lcst = pcst; 177 boolean matches = true; 178 179 // Check token2 180 if (!cmd.token2.isEmpty() && !cmd.token2.equals(" ")) { 181 lcst = lcst.nextSolidToken(); 182 if (!tokenMatches(lcst, cmd.token2)) { 183 matches = false; 184 } else { 185 tokensMatched = 2; 186 } 187 } 188 189 // Check token3 190 if (matches && !cmd.token3.isEmpty() && !cmd.token3.equals(" ")) { 191 lcst = lcst.nextSolidToken(); 192 if (!tokenMatches(lcst, cmd.token3)) { 193 matches = false; 194 } else { 195 tokensMatched = 3; 196 } 197 } 198 199 // Check token4 200 if (matches && !cmd.token4.isEmpty() && !cmd.token4.equals(" ")) { 201 lcst = lcst.nextSolidToken(); 202 if (!tokenMatches(lcst, cmd.token4)) { 203 matches = false; 204 } else { 205 tokensMatched = 4; 206 } 207 } 208 209 // Check token5 210 if (matches && !cmd.token5.isEmpty() && !cmd.token5.equals(" ")) { 211 lcst = lcst.nextSolidToken(); 212 if (!tokenMatches(lcst, cmd.token5)) { 213 matches = false; 214 } else { 215 tokensMatched = 5; 216 } 217 } 218 219 // Check token6 220 if (matches && !cmd.token6.isEmpty() && !cmd.token6.equals(" ")) { 221 lcst = lcst.nextSolidToken(); 222 if (!tokenMatches(lcst, cmd.token6)) { 223 matches = false; 224 } else { 225 tokensMatched = 6; 226 } 227 } 228 229 // Check token7 230 if (matches && !cmd.token7.isEmpty() && !cmd.token7.equals(" ")) { 231 lcst = lcst.nextSolidToken(); 232 if (!tokenMatches(lcst, cmd.token7)) { 233 matches = false; 234 } else { 235 tokensMatched = 7; 236 } 237 } 238 239 // If this command matches and has more tokens than our current best match, update it 240 if (matches && tokensMatched > maxTokensMatched) { 241 bestMatch = cmd; 242 maxTokensMatched = tokensMatched; 243 244 // If this command ends with an empty token (exact match), we can return immediately 245 // as no longer match is possible 246 if ((tokensMatched == 2 && cmd.token3.isEmpty()) || 247 (tokensMatched == 3 && cmd.token4.isEmpty()) || 248 (tokensMatched == 4 && cmd.token5.isEmpty()) || 249 (tokensMatched == 5 && cmd.token6.isEmpty()) || 250 (tokensMatched == 6 && cmd.token7.isEmpty()) || 251 (tokensMatched == 7)) { 252 return bestMatch; 253 } 254 } 255 } 256 257 return bestMatch; 258 } 259 260 /** 261 * Check if a token matches a pattern string. 262 * Pattern can be "*" (any), or a literal string. 263 */ 264 private boolean tokenMatches(TSourceToken token, String pattern) { 265 if (token == null) return false; 266 if (pattern.equals("*")) return true; 267 268 // Check literal match (case-insensitive) 269 return token.toString().equalsIgnoreCase(pattern); 270 } 271 272 @Override 273 public ESqlStatementType getStatementTypeForToken(TSourceToken token) { 274 ensureInitialized(); 275 TSqlCmd cmd = finddbcmd(token, sqlCmdList); 276 return (cmd != null) ? cmd.sqlstatementtype : ESqlStatementType.sstinvalid; 277 } 278 279 /** 280 * Detect Common Table Expression (CTE) for all database vendors. 281 * Handles WITH clauses before DELETE, INSERT, SELECT, UPDATE, MERGE. 282 * 283 * This is a shared method used by all vendor implementations since CTE 284 * syntax is similar across vendors (standardized by SQL:1999). 285 * 286 * @param ptoken The WITH token 287 * @return Statement object for the CTE query, or null if not a valid CTE 288 */ 289 protected TCustomSqlStatement findcte(TSourceToken ptoken) { 290 TCustomSqlStatement ret = null; 291 TSourceToken lctoken = null; 292 int lcnested = 0, k, j; 293 boolean inXmlNamespaces = false; 294 boolean isXmlNamespaces = false; 295 296 int lcpos = ptoken.posinlist; 297 TSourceTokenList lcsourcetokenlist = ptoken.container; 298 299 for (int i = lcpos + 1; i < lcsourcetokenlist.size(); i++) { 300 lctoken = lcsourcetokenlist.get(i); 301 302 // Handle XML namespaces (for SQL Server compatibility) 303 if (lctoken.tokencode == TBaseType.rrw_xmlnamespaces) { 304 inXmlNamespaces = true; 305 lcnested = 0; 306 continue; 307 } 308 309 if (inXmlNamespaces) { 310 if (lctoken.tokentype == ETokenType.ttleftparenthesis) lcnested++; 311 if (lctoken.tokentype == ETokenType.ttrightparenthesis) { 312 lcnested--; 313 if (lcnested == 0) { 314 inXmlNamespaces = false; 315 isXmlNamespaces = true; 316 } 317 } 318 continue; 319 } 320 321 // Look for AS keyword or after XML namespaces 322 if ((lctoken.tokencode == TBaseType.rrw_as) || isXmlNamespaces) { 323 lcnested = 0; 324 int startPos = i + 1; 325 if (isXmlNamespaces) startPos = i; 326 327 for (j = startPos; j < lcsourcetokenlist.size(); j++) { 328 lctoken = lcsourcetokenlist.get(j); 329 if (lctoken.isnonsolidtoken()) continue; 330 if (lctoken.tokentype == ETokenType.ttleftparenthesis) lcnested++; 331 if (lctoken.tokentype == ETokenType.ttrightparenthesis) lcnested--; 332 333 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_delete)) { 334 ret = new TDeleteSqlStatement(vendor); 335 ret.isctequery = true; 336 gnewsqlstatementtype = ESqlStatementType.sstdelete; 337 break; 338 } 339 340 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_merge)) { 341 ret = new TMergeSqlStatement(vendor); 342 ret.isctequery = true; 343 gnewsqlstatementtype = ESqlStatementType.sstmerge; 344 break; 345 } 346 347 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_insert)) { 348 ret = new TInsertSqlStatement(vendor); 349 ret.isctequery = true; 350 gnewsqlstatementtype = ESqlStatementType.sstinsert; 351 ret.dummytag = 1; // select stmt in insert is permitted 352 353 for (k = lctoken.posinlist + 1; k < lcsourcetokenlist.size(); k++) { 354 if (lcsourcetokenlist.get(k).isnonsolidtoken()) continue; 355 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_values) break; 356 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_go) break; 357 if (lcsourcetokenlist.get(k).tokentype == ETokenType.ttsemicolon) break; 358 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_select) break; 359 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_teradata_sel) break; 360 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_execute) break; 361 if (lcsourcetokenlist.get(k).tokencode == TBaseType.rrw_exec) break; 362 } 363 if (k > lcsourcetokenlist.size() - 1) 364 k = lcsourcetokenlist.size() - 1; 365 366 for (int m = lctoken.posinlist + 1; m <= k; m++) { 367 lcsourcetokenlist.get(m).tokenstatus = ETokenStatus.tsignoredbygetrawstatement; 368 } 369 370 break; 371 } 372 373 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_values) && (vendor == EDbVendor.dbvpostgresql)) { 374 ret = new TSelectSqlStatement(vendor); 375 ret.isctequery = true; 376 gnewsqlstatementtype = ESqlStatementType.sstselect; 377 break; 378 } 379 380 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_select)) { 381 ret = new TSelectSqlStatement(vendor); 382 ret.isctequery = true; 383 gnewsqlstatementtype = ESqlStatementType.sstselect; 384 break; 385 } 386 387 // Teradata SEL keyword (abbreviation for SELECT) 388 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_teradata_sel)) { 389 ret = new TSelectSqlStatement(vendor); 390 ret.isctequery = true; 391 gnewsqlstatementtype = ESqlStatementType.sstselect; 392 break; 393 } 394 395 if ((lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_update)) { 396 ret = new TUpdateSqlStatement(vendor); 397 ret.isctequery = true; 398 ret.dummytag = 1; // means set clause in update is not found yet 399 gnewsqlstatementtype = ESqlStatementType.sstupdate; 400 break; 401 } 402 403 if ((vendor == EDbVendor.dbvhive) && (lcnested == 0) && (lctoken.tokencode == TBaseType.rrw_from)) { 404 TSourceToken cmdToken = lctoken.searchToken(TBaseType.rrw_insert, 3); 405 if (cmdToken != null) { 406 ret = new TInsertSqlStatement(vendor); 407 ret.isctequery = true; 408 gnewsqlstatementtype = ESqlStatementType.sstinsert; 409 } else { 410 ret = new TSelectSqlStatement(vendor); 411 ret.isctequery = true; 412 gnewsqlstatementtype = ESqlStatementType.ssthiveFromQuery; 413 } 414 break; 415 } 416 } 417 418 // Mark CTE tokens as ignored for raw statement processing 419 if (ret != null) { 420 for (k = lcpos + 1; k <= j; k++) { 421 lcsourcetokenlist.get(k).tokenstatus = ETokenStatus.tsignoredbygetrawstatement; 422 } 423 break; 424 } 425 } 426 } 427 428 return ret; 429 } 430}