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}