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}