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}