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}