001package gudusoft.gsqlparser.resolver2.namespace;
002
003import gudusoft.gsqlparser.nodes.TObjectName;
004import gudusoft.gsqlparser.nodes.TTable;
005import gudusoft.gsqlparser.resolver2.ColumnLevel;
006import gudusoft.gsqlparser.resolver2.matcher.INameMatcher;
007import gudusoft.gsqlparser.resolver2.matcher.DefaultNameMatcher;
008import gudusoft.gsqlparser.resolver2.model.ColumnReference;
009import gudusoft.gsqlparser.resolver2.model.ColumnSource;
010import gudusoft.gsqlparser.resolver2.model.ColumnSourceWithReferences;
011
012import java.util.*;
013
014/**
015 * Abstract base class for all namespaces.
016 * Provides common functionality for column resolution and caching.
017 *
018 * <p>Enhanced with reference traceability support: multiple syntactically different
019 * identifiers that refer to the same semantic column can be tracked and traced back.</p>
020 */
021public abstract class AbstractNamespace implements INamespace {
022
023    /** Associated AST node */
024    protected final Object node;
025
026    /** Whether this namespace has been validated */
027    protected boolean validated = false;
028
029    /** Cached column sources (populated during validation) - keyed by normalized name */
030    protected Map<String, ColumnSource> columnSources = null;
031
032    /**
033     * Enhanced column sources with reference traceability (keyed by normalized name).
034     * When enabled, this map stores ColumnSourceWithReferences that track all original
035     * references to each semantic column.
036     */
037    protected Map<String, ColumnSourceWithReferences> columnSourcesWithRefs = null;
038
039    /** Whether reference traceability is enabled */
040    protected boolean referenceTraceabilityEnabled = false;
041
042    /** Name matcher for column name comparisons */
043    protected final INameMatcher nameMatcher;
044
045    /**
046     * Strategy for handling ambiguous columns.
047     * Defaults to -1, which means use global TBaseType.GUESS_COLUMN_STRATEGY.
048     * Can be overridden per-namespace instance.
049     */
050    protected int guessColumnStrategy = -1;
051
052    protected AbstractNamespace(Object node, INameMatcher nameMatcher) {
053        this.node = node;
054        this.nameMatcher = nameMatcher != null ? nameMatcher : new DefaultNameMatcher();
055    }
056
057    protected AbstractNamespace(Object node) {
058        this(node, new DefaultNameMatcher());
059    }
060
061    /**
062     * Set the strategy for handling ambiguous columns.
063     * @param strategy One of TBaseType.GUESS_COLUMN_STRATEGY_* constants, or -1 to use global default
064     */
065    public void setGuessColumnStrategy(int strategy) {
066        this.guessColumnStrategy = strategy;
067    }
068
069    /**
070     * Get the strategy for handling ambiguous columns.
071     * Returns the instance-level strategy if set, otherwise returns the global default.
072     * @return The strategy constant
073     */
074    public int getGuessColumnStrategy() {
075        if (guessColumnStrategy >= 0) {
076            return guessColumnStrategy;
077        }
078        return gudusoft.gsqlparser.TBaseType.GUESS_COLUMN_STRATEGY;
079    }
080
081    @Override
082    public Object getNode() {
083        return node;
084    }
085
086    @Override
087    public boolean isValidated() {
088        return validated;
089    }
090
091    @Override
092    public void validate() {
093        if (!validated) {
094            doValidate();
095            validated = true;
096        }
097    }
098
099    /**
100     * Subclasses override this to perform actual validation logic.
101     */
102    protected abstract void doValidate();
103
104    /**
105     * Enable reference traceability for this namespace.
106     * When enabled, all column additions will track original references.
107     */
108    public void enableReferenceTraceability() {
109        this.referenceTraceabilityEnabled = true;
110        if (columnSourcesWithRefs == null) {
111            columnSourcesWithRefs = new LinkedHashMap<>();
112        }
113    }
114
115    /**
116     * Check if reference traceability is enabled.
117     *
118     * @return true if traceability is enabled
119     */
120    public boolean isReferenceTraceabilityEnabled() {
121        return referenceTraceabilityEnabled;
122    }
123
124    /**
125     * Add a column source with reference traceability support.
126     *
127     * <p>This method normalizes the column name and either:</p>
128     * <ul>
129     *   <li>Creates a new entry if this is the first reference</li>
130     *   <li>Adds a reference to an existing entry if the column already exists</li>
131     * </ul>
132     *
133     * @param columnName original column name (may include quotes)
134     * @param source the column source
135     * @param objectName the original AST node for traceability (may be null)
136     */
137    protected void addColumnSource(String columnName, ColumnSource source, TObjectName objectName) {
138        if (columnSources == null) {
139            columnSources = new LinkedHashMap<>();
140        }
141
142        String normalizedKey = nameMatcher.normalize(columnName);
143
144        // Add to basic map if not exists
145        if (!columnSources.containsKey(normalizedKey)) {
146            columnSources.put(normalizedKey, source);
147        }
148
149        // Add to enhanced map with references if traceability is enabled
150        if (referenceTraceabilityEnabled) {
151            if (columnSourcesWithRefs == null) {
152                columnSourcesWithRefs = new LinkedHashMap<>();
153            }
154
155            ColumnSourceWithReferences enhanced = columnSourcesWithRefs.computeIfAbsent(
156                normalizedKey,
157                k -> new ColumnSourceWithReferences(normalizedKey, source)
158            );
159
160            if (objectName != null) {
161                enhanced.addReference(new ColumnReference(objectName));
162            }
163        }
164    }
165
166    /**
167     * Add a column source (backward compatible - no traceability).
168     *
169     * @param columnName original column name
170     * @param source the column source
171     */
172    protected void addColumnSource(String columnName, ColumnSource source) {
173        addColumnSource(columnName, source, null);
174    }
175
176    @Override
177    public ColumnLevel hasColumn(String columnName) {
178        ensureValidated();
179
180        if (columnSources == null) {
181            return ColumnLevel.NOT_EXISTS;
182        }
183
184        // Use normalized key for O(1) lookup
185        String normalizedKey = nameMatcher.normalize(columnName);
186        if (columnSources.containsKey(normalizedKey)) {
187            return ColumnLevel.EXISTS;
188        }
189
190        // Fallback to linear search for backward compatibility
191        for (String existingCol : columnSources.keySet()) {
192            if (nameMatcher.matches(existingCol, columnName)) {
193                return ColumnLevel.EXISTS;
194            }
195        }
196
197        return ColumnLevel.NOT_EXISTS;
198    }
199
200    @Override
201    public ColumnSource resolveColumn(String columnName) {
202        ensureValidated();
203
204        if (columnSources == null) {
205            return null;
206        }
207
208        // Use normalized key for O(1) lookup
209        String normalizedKey = nameMatcher.normalize(columnName);
210        ColumnSource source = columnSources.get(normalizedKey);
211        if (source != null) {
212            return source;
213        }
214
215        // Fallback to linear search for backward compatibility
216        for (Map.Entry<String, ColumnSource> entry : columnSources.entrySet()) {
217            if (nameMatcher.matches(entry.getKey(), columnName)) {
218                return entry.getValue();
219            }
220        }
221
222        return null;
223    }
224
225    @Override
226    public Map<String, ColumnSource> getAllColumnSources() {
227        ensureValidated();
228        return columnSources != null
229            ? Collections.unmodifiableMap(columnSources)
230            : Collections.emptyMap();
231    }
232
233    /**
234     * Get all column references for a specific column.
235     *
236     * <p>Requires reference traceability to be enabled.</p>
237     *
238     * @param columnName the column name (normalized or original)
239     * @return list of all references, empty if not found or traceability not enabled
240     */
241    public List<ColumnReference> getColumnReferences(String columnName) {
242        if (columnSourcesWithRefs == null) {
243            return Collections.emptyList();
244        }
245
246        String normalizedKey = nameMatcher.normalize(columnName);
247        ColumnSourceWithReferences enhanced = columnSourcesWithRefs.get(normalizedKey);
248
249        return enhanced != null
250            ? enhanced.getAllReferences()
251            : Collections.emptyList();
252    }
253
254    /**
255     * Get all unique columns with their references.
256     *
257     * <p>Requires reference traceability to be enabled.</p>
258     *
259     * @return collection of enhanced column sources, empty if traceability not enabled
260     */
261    public Collection<ColumnSourceWithReferences> getAllUniqueColumns() {
262        if (columnSourcesWithRefs == null) {
263            return Collections.emptyList();
264        }
265        return Collections.unmodifiableCollection(columnSourcesWithRefs.values());
266    }
267
268    /**
269     * Get enhanced column source with references for a specific column.
270     *
271     * @param columnName the column name
272     * @return enhanced column source, or null if not found
273     */
274    public ColumnSourceWithReferences getColumnSourceWithReferences(String columnName) {
275        if (columnSourcesWithRefs == null) {
276            return null;
277        }
278        String normalizedKey = nameMatcher.normalize(columnName);
279        return columnSourcesWithRefs.get(normalizedKey);
280    }
281
282    @Override
283    public List<TTable> getAllFinalTables() {
284        TTable finalTable = getFinalTable();
285        if (finalTable != null) {
286            return Collections.singletonList(finalTable);
287        }
288        return Collections.emptyList();
289    }
290
291    /**
292     * Ensure this namespace is validated before accessing column info
293     */
294    protected void ensureValidated() {
295        if (!validated) {
296            validate();
297        }
298    }
299
300    /**
301     * Get the name matcher used by this namespace.
302     *
303     * @return the name matcher
304     */
305    public INameMatcher getNameMatcher() {
306        return nameMatcher;
307    }
308
309    @Override
310    public String toString() {
311        return getDisplayName();
312    }
313}