001package gudusoft.gsqlparser.resolver2.namespace; 002 003import gudusoft.gsqlparser.nodes.TParseTreeNode; 004import gudusoft.gsqlparser.nodes.TTable; 005import gudusoft.gsqlparser.resolver2.ColumnLevel; 006import gudusoft.gsqlparser.resolver2.matcher.INameMatcher; 007import gudusoft.gsqlparser.resolver2.model.ColumnSource; 008import gudusoft.gsqlparser.resolver2.inference.InferenceEngine; 009 010import java.util.HashMap; 011import java.util.HashSet; 012import java.util.Map; 013import java.util.Set; 014 015/** 016 * Dynamic namespace for handling SELECT * without metadata. 017 * 018 * <p>This namespace represents a table or subquery where we have SELECT * 019 * but no schema information. It dynamically infers columns based on: 020 * - Usage in WHERE clauses 021 * - Usage in JOIN conditions 022 * - Usage in other parts of the query 023 * 024 * <p>Example scenario: 025 * <pre> 026 * -- No metadata available for employees table 027 * SELECT * FROM employees e 028 * WHERE e.department_id = 10 029 * AND e.salary > 50000 030 * 031 * DynamicStarSource will infer: 032 * - "department_id" column (from WHERE clause) 033 * - "salary" column (from WHERE clause) 034 * </pre> 035 * 036 * <p>The inference is done lazily - columns are added as evidence is discovered 037 * during resolution. This allows the resolver to handle queries without schema 038 * information. 039 * 040 * <p>Confidence scoring: 041 * - Qualified references (t.col): High confidence (0.95) 042 * - JOIN conditions: Very high confidence (0.9) 043 * - Unqualified references: Lower confidence (0.5-0.8) 044 */ 045public class DynamicStarSource extends AbstractNamespace { 046 047 /** The table this star source represents */ 048 private final TTable table; 049 050 /** Inference engine for collecting evidence */ 051 private final InferenceEngine inferenceEngine; 052 053 /** Dynamically discovered columns */ 054 private final Map<String, ColumnSource> dynamicColumns = new HashMap<>(); 055 056 /** Whether this source has been validated (evidence collected) */ 057 private boolean evidenceCollected = false; 058 059 public DynamicStarSource(TTable table, INameMatcher nameMatcher) { 060 super(table, nameMatcher); 061 this.table = table; 062 this.inferenceEngine = new InferenceEngine(); 063 } 064 065 /** 066 * Get the inference engine for this star source. 067 * External code can add evidence to this engine. 068 * 069 * @return the inference engine 070 */ 071 public InferenceEngine getInferenceEngine() { 072 return inferenceEngine; 073 } 074 075 /** 076 * Add a dynamically inferred column. 077 * 078 * @param columnName the column name 079 * @param confidence the confidence score 080 * @param evidence the evidence description 081 * @return true if the column was added, false if it already exists 082 */ 083 @Override 084 public boolean addInferredColumn(String columnName, double confidence, String evidence) { 085 if (columnName == null || columnName.isEmpty()) { 086 return false; 087 } 088 089 // Check if column already exists 090 for (String existing : dynamicColumns.keySet()) { 091 if (nameMatcher.matches(existing, columnName)) { 092 return false; // Already exists 093 } 094 } 095 096 // Create a column source for this inferred column 097 ColumnSource columnSource = new ColumnSource( 098 this, 099 columnName, 100 table, // Use table as definition node 101 confidence, 102 evidence 103 ); 104 105 dynamicColumns.put(columnName, columnSource); 106 return true; 107 } 108 109 /** 110 * Add a dynamically inferred column from an existing ColumnSource. 111 * This is used for reverse inference (Principle 3: Star Column双向处理). 112 * 113 * @param columnName the column name 114 * @param columnSource the column source with confidence and evidence 115 */ 116 public void addInferredColumn(String columnName, ColumnSource columnSource) { 117 if (columnName == null || columnName.isEmpty() || columnSource == null) { 118 return; 119 } 120 121 dynamicColumns.put(columnName, columnSource); 122 } 123 124 /** 125 * Infer columns from the inference engine's evidence. 126 * This should be called after all evidence has been collected. 127 */ 128 public void inferColumnsFromEvidence() { 129 if (evidenceCollected) { 130 return; // Already done 131 } 132 133 evidenceCollected = true; 134 135 // Get table name for matching evidence 136 String tableName = table.getName(); 137 if (tableName == null || tableName.isEmpty()) { 138 tableName = table.getAliasName(); 139 } 140 141 if (tableName == null) { 142 return; 143 } 144 145 // Get all inferred columns for this table 146 Set<String> inferredColumns = inferenceEngine.getInferredColumns(tableName); 147 148 for (String columnName : inferredColumns) { 149 double confidence = inferenceEngine.calculateConfidence(tableName, columnName); 150 ColumnSource columnSource = inferenceEngine.createInferredColumnSource( 151 tableName, 152 columnName, 153 table 154 ); 155 156 if (columnSource != null) { 157 dynamicColumns.put(columnName, columnSource); 158 } 159 } 160 } 161 162 @Override 163 protected void doValidate() { 164 // Validation for dynamic star source means inferring columns from evidence 165 inferColumnsFromEvidence(); 166 } 167 168 @Override 169 public ColumnLevel hasColumn(String columnName) { 170 if (columnName == null) { 171 return ColumnLevel.NOT_EXISTS; 172 } 173 174 // Ensure evidence has been collected 175 if (!evidenceCollected) { 176 inferColumnsFromEvidence(); 177 } 178 179 // Check if we have inferred this column 180 for (String knownColumn : dynamicColumns.keySet()) { 181 if (nameMatcher.matches(knownColumn, columnName)) { 182 return ColumnLevel.EXISTS; 183 } 184 } 185 186 // For dynamic sources, we use MAYBE instead of NOT_EXISTS 187 // because the column might exist but we haven't seen evidence yet 188 return ColumnLevel.MAYBE; 189 } 190 191 @Override 192 public ColumnSource resolveColumn(String columnName) { 193 if (columnName == null) { 194 return null; 195 } 196 197 // Ensure evidence has been collected 198 if (!evidenceCollected) { 199 inferColumnsFromEvidence(); 200 } 201 202 // Find matching column 203 for (Map.Entry<String, ColumnSource> entry : dynamicColumns.entrySet()) { 204 if (nameMatcher.matches(entry.getKey(), columnName)) { 205 return entry.getValue(); 206 } 207 } 208 209 // Column not found - but for dynamic sources, we could add it with low confidence 210 // For now, return null 211 return null; 212 } 213 214 @Override 215 public Map<String, ColumnSource> getAllColumnSources() { 216 // Ensure evidence has been collected 217 if (!evidenceCollected) { 218 inferColumnsFromEvidence(); 219 } 220 221 return new HashMap<>(dynamicColumns); 222 } 223 224 @Override 225 public TTable getFinalTable() { 226 return table; 227 } 228 229 @Override 230 public String getDisplayName() { 231 String tableName = table.getName() != null ? table.getName() : table.getAliasName(); 232 return "DynamicStarSource(" + tableName + ")"; 233 } 234 235 /** 236 * Get the number of dynamically inferred columns. 237 * 238 * @return column count 239 */ 240 public int getInferredColumnCount() { 241 return dynamicColumns.size(); 242 } 243 244 /** 245 * Check if any columns have been inferred. 246 * 247 * @return true if columns were inferred, false otherwise 248 */ 249 public boolean hasInferredColumns() { 250 return !dynamicColumns.isEmpty(); 251 } 252 253 /** 254 * Get statistics about this dynamic star source. 255 * 256 * @return statistics string 257 */ 258 public String getStatistics() { 259 return String.format( 260 "DynamicStarSource[table=%s, inferred=%d, evidence=%d]", 261 table.getName() != null ? table.getName() : table.getAliasName(), 262 dynamicColumns.size(), 263 inferenceEngine.getEvidenceCount() 264 ); 265 } 266 267 @Override 268 public boolean supportsDynamicInference() { 269 return true; 270 } 271 272 @Override 273 public Set<String> getInferredColumns() { 274 return new HashSet<>(dynamicColumns.keySet()); 275 } 276 277 @Override 278 public boolean hasStarColumn() { 279 // DynamicStarSource represents a star column by definition 280 return true; 281 } 282 283 @Override 284 public String toString() { 285 return getStatistics(); 286 } 287}