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) {
070            TObjectNameList columns = aliasClause.getColumns();
071            if (columns != null && columns.size() > 0) {
072                // Get VALUES expressions from the first row to use as definition nodes
073                // This allows sourceColumn to point to the actual VALUE expression (e.g., :empno)
074                TResultColumn[] valueExpressions = getValueExpressions();
075
076                for (int i = 0; i < columns.size(); i++) {
077                    TObjectName colName = columns.getObjectName(i);
078                    if (colName != null) {
079                        String name = colName.toString();
080                        // Use the corresponding VALUE expression as definition node if available
081                        TResultColumn definitionNode = (valueExpressions != null && i < valueExpressions.length)
082                            ? valueExpressions[i]
083                            : null;
084
085                        ColumnSource source = new ColumnSource(
086                            this,
087                            name,
088                            definitionNode,  // VALUE expression for legacy sourceColumn sync
089                            1.0,             // Definite - defined in alias clause
090                            "values_alias_column"
091                        );
092                        columnSources.put(name, source);
093                    }
094                }
095            }
096        }
097    }
098
099    /**
100     * Get the VALUE expressions from the first row of the VALUES clause.
101     * These expressions map to the column names in the alias clause.
102     *
103     * For example: VALUES (:empno, :name, :salary) AS s(empno, name, salary)
104     * Returns: [TResultColumn(:empno), TResultColumn(:name), TResultColumn(:salary)]
105     *
106     * @return Array of TResultColumn from the first row, or null if not available
107     */
108    private TResultColumn[] getValueExpressions() {
109        // First try the preferred method: TValueClause.getRows()
110        TValueClause valueClause = table.getValueClause();
111        if (valueClause != null && valueClause.getRows() != null && !valueClause.getRows().isEmpty()) {
112            TResultColumnList firstRow = valueClause.getRows().get(0);
113            if (firstRow != null && firstRow.size() > 0) {
114                int size = firstRow.size();
115                TResultColumn[] result = new TResultColumn[size];
116                for (int i = 0; i < size; i++) {
117                    result[i] = firstRow.getResultColumn(i);
118                }
119                return result;
120            }
121        }
122
123        // Fallback to deprecated method: TMultiTargetList for older parsers
124        TMultiTargetList rowList = table.getRowList();
125        if (rowList != null && rowList.size() > 0) {
126            TMultiTarget firstRow = rowList.getMultiTarget(0);
127            if (firstRow != null && firstRow.getColumnList() != null) {
128                int size = firstRow.getColumnList().size();
129                TResultColumn[] result = new TResultColumn[size];
130                for (int i = 0; i < size; i++) {
131                    result[i] = firstRow.getColumnList().getResultColumn(i);
132                }
133                return result;
134            }
135        }
136
137        return null;
138    }
139
140    @Override
141    public boolean hasStarColumn() {
142        // VALUES tables don't support star expansion in the traditional sense
143        return false;
144    }
145
146    @Override
147    public boolean supportsDynamicInference() {
148        // Columns are fixed by alias clause - don't dynamically infer
149        return false;
150    }
151
152    @Override
153    public ColumnLevel hasColumn(String columnName) {
154        ensureValidated();
155
156        // Check explicit columns from alias clause
157        if (columnSources != null) {
158            for (String existingCol : columnSources.keySet()) {
159                if (nameMatcher.matches(existingCol, columnName)) {
160                    return ColumnLevel.EXISTS;
161                }
162            }
163        }
164
165        // VALUES tables have a fixed set of columns
166        return ColumnLevel.NOT_EXISTS;
167    }
168
169    @Override
170    public ColumnSource resolveColumn(String columnName) {
171        ensureValidated();
172
173        // Resolve from known columns
174        ColumnSource source = super.resolveColumn(columnName);
175        if (source != null) {
176            return source;
177        }
178
179        // VALUES tables do NOT auto-infer unknown columns
180        return null;
181    }
182
183    /**
184     * Get the alias for this VALUES table.
185     */
186    public String getAlias() {
187        return alias;
188    }
189
190    public TTable getTable() {
191        return table;
192    }
193
194    @Override
195    public String toString() {
196        return String.format("ValuesNamespace(%s, columns=%d)",
197            alias != null ? alias : "(values_table)",
198            columnSources != null ? columnSources.size() : 0);
199    }
200}