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}