001package gudusoft.gsqlparser.resolver2.expansion;
002
003import gudusoft.gsqlparser.nodes.TExpression;
004import gudusoft.gsqlparser.nodes.TObjectName;
005import gudusoft.gsqlparser.nodes.TResultColumn;
006import gudusoft.gsqlparser.resolver2.model.ColumnSource;
007import gudusoft.gsqlparser.resolver2.namespace.INamespace;
008
009import java.util.ArrayList;
010import java.util.List;
011import java.util.Map;
012
013/**
014 * Expands SELECT * to explicit column lists.
015 *
016 * <p>Star expansion is crucial for:
017 * - Understanding exactly which columns are selected
018 * - Data lineage tracking
019 * - Query refactoring
020 * - Schema evolution analysis
021 *
022 * <p>Supports multiple star expansion scenarios:
023 * 1. Simple star: SELECT * FROM t1
024 * 2. Qualified star: SELECT t1.* FROM t1 JOIN t2
025 * 3. Multiple stars: SELECT t1.*, t2.* FROM t1 JOIN t2
026 * 4. Mixed: SELECT t1.*, t2.id FROM t1 JOIN t2
027 * 5. Nested: SELECT * FROM (SELECT * FROM t1) sub
028 *
029 * <p>Example:
030 * <pre>
031 * -- Input:
032 * SELECT * FROM employees
033 *
034 * -- Expanded (if metadata available):
035 * SELECT employees.id, employees.name, employees.salary
036 * FROM employees
037 * </pre>
038 */
039public class StarColumnExpander {
040
041    /** Whether to include table qualifiers in expanded columns */
042    private final boolean includeTableQualifier;
043
044    /** Whether to expand stars in subqueries recursively */
045    private final boolean expandSubqueries;
046
047    public StarColumnExpander() {
048        this(true, true);
049    }
050
051    public StarColumnExpander(boolean includeTableQualifier, boolean expandSubqueries) {
052        this.includeTableQualifier = includeTableQualifier;
053        this.expandSubqueries = expandSubqueries;
054    }
055
056    /**
057     * Expand a star column to explicit column list.
058     *
059     * @param starColumn the star column (SELECT *)
060     * @param namespace the namespace to expand from
061     * @return list of expanded columns, or empty if expansion not possible
062     */
063    public List<ExpandedColumn> expandStar(TResultColumn starColumn, INamespace namespace) {
064        List<ExpandedColumn> expandedColumns = new ArrayList<>();
065
066        if (starColumn == null || namespace == null) {
067            return expandedColumns;
068        }
069
070        // Check if this is actually a star column
071        if (!isStarColumn(starColumn)) {
072            return expandedColumns;
073        }
074
075        // Get all columns from the namespace
076        Map<String, ColumnSource> columnSources = namespace.getAllColumnSources();
077
078        if (columnSources == null || columnSources.isEmpty()) {
079            return expandedColumns;
080        }
081
082        // Create expanded column for each source column
083        for (Map.Entry<String, ColumnSource> entry : columnSources.entrySet()) {
084            String columnName = entry.getKey();
085            ColumnSource source = entry.getValue();
086
087            ExpandedColumn expanded = new ExpandedColumn(
088                columnName,
089                source,
090                starColumn,
091                includeTableQualifier
092            );
093
094            expandedColumns.add(expanded);
095        }
096
097        return expandedColumns;
098    }
099
100    /**
101     * Expand qualified star (e.g., t1.*) to explicit column list.
102     *
103     * @param starColumn the star column (SELECT t1.*)
104     * @param namespace the namespace to expand from
105     * @param tableAlias the table qualifier (e.g., "t1")
106     * @return list of expanded columns, or empty if expansion not possible
107     */
108    public List<ExpandedColumn> expandQualifiedStar(
109            TResultColumn starColumn,
110            INamespace namespace,
111            String tableAlias) {
112
113        List<ExpandedColumn> expandedColumns = new ArrayList<>();
114
115        if (starColumn == null || namespace == null || tableAlias == null) {
116            return expandedColumns;
117        }
118
119        // Get all columns from the namespace
120        Map<String, ColumnSource> columnSources = namespace.getAllColumnSources();
121
122        if (columnSources == null || columnSources.isEmpty()) {
123            return expandedColumns;
124        }
125
126        // Create expanded column for each source column
127        for (Map.Entry<String, ColumnSource> entry : columnSources.entrySet()) {
128            String columnName = entry.getKey();
129            ColumnSource source = entry.getValue();
130
131            ExpandedColumn expanded = new ExpandedColumn(
132                columnName,
133                source,
134                starColumn,
135                tableAlias,
136                includeTableQualifier
137            );
138
139            expandedColumns.add(expanded);
140        }
141
142        return expandedColumns;
143    }
144
145    /**
146     * Expand all stars in a result column list.
147     *
148     * @param resultColumns the result columns
149     * @param availableNamespaces list of available namespaces to expand from
150     * @return expansion result with all expanded columns
151     */
152    public ExpansionResult expandAllStars(
153            List<TResultColumn> resultColumns,
154            List<INamespace> availableNamespaces) {
155
156        ExpansionResult result = new ExpansionResult();
157
158        if (resultColumns == null || availableNamespaces == null) {
159            return result;
160        }
161
162        for (TResultColumn rc : resultColumns) {
163            if (isStarColumn(rc)) {
164                // This is a star column - expand it
165                if (isQualifiedStar(rc)) {
166                    // Qualified star: t1.*
167                    String tableAlias = getStarQualifier(rc);
168                    INamespace targetNamespace = findNamespaceByAlias(
169                        availableNamespaces,
170                        tableAlias
171                    );
172
173                    if (targetNamespace != null) {
174                        List<ExpandedColumn> expanded = expandQualifiedStar(
175                            rc,
176                            targetNamespace,
177                            tableAlias
178                        );
179                        result.addExpandedStar(rc, expanded);
180                    } else {
181                        result.addUnexpandableStar(rc, "Namespace not found: " + tableAlias);
182                    }
183                } else {
184                    // Unqualified star: *
185                    // Expand from all available namespaces
186                    List<ExpandedColumn> allExpanded = new ArrayList<>();
187                    for (INamespace ns : availableNamespaces) {
188                        List<ExpandedColumn> expanded = expandStar(rc, ns);
189                        allExpanded.addAll(expanded);
190                    }
191
192                    if (!allExpanded.isEmpty()) {
193                        result.addExpandedStar(rc, allExpanded);
194                    } else {
195                        result.addUnexpandableStar(rc, "No columns available for expansion");
196                    }
197                }
198            } else {
199                // Not a star column - keep as is
200                result.addNonStarColumn(rc);
201            }
202        }
203
204        return result;
205    }
206
207    /**
208     * Check if a result column is a star column (* or t.*)
209     */
210    private boolean isStarColumn(TResultColumn rc) {
211        if (rc == null || rc.getExpr() == null) {
212            return false;
213        }
214
215        TExpression expr = rc.getExpr();
216        if (expr.getExpressionType() != gudusoft.gsqlparser.EExpressionType.simple_object_name_t) {
217            return false;
218        }
219
220        TObjectName objName = expr.getObjectOperand();
221        if (objName == null) {
222            return false;
223        }
224
225        String str = objName.toString();
226        return str != null && (str.equals("*") || str.endsWith(".*"));
227    }
228
229    /**
230     * Check if a star column is qualified (t.*)
231     */
232    private boolean isQualifiedStar(TResultColumn rc) {
233        if (!isStarColumn(rc)) {
234            return false;
235        }
236
237        TObjectName objName = rc.getExpr().getObjectOperand();
238        String str = objName.toString();
239        return str != null && str.endsWith(".*") && str.length() > 2;
240    }
241
242    /**
243     * Get the qualifier from a qualified star (t.* returns "t")
244     */
245    private String getStarQualifier(TResultColumn rc) {
246        if (!isQualifiedStar(rc)) {
247            return null;
248        }
249
250        TObjectName objName = rc.getExpr().getObjectOperand();
251        String str = objName.toString();
252        if (str != null && str.endsWith(".*")) {
253            return str.substring(0, str.length() - 2);
254        }
255
256        return null;
257    }
258
259    /**
260     * Find a namespace by alias/name
261     */
262    private INamespace findNamespaceByAlias(List<INamespace> namespaces, String alias) {
263        if (namespaces == null || alias == null) {
264            return null;
265        }
266
267        for (INamespace ns : namespaces) {
268            // Check if namespace display name matches
269            if (ns.getDisplayName() != null &&
270                ns.getDisplayName().equalsIgnoreCase(alias)) {
271                return ns;
272            }
273
274            // For table namespaces, check table name/alias
275            if (ns.getFinalTable() != null) {
276                String tableName = ns.getFinalTable().getName();
277                String tableAlias = ns.getFinalTable().getAliasName();
278
279                if ((tableName != null && tableName.equalsIgnoreCase(alias)) ||
280                    (tableAlias != null && tableAlias.equalsIgnoreCase(alias))) {
281                    return ns;
282                }
283            }
284        }
285
286        return null;
287    }
288}