001package gudusoft.gsqlparser.nodes;
002
003
004import gudusoft.gsqlparser.EDataType;
005import gudusoft.gsqlparser.ESqlClause;
006import gudusoft.gsqlparser.ESqlStatementType;
007import gudusoft.gsqlparser.TCustomSqlStatement;
008import gudusoft.gsqlparser.nodes.TExpression;
009import gudusoft.gsqlparser.nodes.TParseTreeNode;
010import gudusoft.gsqlparser.nodes.TParseTreeVisitor;
011import gudusoft.gsqlparser.sqlenv.TSQLColumn;
012import gudusoft.gsqlparser.stmt.TSelectSqlStatement;
013
014
015public class TUnnestClause  extends TParseTreeNode {
016
017    private TObjectNameList derivedColumnList = null;
018    private TAliasClause withOffsetAlais;
019
020    public TAliasClause getWithOffsetAlais() {
021        return withOffsetAlais;
022    }
023
024    public TDummy getWithOffset() {
025        return withOffset;
026    }
027
028    private TDummy withOffset;
029
030    /** Presto/Trino/Athena {@code UNNEST(...) WITH ORDINALITY}: the last alias column is the 1-based position. */
031    private TDummy withOrdinality;
032
033    public TDummy getWithOrdinality() {
034        return withOrdinality;
035    }
036
037    public boolean isWithOrdinality() {
038        return withOrdinality != null;
039    }
040
041    public void setWithOrdinality(TDummy withOrdinality) {
042        this.withOrdinality = withOrdinality;
043    }
044    private TObjectNameList columns;
045
046    public TObjectNameList getColumns() {
047        return columns;
048    }
049
050    private TExpression arrayExpr;
051
052    public TExpression getArrayExpr() {
053        return arrayExpr;
054    }
055
056    private TExpressionList exprList;
057
058    /**
059     * All UNNEST arguments as expressions (presto/trino), e.g.
060     * <code>UNNEST(array[1,2], array[3,4])</code> or
061     * <code>UNNEST(SPLIT(c, ','))</code>. Plain column arguments are also
062     * mirrored into {@link #getColumns()} for backward compatibility.
063     *
064     * @return the argument expression list, or null for the legacy forms
065     */
066    public TExpressionList getExprList() {
067        return exprList;
068    }
069
070    public  void init(Object arg1){
071        if (arg1 instanceof TExpression){
072            //bigquery, athena
073            arrayExpr = (TExpression)arg1;
074        }else if (arg1 instanceof TObjectNameList){
075            //presto,bigquery
076            columns = (TObjectNameList)arg1;
077        }else if (arg1 instanceof TExpressionList){
078            //presto/trino: UNNEST takes arbitrary array-typed expressions
079            exprList = (TExpressionList)arg1;
080            for(int i=0;i<exprList.size();i++){
081                TExpression expr = exprList.getExpression(i);
082                if ((expr.getExpressionType() == gudusoft.gsqlparser.EExpressionType.simple_object_name_t)
083                        && (expr.getObjectOperand() != null)
084                        // placeholders (? / :b) are simple_object_name_t too but
085                        // are not columns and must not be linked to a source table
086                        && !isPlaceholderName(expr.getObjectOperand())){
087                    if (columns == null){
088                        columns = new TObjectNameList();
089                    }
090                    columns.addObjectName(expr.getObjectOperand());
091                }
092            }
093        }
094
095    }
096
097    public  void init(Object arg1, Object arg2){
098        init(arg1);
099        this.withOffset = (TDummy)arg2;
100        if (this.withOffset != null){
101            if (this.withOffset.int1 == 2){
102                this.withOffsetAlais = (TAliasClause)this.withOffset.node1;
103            }
104        }
105    }
106
107    private static boolean isPlaceholderName(TObjectName name){
108        if (name.getObjectType() == TObjectName.ttobjPositionalParameters) return true;
109        String text = name.toString();
110        // non-identifier-compare: placeholder markers, not database object names
111        return text.equals("?") || text.startsWith(":");
112    }
113
114    public TObjectNameList getDerivedColumnList() {
115        return derivedColumnList;
116    }
117
118    public void doParse(TCustomSqlStatement psql, ESqlClause plocation)
119    {
120        if (exprList != null){
121            // presto/trino: non-column arguments (function calls, array
122            // constructors, ...) resolve through the generic expression path;
123            // plain column arguments are mirrored in columns and linked below
124            for(int i=0;i<exprList.size();i++){
125                TExpression expr = exprList.getExpression(i);
126                if (expr.getExpressionType() != gudusoft.gsqlparser.EExpressionType.simple_object_name_t){
127                    expr.doParse(psql,plocation);
128                }
129            }
130        }
131        if (arrayExpr != null){
132            arrayExpr.doParse(psql,plocation);
133        }else if (columns != null){
134            // link to the first table of the from clause
135            TTable lcFirstTable = null;
136            for(TObjectName pColumn:columns){
137                if (psql.getTables().size() > 0){
138                    lcFirstTable = psql.getTables().getTable(0);
139                    lcFirstTable.getLinkedColumns().addObjectName(pColumn);
140                    pColumn.setSourceTable(lcFirstTable);
141                }else if ((psql.getParentStmt() != null) && (psql.getParentStmt().sqlstatementtype == ESqlStatementType.sstselect)){
142                    // search up level
143                    TSelectSqlStatement selectSqlStatement = (TSelectSqlStatement)psql.getParentStmt();
144                    if (selectSqlStatement.getTables().size()>0){
145                        lcFirstTable = selectSqlStatement.getTables().getTable(0);
146                        lcFirstTable.getLinkedColumns().addObjectName(pColumn);
147                        pColumn.setSourceTable(lcFirstTable);
148                    }
149                }
150
151                // get derived columns from this column if it's type of struct<>
152                // create table absolute-runner-302907.gudu_sqlflow.ADDRESS_NESTED (
153                //      Emp_id INT64,Name STRING
154                //      ,Address ARRAY<STRUCT<State STRING, City STRING, Zipcode INT64>> );
155
156                if ((psql.getSqlEnv() != null)&&(lcFirstTable != null)&&(lcFirstTable.getFullName()!=null)) {
157                   // System.out.println("env\n"+psql.getSqlEnv().toString());
158
159                    TSQLColumn tsqlColumn = psql.getSqlEnv().getColumnInTable(lcFirstTable.getFullName(), pColumn.toString());
160                    if (tsqlColumn != null){
161                        TTypeName columnDataType = tsqlColumn.getColumnDataType();
162                        if (columnDataType != null){
163                          if (columnDataType.getDataType() == EDataType.array_t){
164                              TTypeName subDataType = columnDataType.getTypeOfList();
165                              if (subDataType.getDataType() == EDataType.struct_t){
166                                  if (derivedColumnList == null){
167                                      derivedColumnList = new TObjectNameList();
168                                  }
169                                  //System.out.println("element name in struct: "+subDataType.getDataTypeName());
170                                  for(int i=0;i<subDataType.getColumnDefList().size();i++){
171                                      //System.out.println(subDataType.getColumnDefList().getColumn(i).getColumnName().toString());
172                                      TObjectName attributeName = subDataType.getColumnDefList().getColumn(i).getColumnName();
173                                      attributeName.setParentObjectName(pColumn);
174                                      derivedColumnList.addObjectName(attributeName);
175                                  }
176                              }
177                          }
178                          else if (columnDataType.getDataType() == EDataType.struct_t){
179                              if (derivedColumnList == null){
180                                  derivedColumnList = new TObjectNameList();
181                              }
182                              //System.out.println("element name in struct: "+subDataType.getDataTypeName());
183                              for(int i=0;i<columnDataType.getColumnDefList().size();i++){
184                                  //System.out.println(subDataType.getColumnDefList().getColumn(i).getColumnName().toString());
185                                  TObjectName attributeName = columnDataType.getColumnDefList().getColumn(i).getColumnName();
186                                  attributeName.setParentObjectName(pColumn);
187                                  derivedColumnList.addObjectName(attributeName);
188                              }
189                          }
190                        }
191                        //System.out.println("Find column: "+pColumn.toString()+" in table: "+ lcFirstTable.getFullName().toString()+" with datatype:"+tsqlColumn.getColumnDataType());
192                    }
193                }
194
195            }
196        }
197    }
198
199    public void accept(TParseTreeVisitor v){
200        v.preVisit(this);
201        v.postVisit(this);
202    }
203
204    public void acceptChildren(TParseTreeVisitor v){
205        v.preVisit(this);
206        if (this.getExprList() != null){
207            // exprList contains every argument (columns included); visiting
208            // both it and getColumns() would visit the same nodes twice
209            this.getExprList().acceptChildren(v);
210        }else if (this.getColumns() != null){
211            this.getColumns().acceptChildren(v);
212        }
213        if (this.getArrayExpr() != null){
214            this.getArrayExpr().acceptChildren(v);
215        }
216        v.postVisit(this);
217    }
218}