001package gudusoft.gsqlparser.nodes;
002
003import gudusoft.gsqlparser.*;
004
005import java.util.ArrayList;
006
007public class TTableFunction extends TFunctionCall implements IRelation {
008    ArrayList<TAttributeNode> relationAttributes = new ArrayList<>();
009
010    @Override
011    public int size(){
012        return relationAttributes.size();
013    }
014
015    @Override
016    public ArrayList<TAttributeNode> getAttributes(){
017        return relationAttributes;
018    };
019
020    @Override
021    public String getRelationName(){
022        return this.getFunctionName().toString();
023    };
024
025   public void initAttributes(){
026        if (fieldDefs == null) return;
027        int c = 0;
028        TResultColumn rc = null;
029        for(TColumnDefinition cd:fieldDefs){
030            if (fieldValues != null){
031                rc = fieldValues.getResultColumn(c);
032                c++;
033            }
034            TAttributeNode node = new TAttributeNode(cd.getColumnName().toString(),null,cd,rc);
035            //relationAttributes.add(node);
036            TAttributeNode.addNodeToList(node,relationAttributes);
037        }
038    }
039
040
041
042
043    public void init(Object arg1,Object arg2,Object arg3){
044        init(arg1,arg2);
045        switch (functionType){
046            case struct_t:
047                this.fieldValues = (TResultColumnList)arg3;
048                break;
049        }
050    }
051
052    public void init(Object arg1,Object arg2,Object arg3,Object arg4){
053        init(arg1,arg2,arg3);
054        switch (functionType){
055            case struct_t:
056                this.fieldDefs = (TColumnDefinitionList) arg4;
057                break;
058        }
059    }
060
061    public void doParse(TCustomSqlStatement psql, ESqlClause plocation){
062        this.dbvendor = psql.dbvendor;
063        switch(this.getFunctionType()){
064            case unknown_t:
065            case chr_t:
066            case udf_t:
067            case builtin_t:
068            case listagg_t:
069            case year_t:
070                if (Args != null){
071                    boolean check_first_arg = true;
072                    if ((psql.dbvendor == EDbVendor.dbvsybase)||(psql.dbvendor == EDbVendor.dbvmssql)){
073                        check_first_arg = !((functionName.toString().equalsIgnoreCase("dateadd"))
074                                ||(functionName.toString().equalsIgnoreCase("datediff"))
075                                ||(functionName.toString().equalsIgnoreCase("datename"))
076                                ||(functionName.toString().equalsIgnoreCase("datepart"))
077                        );
078                    }
079                    for(int i=0;i<Args.size();i++){
080                        if ((!check_first_arg)&&(i==0)) {
081                            // mark the first argument in datediff to
082                            setFirstArgAsDateTimePart(i);
083                            continue;
084                        }
085                        Args.getExpression(i).doParse(psql,plocation);
086                    }
087                }
088
089                if (psql.dbvendor == EDbVendor.dbvmssql){
090                    // check OGC function of sql server
091//                    System.out.println("func:"+functionName.toString());
092                    if (isSQLServerOGCMethod(functionName.getObjectToken())){
093                        if (functionName.getSchemaToken() != null){
094                            functionName.getSchemaToken().setDbObjType(TObjectName.ttobjColumn);
095                            if (functionName.getDatabaseToken() != null){
096                                functionName.getDatabaseToken().setDbObjType(TObjectName.ttobjTable);
097                            }
098
099                            TObjectName objectName = new TObjectName();
100                            objectName.init(functionName.getDatabaseToken(),functionName.getSchemaToken());
101                            objectName.setGsqlparser(psql.getGsqlparser()); // this will make toString work correctly
102                            psql.linkColumnReferenceToTable(objectName,plocation);
103                            psql.linkColumnToTable(objectName,plocation);
104                        }
105                    }else if (isSQLServerFunctionsONXMLColumn()) {
106
107
108
109                    }
110                } else if (psql.dbvendor == EDbVendor.dbvoracle){
111                    if (plocation == ESqlClause.spAssignValue){
112                        if (functionName.getNumberOfPart() == 3){
113                            // schema1.pkg1.GETCUSTOMERNAME(2)
114                            functionName.setPackageToken(functionName.getSchemaToken());
115                            functionName.setSchemaToken(functionName.getDatabaseToken());
116                            functionName.setServerToken(null);
117                        }
118                    }
119                } else if (psql.dbvendor == EDbVendor.dbvteradata){
120                    // Handle NPath function - extract ON clause tables for data lineage
121                    if (functionName != null && functionName.toString().equalsIgnoreCase("NPath")){
122                        analyzeTeradataNPathOnClause(psql, plocation);
123                    }
124                }
125                break;
126
127            case xmlquery_t:
128                if (xmlPassingClause != null){
129                    xmlPassingClause.doParse(psql,plocation);
130                }
131                break;
132
133            case struct_t:
134                if (fieldValues != null){
135                    fieldValues.doParse(psql,plocation);
136                }
137                if (fieldDefs != null){
138                    fieldDefs.doParse(psql,plocation);
139                }
140                break;
141            case if_t:
142                expr1.doParse(psql,plocation);
143                expr2.doParse(psql,plocation);
144                expr3.doParse(psql,plocation);
145                break;
146            case array_t: // bigquery array (subquery)
147                this.getArgs().getExpression(0).doParse(psql,plocation);
148                break;
149            case array_agg_t:
150                this.getArgs().getExpression(0).doParse(psql,plocation);
151                break;
152            default:;
153        }
154
155        if (withinGroup != null){
156            withinGroup.doParse(psql,plocation);
157        }
158        if (analyticFunction != null){
159            analyticFunction.doParse(psql,plocation);
160        }
161
162        if (filterClause != null){
163            filterClause.doParse(psql,plocation);
164        }
165
166        if (windowDef != null){
167            windowDef.doParse(psql,plocation);
168        }
169    }
170
171    public void accept(TParseTreeVisitor v){
172        v.preVisit(this);
173        v.postVisit(this);
174    }
175
176    public void acceptChildren(TParseTreeVisitor v){
177        v.preVisit(this);
178        this.getFunctionName().acceptChildren(v);
179        switch(this.getFunctionType()){
180            case unknown_t:
181                if (this.getArgs() != null){
182                    this.getArgs().acceptChildren(v);
183                }
184                break;
185            case udf_t:
186                if (this.getArgs() != null){
187                    this.getArgs().acceptChildren(v);
188                }
189                break;
190            case case_n_t:
191                this.getArgs().acceptChildren(v);
192                break;
193
194            case xmlquery_t:
195                break;
196            case xmlcast_t:
197                break;
198            case match_against_t:
199                //for(int i=0;i<this.getMatchColumns().size();i++){
200                //    psql.linkColumnReferenceToTable(this.getMatchColumns().getObjectName(i),plocation);
201                //}
202                this.getAgainstExpr().acceptChildren(v);
203                break;
204            case struct_t:
205                if (fieldValues != null){
206                    fieldValues.acceptChildren(v);
207                }
208                if (fieldDefs != null){
209                    fieldDefs.acceptChildren(v);
210                }
211                break;
212
213            default:
214                if (this.getArgs() != null){
215                    this.getArgs().acceptChildren(v);
216                }
217                break;
218        }
219
220        if (this.getAnalyticFunction() != null){
221            this.getAnalyticFunction().acceptChildren(v);
222        }
223
224        if (this.getFilterClause() != null){
225            this.getFilterClause().acceptChildren(v);
226        }
227
228        if (this.getWindowDef()  != null){
229            this.getWindowDef().acceptChildren(v);
230        }
231
232        v.postVisit(this);
233
234    }
235
236    /**
237     * Extracts and analyzes tables from Teradata NPath function's ON clause.
238     * The NPath function syntax is: NPath(ON table_or_subquery PARTITION BY ... USING ...)
239     * This method parses the ON clause to extract table references for data lineage.
240     */
241    private void analyzeTeradataNPathOnClause(TCustomSqlStatement psql, ESqlClause plocation) {
242        String funcText = this.toString();
243        if (funcText == null || funcText.isEmpty()) {
244            return;
245        }
246
247        // Find the ON clause
248        String upperFuncText = funcText.toUpperCase();
249        int onIndex = upperFuncText.indexOf(" ON ");
250        if (onIndex < 0) {
251            onIndex = upperFuncText.indexOf("(ON ");
252            if (onIndex >= 0) {
253                onIndex++; // Skip the opening parenthesis
254            }
255        }
256        if (onIndex < 0) {
257            return;
258        }
259
260        // Extract text after ON
261        String afterOn = funcText.substring(onIndex + 4).trim();
262        if (afterOn.isEmpty()) {
263            return;
264        }
265
266        try {
267            if (afterOn.startsWith("(")) {
268                // It's a subquery - find matching closing parenthesis
269                int depth = 0;
270                int endPos = -1;
271                for (int i = 0; i < afterOn.length(); i++) {
272                    char c = afterOn.charAt(i);
273                    if (c == '(') depth++;
274                    else if (c == ')') {
275                        depth--;
276                        if (depth == 0) {
277                            endPos = i + 1;
278                            break;
279                        }
280                    }
281                }
282                if (endPos > 0) {
283                    String subqueryText = afterOn.substring(0, endPos);
284                    // Parse the subquery to extract tables
285                    TGSqlParser subParser = new TGSqlParser(EDbVendor.dbvteradata);
286                    subParser.sqltext = "SELECT * FROM " + subqueryText + " __npath_on";
287                    if (subParser.parse() == 0 && subParser.sqlstatements.size() > 0) {
288                        gudusoft.gsqlparser.stmt.TSelectSqlStatement subSelect =
289                            (gudusoft.gsqlparser.stmt.TSelectSqlStatement) subParser.sqlstatements.get(0);
290                        if (subSelect.tables != null && subSelect.tables.size() > 0) {
291                            TTable onTable = subSelect.tables.getTable(0);
292                            // If it has a subquery, analyze its tables
293                            if (onTable.getSubquery() != null) {
294                                for (int i = 0; i < onTable.getSubquery().tables.size(); i++) {
295                                    TTable sourceTable = onTable.getSubquery().tables.getTable(i);
296                                    // Add to the NPath function's internal tables list
297                                    addNPathSourceTable(sourceTable, psql);
298                                }
299                            }
300                        }
301                    }
302                }
303            } else {
304                // It's a direct table reference - extract until whitespace or special char
305                StringBuilder tableName = new StringBuilder();
306                for (int i = 0; i < afterOn.length(); i++) {
307                    char c = afterOn.charAt(i);
308                    if (Character.isWhitespace(c) || c == ')' || c == ',') {
309                        break;
310                    }
311                    tableName.append(c);
312                }
313                if (tableName.length() > 0) {
314                    // Parse the table reference
315                    TGSqlParser subParser = new TGSqlParser(EDbVendor.dbvteradata);
316                    subParser.sqltext = "SELECT * FROM " + tableName.toString() + " __npath_on";
317                    if (subParser.parse() == 0 && subParser.sqlstatements.size() > 0) {
318                        gudusoft.gsqlparser.stmt.TSelectSqlStatement subSelect =
319                            (gudusoft.gsqlparser.stmt.TSelectSqlStatement) subParser.sqlstatements.get(0);
320                        if (subSelect.tables != null && subSelect.tables.size() > 0) {
321                            TTable sourceTable = subSelect.tables.getTable(0);
322                            addNPathSourceTable(sourceTable, psql);
323                        }
324                    }
325                }
326            }
327        } catch (Exception e) {
328            // Silently ignore parsing errors - the main query still works
329        }
330    }
331
332    /**
333     * Adds a source table from NPath ON clause to the current statement's table list.
334     */
335    private void addNPathSourceTable(TTable sourceTable, TCustomSqlStatement psql) {
336        if (sourceTable == null || psql == null) {
337            return;
338        }
339        // Mark the table as coming from NPath ON clause
340        sourceTable.setNPathOnClauseTable(true);
341        // Add to the statement's table list
342        if (psql.tables == null) {
343            psql.tables = new TTableList();
344        }
345        psql.tables.addTable(sourceTable);
346    }
347}