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}