001package gudusoft.gsqlparser.resolver2.namespace; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.EExpressionType; 005import gudusoft.gsqlparser.ETableSource; 006import gudusoft.gsqlparser.nodes.TExpression; 007import gudusoft.gsqlparser.nodes.TExpressionList; 008import gudusoft.gsqlparser.nodes.TFunctionCall; 009import gudusoft.gsqlparser.nodes.TLateralView; 010import gudusoft.gsqlparser.nodes.TObjectName; 011import gudusoft.gsqlparser.nodes.TObjectNameList; 012import gudusoft.gsqlparser.nodes.TParseTreeNode; 013import gudusoft.gsqlparser.nodes.TTable; 014import gudusoft.gsqlparser.nodes.TUnnestClause; 015import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 016import gudusoft.gsqlparser.sqlenv.TFunctoinColumn; 017import gudusoft.gsqlparser.sqlenv.TSQLEnv; 018import gudusoft.gsqlparser.sqlenv.TSQLFunction; 019import gudusoft.gsqlparser.stmt.TCreateFunctionStmt; 020import gudusoft.gsqlparser.stmt.TSelectSqlStatement; 021import gudusoft.gsqlparser.util.SQLUtil; 022 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.List; 026 027/** 028 * The columns of a FROM item that a call generates (a built-in table function, {@code UNNEST}, a Hive-family 029 * {@code LATERAL VIEW}), and which of the call's arguments each one is computed from. One rule for every consumer: 030 * {@link TableNamespace} takes a function's column names from it, and {@code TTable.getLateralColumnNames()} and its 031 * siblings publish the whole layout to the SQL-LR producer (plan 09 slice 11). 032 * 033 * <p>A column's inputs are the indexes, in {@link #getArguments()}, of the arguments whose values make its value 034 * ({@code "0,1"}); the empty string says the column is computed over the rows the call generates and has no value of 035 * an argument in it (an ordinal: {@code WITH ORDINALITY}, {@code WITH OFFSET}, FLATTEN's {@code SEQ} and 036 * {@code INDEX}, {@code posexplode}'s position). {@link #getRowInputs()} lists the arguments whose values decide how 037 * many rows the call generates. An argument that is in neither (a FLATTEN option) feeds nothing: the layout never says 038 * "every argument flows into every column".</p> 039 * 040 * <p>A call whose columns the vendor fixes has a layout: an unqualified built-in of the vendor 041 * ({@code myschema.flatten(...)} is a user function that shares the name). So does a user table function the catalog 042 * declares before the call ({@link #declaredColumns}, recorded on the call by {@link #recordDeclared}): its names only, 043 * which arguments make them and decide its rows being its body's. Any other function, a call with a shape the rule 044 * below does not settle (STRING_SPLIT with a non-constant {@code enable_ordinal}), has none. A call 045 * whose rows are settled but not its columns (an {@code UNNEST} of several arrays without a column list naming one 046 * column per array) has a layout without names: its rows still come from its arrays.</p> 047 * 048 * @since 4.2.17 049 */ 050public final class TableFunctionLayout { 051 052 /** The inputs of a column computed over the generated rows (an ordinal), with no argument value in it. */ 053 public static final String OVER_ROWS = ""; 054 055 private final List<String> names; 056 private final List<String> inputs; 057 private final String rowInputs; 058 private final boolean closed; 059 private final List<TParseTreeNode> arguments; 060 061 private TableFunctionLayout(List<String> names, List<String> inputs, String rowInputs, boolean closed, 062 List<? extends TParseTreeNode> arguments) { 063 this.names = names == null ? null : Collections.unmodifiableList(names); 064 this.inputs = inputs == null ? null : Collections.unmodifiableList(inputs); 065 this.rowInputs = rowInputs; 066 this.closed = closed; 067 this.arguments = Collections.unmodifiableList(new ArrayList<TParseTreeNode>(arguments)); 068 } 069 070 /** 071 * The column names, in order; an element is null for a column the vendor leaves without a name a query can use 072 * (BigQuery's element of an unaliased {@code UNNEST}). Null when only the rows are settled (an {@code UNNEST} of 073 * several arrays without a column list for each): the call then has no column this rule names. 074 */ 075 public List<String> getNames() { 076 return names; 077 } 078 079 /** 080 * Per column, the arguments whose values make it ({@code "0,1"}), or {@link #OVER_ROWS}; null when the names are 081 * settled but the arguments are not (a FLATTEN call whose arguments the rule does not read). 082 */ 083 public List<String> getInputs() { 084 return inputs; 085 } 086 087 /** The arguments whose values decide the generated rows ({@code "0,1"}; empty when none). */ 088 public String getRowInputs() { 089 return rowInputs; 090 } 091 092 /** 093 * Whether the columns listed are all the call's columns: false when the element type of an array is not known 094 * (an {@code UNNEST} of a column may hold a STRUCT or ROW whose fields are columns of their own). 095 */ 096 public boolean isClosed() { 097 return closed; 098 } 099 100 /** 101 * The arguments, in order: the call's own expressions, or the arrays of an {@code UNNEST} (an expression, or the 102 * column name of BigQuery's and Presto's {@code UNNEST(t.arr)}). 103 */ 104 public List<TParseTreeNode> getArguments() { 105 return arguments; 106 } 107 108 /** 109 * The layout of a FROM item, or null when the item is not a call whose columns or rows this rule settles. A layout 110 * whose {@link #getNames()} is null settles the rows only. 111 */ 112 public static TableFunctionLayout of(TTable table) { 113 if (table == null) { 114 return null; 115 } 116 ETableSource type = table.getTableType(); 117 if (type == ETableSource.function) { 118 return function(table); 119 } 120 if (type == ETableSource.unnest) { 121 return unnest(table); 122 } 123 if (type == ETableSource.lateralView) { 124 return lateralView(table); 125 } 126 return null; 127 } 128 129 /** 130 * The names of a table function's columns before an alias column list renames them, or null when the function 131 * has no fixed columns: the names {@link TableNamespace} registers. 132 */ 133 static String[] builtinNames(TTable table) { 134 TableFunctionLayout layout = table == null || table.getTableType() != ETableSource.function ? null 135 : function(table, false); 136 return layout == null || layout.names == null ? null : layout.names.toArray(new String[0]); 137 } 138 139 // --------------------------------------------------------------------------------------------- 140 // Table functions 141 // --------------------------------------------------------------------------------------------- 142 143 /** SQL Server STRING_SPLIT; {@code ordinal} only when enable_ordinal is the constant 1 (SQL Server 2022+). */ 144 private static final String[] STRING_SPLIT = {"value", "ordinal"}; 145 /** Snowflake FLATTEN: https://docs.snowflake.com/en/sql-reference/functions/flatten */ 146 private static final String[] FLATTEN = {"SEQ", "KEY", "PATH", "INDEX", "VALUE", "THIS"}; 147 /** FLATTEN's arguments in their positional order. */ 148 private static final String[] FLATTEN_ARGUMENTS = {"INPUT", "PATH", "OUTER", "RECURSIVE", "MODE"}; 149 /** Snowflake SPLIT_TO_TABLE: https://docs.snowflake.com/en/sql-reference/functions/split_to_table */ 150 private static final String[] SPLIT_TO_TABLE = {"SEQ", "INDEX", "VALUE"}; 151 152 private static TableFunctionLayout function(TTable table) { 153 return function(table, true); 154 } 155 156 // --------------------------------------------------------------------------------------------- 157 // User table functions declared in the input or the catalog 158 // --------------------------------------------------------------------------------------------- 159 160 /** 161 * The columns a CREATE FUNCTION declares its table result to have, in order: a {@code RETURNS TABLE (c ...)} or 162 * {@code RETURNS @t TABLE (c ...)} column list, else the output names of its query (SQL Server's inline 163 * {@code RETURN (SELECT ...)}, BigQuery's {@code CREATE TABLE FUNCTION ... AS SELECT ...}); null when it declares 164 * no table result or a column has no name (a wildcard, an expression without an alias). Registered with the 165 * function in the catalog; a call of it in FROM has these columns ({@link #recordDeclared}). 166 */ 167 public static List<String> declaredColumns(TCreateFunctionStmt function) { 168 if (function == null) { 169 return null; 170 } 171 List<String> names = new ArrayList<String>(); 172 if (function.getReturnTableDefinitions() != null && function.getReturnTableDefinitions().size() > 0) { 173 for (int i = 0; i < function.getReturnTableDefinitions().size(); i++) { 174 gudusoft.gsqlparser.nodes.TTableElement element = function.getReturnTableDefinitions().getTableElement(i); 175 if (element != null && element.getColumnDefinition() != null 176 && element.getColumnDefinition().getColumnName() != null) { 177 names.add(element.getColumnDefinition().getColumnName().toString()); 178 } 179 } 180 return names.isEmpty() ? null : names; 181 } 182 if (postgresFamily(function.dbvendor) && function.getParameterDeclarations() != null) { 183 // PostgreSQL: the OUT and INOUT parameters are the columns of the row the function returns (plan 09 slice 184 // 13f), RETURNS SETOF RECORD included; one of a composite type returns that type's row (its attributes, 185 // laid out from the return type: Codex 13f #3) 186 int outputs = 0; 187 boolean composite = false; 188 for (int i = 0; i < function.getParameterDeclarations().size(); i++) { 189 gudusoft.gsqlparser.nodes.TParameterDeclaration p = 190 function.getParameterDeclarations().getParameterDeclarationItem(i); 191 gudusoft.gsqlparser.EParameterMode mode = p == null ? null : p.getParameterMode(); 192 if (mode == gudusoft.gsqlparser.EParameterMode.out || mode == gudusoft.gsqlparser.EParameterMode.inout) { 193 outputs++; 194 composite = p.getDataType() != null 195 && p.getDataType().getDataType() == gudusoft.gsqlparser.EDataType.generic_t; 196 } 197 } 198 if (outputs == 1 && composite) { 199 return null; 200 } 201 for (int i = 0; i < function.getParameterDeclarations().size(); i++) { 202 gudusoft.gsqlparser.nodes.TParameterDeclaration p = 203 function.getParameterDeclarations().getParameterDeclarationItem(i); 204 gudusoft.gsqlparser.EParameterMode mode = p == null ? null : p.getParameterMode(); 205 if (mode == gudusoft.gsqlparser.EParameterMode.out || mode == gudusoft.gsqlparser.EParameterMode.inout) { 206 if (p.getParameterName() == null) { 207 return null; // an unnamed OUT parameter's column is named by position: not settled here 208 } 209 names.add(p.getParameterName().toString()); 210 } 211 } 212 if (!names.isEmpty()) { 213 return names; 214 } 215 } 216 TSelectSqlStatement query = function.getSqlQuery(); 217 if (query == null && function.getReturnStmt() != null) { 218 query = function.getReturnStmt().getSubquery() != null ? function.getReturnStmt().getSubquery() 219 : function.getReturnStmt().getReturnExpr() != null 220 ? function.getReturnStmt().getReturnExpr().getSubQuery() : null; 221 } 222 while (query != null && query.isCombinedQuery()) { 223 query = query.getLeftStmt(); // a set operation's columns are named by its first query 224 } 225 if (query == null || query.getResultColumnList() == null || query.getResultColumnList().size() == 0) { 226 return null; 227 } 228 for (int i = 0; i < query.getResultColumnList().size(); i++) { 229 gudusoft.gsqlparser.nodes.TResultColumn column = query.getResultColumnList().getResultColumn(i); 230 String name = null; 231 if (column != null && column.getAliasClause() != null && column.getAliasClause().getAliasName() != null) { 232 name = column.getAliasClause().getAliasName().toString(); 233 } else if (column != null && column.getExpr() != null 234 && column.getExpr().getExpressionType() == EExpressionType.simple_object_name_t 235 && column.getExpr().getObjectOperand() != null) { 236 TObjectName ref = column.getExpr().getObjectOperand(); 237 name = ref.getPartToken() != null && ref.getPartToken().tokentype 238 == gudusoft.gsqlparser.ETokenType.ttasterisk ? null : ref.getColumnNameOnly(); 239 } 240 if (name == null || name.isEmpty()) { 241 return null; // a wildcard or an unnamed value: the declared columns are not settled 242 } 243 names.add(name); 244 } 245 return names; 246 } 247 248 /** 249 * Records on a function call in FROM the columns the catalog declares for the function it calls (a CREATE FUNCTION 250 * of the input registered before the call, or the catalog's metadata), so every consumer of the layout reads the 251 * columns resolver2 resolved the call with; nothing for a built-in this rule lays out, or an unknown function. 252 */ 253 public static void recordDeclared(TTable table, TSQLEnv env) { 254 if (table == null || env == null || table.getTableType() != ETableSource.function 255 || table.getDeclaredFunctionColumns() != null || function(table, false) != null) { 256 return; 257 } 258 TFunctionCall call = unwrapTable(table.dbvendor, table.getFuncCall()); 259 if (call == null || call.getFunctionName() == null) { 260 return; 261 } 262 TSQLFunction declared = env.searchFunction(call.getFunctionName()); 263 if (declared == null) { 264 return; 265 } 266 List<String> names = new ArrayList<String>(); 267 if (declared.getColumnsInReturnTable() != null && !declared.getColumnsInReturnTable().isEmpty()) { 268 for (TFunctoinColumn column : declared.getColumnsInReturnTable()) { 269 names.add(column.getNameKeepCase() != null ? column.getNameKeepCase() : column.getName()); 270 } 271 } else if (postgresFamily(table.dbvendor) && declared.getReturnType() != null) { 272 names = rowTypeColumns(table, call, declared.getReturnType(), env); 273 } 274 if (names != null && !names.isEmpty()) { 275 table.setDeclaredFunctionColumns(names); 276 } 277 } 278 279 /** 280 * The columns of a call of a PostgreSQL-family function by its return type (plan 09 slice 13f): a base type gives 281 * one column, named after the call's alias when it has no column list, else after the function; the row type of a 282 * table the catalog holds gives that table's columns. Null for any other type (record, a composite type). 283 */ 284 private static List<String> rowTypeColumns(TTable table, TFunctionCall call, 285 gudusoft.gsqlparser.nodes.TTypeName type, TSQLEnv env) { 286 if (type.getDataType() != gudusoft.gsqlparser.EDataType.generic_t) { 287 String alias = table.getAliasClause() != null && table.getAliasClause().getAliasName() != null 288 && table.getAliasClause().getColumns() == null 289 ? table.getAliasClause().getAliasName().toString() : null; 290 return list(alias != null && !alias.isEmpty() ? alias : unqualified(call.getFunctionName())); 291 } 292 // the type's name parts (name [. name ...]), after SETOF 293 List<gudusoft.gsqlparser.TSourceToken> parts = new ArrayList<gudusoft.gsqlparser.TSourceToken>(); 294 for (gudusoft.gsqlparser.TSourceToken t = type.getStartToken(); t != null; t = t.getNextTokenInChain()) { 295 if (t.tokentype != gudusoft.gsqlparser.ETokenType.ttwhitespace 296 && t.tokentype != gudusoft.gsqlparser.ETokenType.ttreturn 297 && t.tokentype != gudusoft.gsqlparser.ETokenType.ttsimplecomment 298 && t.tokentype != gudusoft.gsqlparser.ETokenType.ttbracketedcomment 299 && !(parts.isEmpty() && "SETOF".equalsIgnoreCase(t.toString()))) { // non-identifier-compare: keyword 300 parts.add(t); 301 } 302 if (t == type.getEndToken()) { 303 break; 304 } 305 } 306 TObjectName name = null; 307 if (parts.size() == 1) { 308 name = TObjectName.createObjectName(table.dbvendor, gudusoft.gsqlparser.EDbObjectType.table, parts.get(0)); 309 } else if (parts.size() == 3 && ".".equals(parts.get(1).toString())) { 310 name = TObjectName.createObjectName(table.dbvendor, gudusoft.gsqlparser.EDbObjectType.table, parts.get(0), 311 parts.get(2)); 312 } 313 gudusoft.gsqlparser.sqlenv.TSQLTable rowType = name == null ? null : env.searchTable(name); 314 if (rowType == null || rowType.getColumnList() == null || rowType.getColumnList().isEmpty()) { 315 return null; 316 } 317 List<String> out = new ArrayList<String>(); 318 for (gudusoft.gsqlparser.sqlenv.TSQLColumn column : rowType.getColumnList()) { 319 out.add(column.getNameKeepCase() != null ? column.getNameKeepCase() : column.getName()); 320 } 321 return out; 322 } 323 324 /** The layout of a call of a declared user function: its declared names, its inputs and rows not settled. */ 325 private static TableFunctionLayout declared(TTable table, TFunctionCall call, boolean renamed) { 326 List<String> names = table.getDeclaredFunctionColumns(); 327 if (names == null || names.isEmpty() || call == null || withOrdinality(table)) { 328 // WITH ORDINALITY adds a column after the declared ones, computed over the call's rows: not laid out here 329 // (plan 09 phase C, Codex 13c #5), so a wildcard over the call is not expanded to the declared ones only 330 return null; 331 } 332 // which arguments make a column, and which decide the rows, is the function body's (the call binding) 333 return rename(table, new ArrayList<String>(names), null, null, true, arguments(call), renamed); 334 } 335 336 /** 337 * @param renamed whether an alias column list renames the leading columns (false: the function's own names) 338 */ 339 private static TableFunctionLayout function(TTable table, boolean renamed) { 340 EDbVendor vendor = table.dbvendor; 341 TFunctionCall call = table.getFuncCall(); 342 if (call == null || call.getFunctionName() == null) { 343 return null; 344 } 345 if (vendor == EDbVendor.dbvsnowflake) { 346 // Snowflake spells its table functions both as LATERAL f(...) and as TABLE(f(...)) 347 TFunctionCall inner = unwrapTable(vendor, call); 348 String name = unqualified(inner.getFunctionName()); 349 if (same(vendor, name, "FLATTEN")) { 350 return flatten(table, inner, renamed); 351 } 352 if (same(vendor, name, "SPLIT_TO_TABLE")) { 353 List<TExpression> args = arguments(inner); 354 if (args.size() != 2) { 355 return null; 356 } 357 return rename(table, list(SPLIT_TO_TABLE), list(OVER_ROWS, OVER_ROWS, "0,1"), "0,1", true, args, 358 renamed); 359 } 360 return declared(table, inner, renamed); 361 } 362 String name = unqualified(call.getFunctionName()); 363 if ((vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql) && same(vendor, name, "STRING_SPLIT")) { 364 List<TExpression> args = arguments(call); 365 if (args.size() == 2) { 366 return rename(table, list("value"), list("0,1"), "0,1", true, args, renamed); 367 } 368 if (args.size() == 3 && isConstant(args.get(2), "1")) { 369 return rename(table, list(STRING_SPLIT), list("0,1", OVER_ROWS), "0,1", true, args, renamed); 370 } 371 if (args.size() == 3 && isConstant(args.get(2), "0")) { 372 return rename(table, list("value"), list("0,1"), "0,1", true, args, renamed); 373 } 374 return null; 375 } 376 if (postgresFamily(vendor)) { 377 if (same(vendor, name, "unnest")) { 378 List<TExpression> args = arguments(call); 379 // one array: its elements, one column; an array of a composite type has the type's columns instead, 380 // so only an array literal of plain items proves the one column a wildcard expands to 381 if (args.size() == 1) { 382 return scalarFunction(table, name, args, list("0"), isArrayLiteral(args.get(0)) ? "" : "0", 383 isScalarArrayLiteral(args.get(0)), renamed); 384 } 385 // several arrays, zipped: as many rows as the longest one has; which composite columns each array 386 // contributes is not settled here, so only the rows are 387 StringBuilder rows = new StringBuilder(); 388 for (int i = 0; i < args.size(); i++) { 389 if (!isArrayLiteral(args.get(i))) { 390 rows.append(rows.length() == 0 ? "" : ",").append(i); 391 } 392 } 393 return args.isEmpty() ? null : new TableFunctionLayout(null, null, rows.toString(), false, args); 394 } 395 if (same(vendor, name, "generate_series")) { 396 List<TExpression> args = arguments(call); 397 if (args.size() != 2 && args.size() != 3) { 398 return null; 399 } 400 // start + k * step, as many rows as fit up to stop 401 return scalarFunction(table, name, args, list(args.size() == 3 ? "0,2" : "0"), 402 args.size() == 3 ? "0,1,2" : "0,1", true, renamed); 403 } 404 } 405 return declared(table, call, renamed); 406 } 407 408 /** The PostgreSQL rule for a function of a base type: its one column (with ORDINALITY, two). */ 409 private static TableFunctionLayout scalarFunction(TTable table, String functionName, List<TExpression> args, 410 List<String> inputs, String rows, boolean closed, boolean renamed) { 411 // a function returning a base type names its one column after the function, or after the table alias when 412 // there is one and no column alias list 413 String alias = table.getAliasClause() != null && table.getAliasClause().getAliasName() != null 414 ? table.getAliasClause().getAliasName().toString() : null; 415 List<String> names = list(renamed && alias != null && !alias.isEmpty() ? alias : functionName); 416 List<String> in = new ArrayList<String>(inputs); 417 if (withOrdinality(table)) { 418 names.add("ordinality"); 419 in.add(OVER_ROWS); 420 } 421 return rename(table, names, in, rows, closed, args, renamed); 422 } 423 424 private static TableFunctionLayout flatten(TTable table, TFunctionCall call, boolean renamed) { 425 List<TExpression> args = arguments(call); 426 int[] at = new int[FLATTEN_ARGUMENTS.length]; 427 java.util.Arrays.fill(at, -1); 428 boolean named = false; 429 for (int i = 0; i < args.size(); i++) { 430 TExpression arg = call.getArgs().getExpression(i); 431 if (arg != null && arg.getExpressionType() == EExpressionType.assignment_t && arg.getLeftOperand() != null) { 432 named = true; 433 String parameter = arg.getLeftOperand().toString(); 434 int k = -1; 435 for (int j = 0; j < FLATTEN_ARGUMENTS.length; j++) { 436 if (FLATTEN_ARGUMENTS[j].equalsIgnoreCase(parameter)) { // non-identifier-compare: FLATTEN's keyword argument names 437 k = j; 438 } 439 } 440 if (k < 0 || at[k] >= 0) { 441 return rename(table, list(FLATTEN), null, null, true, args, renamed); 442 } 443 at[k] = i; 444 } else if (named || i >= FLATTEN_ARGUMENTS.length) { 445 // a positional argument after a named one: its columns are FLATTEN's, its arguments not settled 446 return rename(table, list(FLATTEN), null, null, true, args, renamed); 447 } else { 448 at[i] = i; 449 } 450 } 451 if (at[0] < 0) { 452 return rename(table, list(FLATTEN), null, null, true, args, renamed); 453 } 454 String value = at[1] >= 0 ? at[0] + "," + at[1] : String.valueOf(at[0]); 455 StringBuilder rows = new StringBuilder(); 456 for (int i = 0; i < args.size(); i++) { 457 rows.append(i == 0 ? "" : ",").append(i); 458 } 459 // SEQ numbers the input record and INDEX the element: computed over the rows; KEY, PATH, VALUE and THIS are 460 // parts of the input at the path 461 return rename(table, list(FLATTEN), list(OVER_ROWS, value, value, OVER_ROWS, value, value), rows.toString(), 462 true, args, renamed); 463 } 464 465 // --------------------------------------------------------------------------------------------- 466 // UNNEST 467 // --------------------------------------------------------------------------------------------- 468 469 /** 470 * {@code UNNEST(a1, ...) [WITH ORDINALITY | WITH OFFSET [AS o]] [AS u [(c1, ...)]]}: the elements of each array, 471 * and the ordinal. A column alias list names the columns in order; with one array every column is made from it 472 * (an element of a ROW or STRUCT type is one column per field), with several arrays the list must name one column 473 * per array. Without a list, BigQuery's element is named by the alias (and is a value a query cannot name without 474 * one); its WITH OFFSET column is named by its own alias, else {@code offset}. 475 */ 476 private static TableFunctionLayout unnest(TTable table) { 477 TUnnestClause clause = table.getUnnestClause(); 478 if (clause == null) { 479 return null; 480 } 481 List<TParseTreeNode> arrays = unnestArrays(clause); 482 if (arrays.isEmpty()) { 483 return null; 484 } 485 // an array literal ([1, 2], ARRAY[a, b]) generates one row per item, whatever their values: its rows have no 486 // argument input; an array value (a column, a function's result) decides its rows (with several arrays, the 487 // longest one: every array that is not a literal) 488 StringBuilder rows = new StringBuilder(); 489 for (int i = 0; i < arrays.size(); i++) { 490 if (!(arrays.get(i) instanceof TExpression && isArrayLiteral((TExpression) arrays.get(i)))) { 491 rows.append(rows.length() == 0 ? "" : ",").append(i); 492 } 493 } 494 String rowInputs = rows.toString(); 495 boolean ordinality = clause.isWithOrdinality(); 496 TObjectNameList aliasColumns = table.getAliasClause() != null ? table.getAliasClause().getColumns() : null; 497 List<String> names = new ArrayList<String>(); 498 List<String> inputs = new ArrayList<String>(); 499 List<TParseTreeNode> arguments = new ArrayList<TParseTreeNode>(arrays); 500 if (aliasColumns != null && aliasColumns.size() > 0) { 501 int elements = aliasColumns.size() - (ordinality ? 1 : 0); 502 if (elements < 1 || (arrays.size() > 1 && elements != arrays.size())) { 503 // which array a column comes from is not settled; the rows still come from the arrays 504 return new TableFunctionLayout(null, null, rowInputs, false, arrays); 505 } 506 // one array whose element is several columns (a ROW or STRUCT type): of a literal, each column is made 507 // from its field's own value in every item, never from the other fields 508 TExpression literal = arrays.size() == 1 && elements > 1 && arrays.get(0) instanceof TExpression 509 && isArrayLiteral((TExpression) arrays.get(0)) ? (TExpression) arrays.get(0) : null; 510 for (int i = 0; i < aliasColumns.size(); i++) { 511 names.add(aliasColumns.getObjectName(i) == null ? null : aliasColumns.getObjectName(i).toString()); 512 inputs.add(i >= elements ? OVER_ROWS : literal != null ? fieldParts(literal, i, elements, arguments) 513 : arrays.size() == 1 ? "0" : String.valueOf(i)); 514 } 515 if (inputs.contains(null)) { 516 return new TableFunctionLayout(names, null, rowInputs, true, arrays); // a part not settled: names only 517 } 518 return new TableFunctionLayout(names, inputs, rowInputs, true, arguments); 519 } 520 if (arrays.size() > 1) { 521 return new TableFunctionLayout(null, null, rowInputs, false, arrays); 522 } 523 TExpression literal = arrays.get(0) instanceof TExpression && isArrayLiteral((TExpression) arrays.get(0)) 524 ? (TExpression) arrays.get(0) : null; 525 // an element whose STRUCT fields are known (STRUCT literals, a typed ARRAY<STRUCT<...>> literal: the rule 526 // resolver2 resolves the fields by, TSelectSqlStatement.unnestStructFieldNames) is one column per field, each a 527 // part of the array's element: of a literal, the field's own value in every item (appended to the arguments), 528 // never the other fields' 529 List<String> fields = gudusoft.gsqlparser.stmt.TSelectSqlStatement.unnestStructFieldNames(table); 530 boolean structKnown = fields != null && !fields.isEmpty(); 531 String alias = table.getAliasName(); 532 if (structKnown) { 533 for (int f = 0; f < fields.size(); f++) { 534 names.add(fields.get(f)); 535 inputs.add(literal == null ? "0" : fieldParts(literal, f, fields.size(), arguments)); 536 } 537 if (inputs.contains(null)) { 538 return new TableFunctionLayout(names, null, null, true, arrays); // a part not settled: names only 539 } 540 } else { 541 names.add(alias != null && !alias.isEmpty() ? alias : null); 542 inputs.add("0"); 543 } 544 if (ordinality) { 545 names.add("ordinality"); 546 inputs.add(OVER_ROWS); 547 } 548 if (clause.getWithOffset() != null) { 549 names.add(clause.getWithOffsetAlais() != null && clause.getWithOffsetAlais().getAliasName() != null 550 ? clause.getWithOffsetAlais().getAliasName().toString() : "offset"); 551 inputs.add(OVER_ROWS); 552 } 553 // an element of a STRUCT or ROW type is one column per field: only an array literal of plain items proves the 554 // element is one column 555 return new TableFunctionLayout(names, inputs, rowInputs, structKnown 556 || (arrays.get(0) instanceof TExpression && isScalarArrayLiteral((TExpression) arrays.get(0))), 557 arguments); 558 } 559 560 /** 561 * The arguments field {@code f} of a literal array of STRUCTs is made from: the f-th value of every item (a STRUCT 562 * literal's field value, a typed array's tuple item), appended to {@code arguments}; null when an item's parts are 563 * not {@code width} plain values. 564 */ 565 private static String fieldParts(TExpression literal, int f, int width, List<TParseTreeNode> arguments) { 566 StringBuilder out = new StringBuilder(); 567 TExpressionList items = literal.getExprList(); 568 for (int i = 0; items != null && i < items.size(); i++) { 569 TExpression item = items.getExpression(i); 570 TExpression part = null; 571 if (item != null && (item.getExpressionType() == EExpressionType.list_t 572 || item.getExpressionType() == EExpressionType.row_constructor_t) && item.getExprList() != null 573 && item.getExprList().size() == width) { 574 part = item.getExprList().getExpression(f); // ARRAY<STRUCT<x, y>>[(1, 'a')], ARRAY[ROW(1, 'a')] 575 } else if (item != null && item.getFunctionCall() != null 576 && item.getFunctionCall().getFieldValues() != null 577 && item.getFunctionCall().getFieldValues().size() == width 578 && item.getFunctionCall().getFieldValues().getResultColumn(f) != null) { 579 part = item.getFunctionCall().getFieldValues().getResultColumn(f).getExpr(); // STRUCT(1 AS a, 2 AS b) 580 } 581 if (part == null) { 582 return null; 583 } 584 out.append(out.length() == 0 ? "" : ",").append(arguments.size()); 585 arguments.add(part); 586 } 587 return out.length() == 0 ? null : out.toString(); 588 } 589 590 /** 591 * The arrays of an {@code UNNEST}, in order: its expressions, or the column names the legacy form keeps 592 * ({@code UNNEST(t.arr)} of BigQuery and Presto: a column list, no expression). 593 */ 594 public static List<TParseTreeNode> unnestArrays(TUnnestClause clause) { 595 List<TParseTreeNode> arrays = new ArrayList<TParseTreeNode>(); 596 if (clause.getExprList() != null && clause.getExprList().size() > 0) { 597 for (int i = 0; i < clause.getExprList().size(); i++) { 598 arrays.add(clause.getExprList().getExpression(i)); 599 } 600 } else if (clause.getArrayExpr() != null) { 601 arrays.add(clause.getArrayExpr()); 602 } else if (clause.getColumns() != null) { 603 for (int i = 0; i < clause.getColumns().size(); i++) { 604 arrays.add(clause.getColumns().getObjectName(i)); 605 } 606 } 607 return arrays; 608 } 609 610 // --------------------------------------------------------------------------------------------- 611 // LATERAL VIEW 612 // --------------------------------------------------------------------------------------------- 613 614 /** 615 * A Hive-family {@code LATERAL VIEW [OUTER] f(x) v AS c1, ...} of a generator whose columns its column aliases 616 * name: {@code explode} (one alias: an array's element; two: a map's key and value), {@code posexplode} (the 617 * position first), {@code inline} (a STRUCT array's fields), and their {@code _outer} forms. 618 */ 619 private static TableFunctionLayout lateralView(TTable table) { 620 TLateralView view = table.getLateralView(); 621 if (view == null || view.getUdtf() == null || view.getUdtf().getFunctionName() == null) { 622 return null; 623 } 624 TObjectNameList aliases = view.getColumnAliasList(); 625 int n = aliases == null ? 0 : aliases.size(); 626 if (n == 0) { 627 return null; 628 } 629 EDbVendor vendor = table.dbvendor; 630 String name = unqualified(view.getUdtf().getFunctionName()); 631 List<TExpression> args = arguments(view.getUdtf()); 632 if (args.size() != 1 || name == null) { 633 return null; 634 } 635 List<String> inputs = new ArrayList<String>(); 636 if (same(vendor, name, "explode") || same(vendor, name, "explode_outer")) { 637 if (n > 2) { 638 return null; 639 } 640 for (int i = 0; i < n; i++) { 641 inputs.add("0"); 642 } 643 } else if (same(vendor, name, "posexplode") || same(vendor, name, "posexplode_outer")) { 644 if (n < 2 || n > 3) { 645 return null; 646 } 647 inputs.add(OVER_ROWS); 648 for (int i = 1; i < n; i++) { 649 inputs.add("0"); 650 } 651 } else if (same(vendor, name, "inline") || same(vendor, name, "inline_outer")) { 652 for (int i = 0; i < n; i++) { 653 inputs.add("0"); 654 } 655 } else { 656 return null; 657 } 658 List<String> names = new ArrayList<String>(); 659 for (int i = 0; i < n; i++) { 660 names.add(aliases.getObjectName(i) == null ? null : aliases.getObjectName(i).toString()); 661 } 662 return new TableFunctionLayout(names, inputs, "0", true, args); 663 } 664 665 // --------------------------------------------------------------------------------------------- 666 // Helpers 667 // --------------------------------------------------------------------------------------------- 668 669 /** 670 * An alias column list renames the leading columns by position; the rest keep their names. A list longer than the 671 * columns is not one the rule settles. 672 */ 673 private static TableFunctionLayout rename(TTable table, List<String> names, List<String> inputs, String rows, 674 boolean closed, List<? extends TParseTreeNode> args, boolean renamed) { 675 TObjectNameList aliasColumns = renamed && table.getAliasClause() != null ? table.getAliasClause().getColumns() 676 : null; 677 if (aliasColumns != null && aliasColumns.size() > 0) { 678 if (aliasColumns.size() > names.size()) { 679 return null; 680 } 681 for (int i = 0; i < aliasColumns.size(); i++) { 682 TObjectName column = aliasColumns.getObjectName(i); 683 if (column != null) { 684 names.set(i, column.toString()); 685 } 686 } 687 } 688 return new TableFunctionLayout(names, inputs, rows, closed, args); 689 } 690 691 /** PostgreSQL and the dialects that keep its rule for a function of a base type in FROM. */ 692 private static boolean postgresFamily(EDbVendor vendor) { 693 return vendor == EDbVendor.dbvpostgresql || vendor == EDbVendor.dbvgreenplum || vendor == EDbVendor.dbvgaussdb 694 || vendor == EDbVendor.dbvedb; 695 } 696 697 private static boolean withOrdinality(TTable table) { 698 // PostgreSQL: f(...) WITH ORDINALITY, kept in the table's text after the call 699 gudusoft.gsqlparser.TSourceToken t = table.getFuncCall() == null ? null : table.getFuncCall().getEndToken(); 700 int words = 0; 701 for (t = t == null ? null : t.getNextTokenInChain(); t != null && words < 2; t = t.getNextTokenInChain()) { 702 if (t.tokentype == gudusoft.gsqlparser.ETokenType.ttwhitespace 703 || t.tokentype == gudusoft.gsqlparser.ETokenType.ttreturn) { 704 continue; 705 } 706 String text = t.getAstext(); 707 if (words == 0 && !"WITH".equalsIgnoreCase(text)) { // non-identifier-compare: SQL keyword 708 return false; 709 } 710 if (words == 1) { 711 return "ORDINALITY".equalsIgnoreCase(text); // non-identifier-compare: SQL keyword 712 } 713 words++; 714 } 715 return false; 716 } 717 718 /** The argument values of a call, in order: a named argument's value ({@code input => x} is x). */ 719 private static List<TExpression> arguments(TFunctionCall call) { 720 List<TExpression> out = new ArrayList<TExpression>(); 721 TExpressionList args = call.getArgs(); 722 for (int i = 0; args != null && i < args.size(); i++) { 723 TExpression arg = args.getExpression(i); 724 if (arg != null && arg.getExpressionType() == EExpressionType.assignment_t && arg.getRightOperand() != null) { 725 arg = arg.getRightOperand(); 726 } 727 out.add(arg); 728 } 729 return out; 730 } 731 732 /** {@code TABLE(f(...))} is f's call; anything else is the call itself. */ 733 private static TFunctionCall unwrapTable(EDbVendor vendor, TFunctionCall call) { 734 if (unqualified(call.getFunctionName()) == null || !same(vendor, unqualified(call.getFunctionName()), "TABLE") 735 || call.getArgs() == null || call.getArgs().size() != 1) { 736 return call; 737 } 738 TExpression arg = call.getArgs().getExpression(0); 739 return arg == null || arg.getFunctionCall() == null || arg.getFunctionCall().getFunctionName() == null ? call 740 : arg.getFunctionCall(); 741 } 742 743 /** The name of an unqualified call, or null for a schema- or database-qualified one (a user function). */ 744 private static String unqualified(TObjectName name) { 745 if (name == null) { 746 return null; 747 } 748 String schema = name.getSchemaString(); 749 String database = name.getDatabaseString(); 750 if ((schema != null && !schema.isEmpty()) || (database != null && !database.isEmpty())) { 751 return null; 752 } 753 return name.toString(); 754 } 755 756 private static boolean same(EDbVendor vendor, String name, String builtin) { 757 return name != null && SQLUtil.sameName(vendor, ESQLDataObjectType.dotFunction, name, builtin); 758 } 759 760 private static boolean isConstant(TExpression e, String text) { 761 return e != null && e.getExpressionType() == EExpressionType.simple_constant_t && text.equals(e.toString()); 762 } 763 764 /** An array literal written with its items ({@code [..]}, {@code ARRAY[..]}): its rows are its items. */ 765 private static boolean isArrayLiteral(TExpression array) { 766 return array != null && array.getExprList() != null && (array.getExpressionType() == EExpressionType.array_t 767 || array.getExpressionType() == EExpressionType.array_constructor_t); 768 } 769 770 /** An array literal ({@code [..]}, {@code ARRAY[..]}) whose every item is a plain constant: its element is one column. */ 771 private static boolean isScalarArrayLiteral(TExpression array) { 772 if (array == null || (array.getExpressionType() != EExpressionType.array_t 773 && array.getExpressionType() != EExpressionType.array_constructor_t)) { 774 return false; 775 } 776 TExpressionList items = array.getExprList(); 777 if (items == null || items.size() == 0) { 778 return false; 779 } 780 for (int i = 0; i < items.size(); i++) { 781 TExpression item = items.getExpression(i); 782 if (item != null && item.getExpressionType() == EExpressionType.unary_minus_t) { 783 item = item.getRightOperand() != null ? item.getRightOperand() : item.getLeftOperand(); 784 } 785 if (item == null || item.getExpressionType() != EExpressionType.simple_constant_t) { 786 return false; 787 } 788 } 789 return true; 790 } 791 792 private static List<String> list(String... items) { 793 List<String> out = new ArrayList<String>(); 794 Collections.addAll(out, items); 795 return out; 796 } 797}