001package gudusoft.gsqlparser.resolver2.namespace;
002
003import gudusoft.gsqlparser.nodes.TAliasClause;
004import gudusoft.gsqlparser.nodes.TMultiTarget;
005import gudusoft.gsqlparser.nodes.TMultiTargetList;
006import gudusoft.gsqlparser.nodes.TObjectName;
007import gudusoft.gsqlparser.nodes.TObjectNameList;
008import gudusoft.gsqlparser.nodes.TResultColumn;
009import gudusoft.gsqlparser.nodes.TResultColumnList;
010import gudusoft.gsqlparser.nodes.TTable;
011import gudusoft.gsqlparser.nodes.TValueClause;
012import gudusoft.gsqlparser.resolver2.ColumnLevel;
013import gudusoft.gsqlparser.resolver2.matcher.INameMatcher;
014import gudusoft.gsqlparser.resolver2.model.ColumnSource;
015
016import java.util.ArrayList;
017import java.util.LinkedHashMap;
018import java.util.List;
019
020/**
021 * Namespace representing a VALUES table expression.
022 *
023 * VALUES tables define inline data with an optional alias and column names.
024 * For example:
025 * - VALUES (1, 'a'), (2, 'b') AS t(id, name)
026 * - In Teradata MERGE: USING VALUES (:empno, :name, :salary) AS s(empno, name, salary)
027 *
028 * The ValuesNamespace provides columns defined in the alias clause.
029 */
030public class ValuesNamespace extends AbstractNamespace {
031
032    private final TTable table;
033    private final String alias;
034
035    public ValuesNamespace(TTable table, String alias, INameMatcher nameMatcher) {
036        super(table, nameMatcher);
037        this.table = table;
038        this.alias = alias;
039    }
040
041    public ValuesNamespace(TTable table, String alias) {
042        this(table, alias, null);
043    }
044
045    @Override
046    public String getDisplayName() {
047        // Match test expectations - use "(values_table)" for consistency
048        return "(values_table)";
049    }
050
051    @Override
052    public TTable getFinalTable() {
053        return table;
054    }
055
056    @Override
057    public List<TTable> getAllFinalTables() {
058        List<TTable> tables = new ArrayList<>();
059        tables.add(table);
060        return tables;
061    }
062
063    @Override
064    protected void doValidate() {
065        columnSources = new LinkedHashMap<>();
066
067        // Extract columns from alias clause: AS t(name, empno, salary)
068        TAliasClause aliasClause = table.getAliasClause();
069        if (aliasClause == null || aliasClause.getColumns() == null || aliasClause.getColumns().size() == 0) {
070            // Without a column list the vendor names the columns, e.g. Snowflake column1, column2 (Mantis 4823)
071            TResultColumn[] valueExpressions = getValueExpressions();
072            for (int i = 0; valueExpressions != null && i < valueExpressions.length; i++) {
073                String name = TValueClause.defaultColumnName(table.dbvendor, i + 1);
074                if (name == null) {
075                    break;
076                }
077                columnSources.put(name, new ColumnSource(this, name, valueExpressions[i], 1.0, "values_default_column"));
078            }
079            return;
080        }
081        if (aliasClause != null) {
082            TObjectNameList columns = aliasClause.getColumns();
083            if (columns != null && columns.size() > 0) {
084                // Get VALUES expressions from the first row to use as definition nodes
085                // This allows sourceColumn to point to the actual VALUE expression (e.g., :empno)
086                TResultColumn[] valueExpressions = getValueExpressions();
087
088                for (int i = 0; i < columns.size(); i++) {
089                    TObjectName colName = columns.getObjectName(i);
090                    if (colName != null) {
091                        String name = colName.toString();
092                        // Use the corresponding VALUE expression as definition node if available
093                        TResultColumn definitionNode = (valueExpressions != null && i < valueExpressions.length)
094                            ? valueExpressions[i]
095                            : null;
096
097                        ColumnSource source = new ColumnSource(
098                            this,
099                            name,
100                            definitionNode,  // VALUE expression for legacy sourceColumn sync
101                            1.0,             // Definite - defined in alias clause
102                            "values_alias_column"
103                        );
104                        columnSources.put(name, source);
105                    }
106                }
107            }
108        }
109    }
110
111    /**
112     * Get the VALUE expressions from the first row of the VALUES clause.
113     * These expressions map to the column names in the alias clause.
114     *
115     * For example: VALUES (:empno, :name, :salary) AS s(empno, name, salary)
116     * Returns: [TResultColumn(:empno), TResultColumn(:name), TResultColumn(:salary)]
117     *
118     * @return Array of TResultColumn from the first row, or null if not available
119     */
120    private TResultColumn[] getValueExpressions() {
121        // First try the preferred method: TValueClause.getRows()
122        TValueClause valueClause = table.getValueClause();
123        if (valueClause != null && valueClause.getRows() != null && !valueClause.getRows().isEmpty()) {
124            TResultColumnList firstRow = valueClause.getRows().get(0);
125            if (firstRow != null && firstRow.size() > 0) {
126                int size = firstRow.size();
127                TResultColumn[] result = new TResultColumn[size];
128                for (int i = 0; i < size; i++) {
129                    result[i] = firstRow.getResultColumn(i);
130                }
131                return result;
132            }
133        }
134
135        // Fallback to deprecated method: TMultiTargetList for older parsers
136        TMultiTargetList rowList = table.getRowList();
137        if (rowList != null && rowList.size() > 0) {
138            TMultiTarget firstRow = rowList.getMultiTarget(0);
139            if (firstRow != null && firstRow.getColumnList() != null) {
140                int size = firstRow.getColumnList().size();
141                TResultColumn[] result = new TResultColumn[size];
142                for (int i = 0; i < size; i++) {
143                    result[i] = firstRow.getColumnList().getResultColumn(i);
144                }
145                return result;
146            }
147        }
148
149        return null;
150    }
151
152    @Override
153    public boolean hasStarColumn() {
154        // VALUES tables don't support star expansion in the traditional sense
155        return false;
156    }
157
158    @Override
159    public boolean supportsDynamicInference() {
160        // Columns are fixed by alias clause - don't dynamically infer
161        return false;
162    }
163
164    @Override
165    public ColumnLevel hasColumn(String columnName) {
166        ensureValidated();
167
168        // Check explicit columns from alias clause
169        if (columnSources != null) {
170            for (String existingCol : columnSources.keySet()) {
171                if (nameMatcher.matches(existingCol, columnName)) {
172                    return ColumnLevel.EXISTS;
173                }
174            }
175        }
176
177        // VALUES tables have a fixed set of columns
178        return ColumnLevel.NOT_EXISTS;
179    }
180
181    @Override
182    public ColumnSource resolveColumn(String columnName) {
183        ensureValidated();
184
185        // Resolve from known columns
186        ColumnSource source = super.resolveColumn(columnName);
187        if (source != null) {
188            return source;
189        }
190
191        // VALUES tables do NOT auto-infer unknown columns
192        return null;
193    }
194
195    /**
196     * Get the alias for this VALUES table.
197     */
198    public String getAlias() {
199        return alias;
200    }
201
202    public TTable getTable() {
203        return table;
204    }
205
206    @Override
207    public String toString() {
208        return String.format("ValuesNamespace(%s, columns=%d)",
209            alias != null ? alias : "(values_table)",
210            columnSources != null ? columnSources.size() : 0);
211    }
212}