001package gudusoft.gsqlparser.resolver2.metadata;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TCustomSqlStatement;
005import gudusoft.gsqlparser.TStatementList;
006import gudusoft.gsqlparser.nodes.TColumnDefinition;
007import gudusoft.gsqlparser.nodes.TColumnDefinitionList;
008import gudusoft.gsqlparser.nodes.TObjectName;
009import gudusoft.gsqlparser.nodes.TResultColumn;
010import gudusoft.gsqlparser.nodes.TResultColumnList;
011import gudusoft.gsqlparser.nodes.TViewAliasItem;
012import gudusoft.gsqlparser.sqlenv.TSQLCatalog;
013import gudusoft.gsqlparser.sqlenv.TSQLEnv;
014import gudusoft.gsqlparser.sqlenv.TSQLSchema;
015import gudusoft.gsqlparser.sqlenv.TSQLTable;
016import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement;
017import gudusoft.gsqlparser.stmt.TCreateViewSqlStatement;
018import gudusoft.gsqlparser.stmt.TSelectSqlStatement;
019
020/**
021 * Delta 1: Batch-local DDL metadata collector.
022 *
023 * Collects table and column metadata from DDL statements (CREATE TABLE, CREATE VIEW)
024 * within the same SQL batch and creates a TSQLEnv for use by TSQLResolver2.
025 *
026 * This enables standalone resolution of SQL batches that contain both DDL and DML,
027 * without requiring external metadata from TSQLEnv.
028 *
029 * Example:
030 * <pre>
031 * CREATE TABLE t(id int, name varchar(10));
032 * SELECT t.id, t.name FROM t;
033 * </pre>
034 *
035 * The collector will extract table "t" with columns "id" and "name" from the
036 * CREATE TABLE statement, making them available for resolution in the SELECT.
037 */
038public class BatchMetadataCollector {
039
040    private final TStatementList statements;
041    private final EDbVendor vendor;
042
043    /**
044     * Create a batch metadata collector.
045     *
046     * @param statements The list of SQL statements to process
047     * @param vendor The database vendor
048     */
049    public BatchMetadataCollector(TStatementList statements, EDbVendor vendor) {
050        this.statements = statements;
051        this.vendor = vendor;
052    }
053
054    /**
055     * Collect metadata from DDL statements and create a TSQLEnv.
056     *
057     * @return A TSQLEnv containing table/column metadata from DDL statements,
058     *         or null if no DDL statements were found
059     */
060    public TSQLEnv collect() {
061        if (statements == null || statements.size() == 0) {
062            return null;
063        }
064
065        // Create a simple TSQLEnv implementation
066        TSQLEnv env;
067        try {
068            env = new TSQLEnv(vendor) {
069                @Override
070                public void initSQLEnv() {
071                    // Initialization done in collect() after construction
072                }
073            };
074        } catch (Exception e) {
075            // TSQLEnv construction failed - return null
076            return null;
077        }
078
079        boolean foundDDL = false;
080
081        // Process each statement looking for DDL
082        for (int i = 0; i < statements.size(); i++) {
083            TCustomSqlStatement stmt = statements.get(i);
084            if (stmt == null) {
085                continue;
086            }
087
088            if (stmt instanceof TCreateTableSqlStatement) {
089                processCreateTable((TCreateTableSqlStatement) stmt, env);
090                foundDDL = true;
091            } else if (stmt instanceof TCreateViewSqlStatement) {
092                processCreateView((TCreateViewSqlStatement) stmt, env);
093                foundDDL = true;
094            }
095        }
096
097        return foundDDL ? env : null;
098    }
099
100    /**
101     * Process a CREATE TABLE statement and add table metadata to the environment.
102     */
103    private void processCreateTable(TCreateTableSqlStatement stmt, TSQLEnv env) {
104        TObjectName tableName = stmt.getTableName();
105        if (tableName == null) {
106            return;
107        }
108
109        TSQLTable table = getOrCreateTable(env, tableName);
110        if (table == null) {
111            return;
112        }
113
114        // Process column definitions
115        TColumnDefinitionList columnList = stmt.getColumnList();
116        if (columnList != null) {
117            for (int i = 0; i < columnList.size(); i++) {
118                TColumnDefinition colDef = columnList.getColumn(i);
119                if (colDef != null && colDef.getColumnName() != null) {
120                    String colName = colDef.getColumnName().toString();
121                    // Use simple addColumn without datatype for resolution purposes
122                    table.addColumn(colName);
123                }
124            }
125        }
126
127        // Handle CREATE TABLE AS SELECT (CTAS)
128        TSelectSqlStatement subquery = stmt.getSubQuery();
129        if (subquery != null) {
130            processCreateTableAsSelect(subquery, table);
131        }
132    }
133
134    /**
135     * Process CREATE TABLE AS SELECT to infer columns from the SELECT list.
136     */
137    private void processCreateTableAsSelect(TSelectSqlStatement select, TSQLTable table) {
138        if (select == null) {
139            return;
140        }
141
142        TResultColumnList resultColumns = select.getResultColumnList();
143        if (resultColumns == null) {
144            return;
145        }
146
147        for (int i = 0; i < resultColumns.size(); i++) {
148            TResultColumn rc = resultColumns.getResultColumn(i);
149            if (rc == null) {
150                continue;
151            }
152
153            // Get column name from alias or expression
154            String colName;
155            if (rc.getAliasClause() != null && rc.getAliasClause().getAliasName() != null) {
156                colName = rc.getAliasClause().getAliasName().toString();
157            } else if (rc.getExpr() != null && rc.getExpr().getObjectOperand() != null) {
158                colName = rc.getExpr().getObjectOperand().getColumnNameOnly();
159            } else {
160                // Fallback to expression string
161                colName = rc.toString();
162            }
163
164            if (colName != null && !colName.isEmpty()) {
165                table.addColumn(colName);
166            }
167        }
168    }
169
170    /**
171     * Process a CREATE VIEW statement and add view metadata to the environment.
172     */
173    private void processCreateView(TCreateViewSqlStatement stmt, TSQLEnv env) {
174        TObjectName viewName = stmt.getViewName();
175        if (viewName == null) {
176            return;
177        }
178
179        TSQLTable view = getOrCreateTable(env, viewName);
180        if (view == null) {
181            return;
182        }
183
184        // Try to get columns from explicit column list
185        if (stmt.getViewAliasClause() != null &&
186            stmt.getViewAliasClause().getViewAliasItemList() != null) {
187            for (int i = 0; i < stmt.getViewAliasClause().getViewAliasItemList().size(); i++) {
188                TViewAliasItem aliasItem = stmt.getViewAliasClause().getViewAliasItemList().getViewAliasItem(i);
189                if (aliasItem != null && aliasItem.getAlias() != null) {
190                    view.addColumn(aliasItem.getAlias().toString());
191                }
192            }
193        }
194        // Infer columns from SELECT if no explicit column list
195        else if (stmt.getSubquery() != null) {
196            processCreateTableAsSelect(stmt.getSubquery(), view);
197        }
198    }
199
200    /**
201     * Get or create a table in the environment based on the table name.
202     * Handles qualified names (catalog.schema.table).
203     */
204    private TSQLTable getOrCreateTable(TSQLEnv env, TObjectName tableName) {
205        // Extract name parts
206        String catalogName = tableName.getDatabaseString();
207        String schemaName = tableName.getSchemaString();
208        String simpleTableName = tableName.getObjectString();
209
210        if (simpleTableName == null || simpleTableName.isEmpty()) {
211            return null;
212        }
213
214        // Use defaults if not specified
215        if (catalogName == null || catalogName.isEmpty()) {
216            catalogName = env.getDefaultCatalogName();
217            if (catalogName == null) {
218                catalogName = "default";
219            }
220        }
221        if (schemaName == null || schemaName.isEmpty()) {
222            schemaName = env.getDefaultSchemaName();
223            if (schemaName == null) {
224                schemaName = "dbo";
225            }
226        }
227
228        // Create catalog, schema, and table
229        TSQLCatalog catalog = env.createSQLCatalog(catalogName);
230        TSQLSchema schema = catalog.createSchema(schemaName);
231        return schema.createTable(simpleTableName);
232    }
233}