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}