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}