001package gudusoft.gsqlparser.resolver; 002 003import gudusoft.gsqlparser.*; 004import gudusoft.gsqlparser.compiler.TContext; 005import gudusoft.gsqlparser.compiler.TScope; 006import gudusoft.gsqlparser.nodes.*; 007import gudusoft.gsqlparser.sqlenv.TSQLEnv; 008import gudusoft.gsqlparser.sqlenv.TSQLFunction; 009import gudusoft.gsqlparser.sqlenv.TSQLTable; 010import gudusoft.gsqlparser.stmt.TCreateFunctionStmt; 011import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement; 012import gudusoft.gsqlparser.stmt.TSelectSqlStatement; 013 014import java.util.Stack; 015 016/** 017 * TMetadataCollector is responsible for collecting metadata information from SQL statements, 018 * particularly focusing on table and function definitions. It traverses the parse tree 019 * and builds up metadata in the global scope. 020 */ 021public class TMetadataCollector extends TParseTreeVisitor { 022 private TContext globalScope; // Stores global context including SQL environment 023 private TStatementList sqlStatements; // List of SQL statements to analyze 024 private Stack<TScope> scopeStack = new Stack<>(); // Maintains scope hierarchy 025 026 /** 027 * Constructor for TMetadataCollector. 028 * Initializes the collector with a list of SQL statements and a global context. 029 * 030 * @param sqls List of SQL statements to analyze 031 * @param context Global context that will store collected metadata 032 */ 033 public TMetadataCollector(TStatementList sqls, TContext context) { 034 this.globalScope = context; 035 sqlStatements = sqls; 036 scopeStack.push(globalScope); 037 } 038 039 /** 040 * Main method to start metadata collection process. 041 * Iterates through all SQL statements and collects metadata from each one. 042 * This includes: 043 * - Table definitions from CREATE TABLE statements 044 * - Function definitions from CREATE FUNCTION statements 045 * - Other metadata from various SQL statements 046 */ 047 public void collect() { 048 for (int i = 0; i < sqlStatements.size(); i++) { 049 sqlStatements.get(i).acceptChildren(this); 050 } 051 } 052 053 /** 054 * Processes CREATE TABLE statements to collect table metadata. 055 * Handles both regular table creation and CREATE TABLE AS SELECT. 056 * 057 * @param stmt The CREATE TABLE statement to process 058 * 059 * Actions performed: 060 * 1. Creates new table entry in SQL environment 061 * 2. Sets table name and resolves it 062 * 3. Processes column definitions 063 * 4. Handles subquery-based table creation 064 */ 065 /** Register only a schema declared by this CREATE, never an unknown LIKE/CLONE shape. */ 066 public static TSQLTable registerDeclaredTable(TCreateTableSqlStatement stmt, TSQLEnv env) { 067 return env == null ? null 068 : registerDeclaredTable(stmt, env, env.getDefaultCatalogName(), env.getDefaultSchemaName()); 069 } 070 071 /** 072 * {@link #registerDeclaredTable(TCreateTableSqlStatement, TSQLEnv)} as if {@code env}'s current database and schema 073 * were {@code defaultCatalog} and {@code defaultSchema}, without changing them. 074 */ 075 public static TSQLTable registerDeclaredTable(TCreateTableSqlStatement stmt, TSQLEnv env, String defaultCatalog, 076 String defaultSchema) { 077 TSQLTable table = addDeclaredTable(stmt, env, defaultCatalog, defaultSchema); 078 if (table == null) return null; 079 stmt.getTargetTable().setResolvedTable(table); 080 stmt.getTargetTable().getTableName().setSqlEnv(env); 081 return table; 082 } 083 084 /** 085 * The catalog half of {@link #registerDeclaredTable}: put the declared table in {@code env} and return it, leaving 086 * the statement as it is. A table {@code env} already holds there is kept, unless the statement replaces it. 087 */ 088 public static TSQLTable addDeclaredTable(TCreateTableSqlStatement stmt, TSQLEnv env) { 089 return env == null ? null : addDeclaredTable(stmt, env, env.getDefaultCatalogName(), env.getDefaultSchemaName()); 090 } 091 092 /** {@link #addDeclaredTable(TCreateTableSqlStatement, TSQLEnv)} in an explicit database and schema. */ 093 public static TSQLTable addDeclaredTable(TCreateTableSqlStatement stmt, TSQLEnv env, String defaultCatalog, 094 String defaultSchema) { 095 if (!hasDeclaredShape(stmt, env)) return null; 096 TObjectName name = stmt.getTableName(); 097 gudusoft.gsqlparser.sqlenv.TSQLSchema owner = declaredTableOwner(name, stmt.dbvendor, env, defaultCatalog, 098 defaultSchema); 099 TSQLTable table = owner.findTable(name.getObjectString()); 100 if (table == null || replacesTable(stmt)) { 101 table = new TSQLTable(owner, name.getObjectString()); 102 if (stmt.getColumnList() != null) for (TColumnDefinition column : stmt.getColumnList()) { 103 if (column.getColumnName() != null) table.addColumn(column.getColumnName().toString(), column.getDatatype()); 104 } 105 } 106 return table; 107 } 108 109 /** 110 * The schema a table named {@code name} is declared in: its own qualification, else {@code defaultCatalog} and 111 * {@code defaultSchema}, else DEFAULT (dbo for SQL Server). Creates the catalog and schema when missing. 112 */ 113 public static gudusoft.gsqlparser.sqlenv.TSQLSchema declaredTableOwner(TObjectName name, EDbVendor vendor, 114 TSQLEnv env, String defaultCatalog, String defaultSchema) { 115 return env.createSQLCatalog(declaredCatalog(name, defaultCatalog)) 116 .createSchema(declaredSchema(name, vendor, defaultSchema)); 117 } 118 119 /** 120 * The table {@code env} already holds where a declaration of {@code stmt}'s table would go if {@code env}'s current 121 * database and schema were {@code defaultCatalog} and {@code defaultSchema}, or null. Creates no catalog or schema. 122 */ 123 public static TSQLTable findDeclaredTable(TCreateTableSqlStatement stmt, TSQLEnv env, String defaultCatalog, 124 String defaultSchema) { 125 if (env == null || !env.isEnableGetMetadataFromDDL() || stmt.getTableName() == null) return null; 126 TObjectName name = stmt.getTableName(); 127 gudusoft.gsqlparser.sqlenv.TSQLCatalog catalog = env.searchCatalog(declaredCatalog(name, defaultCatalog)); 128 gudusoft.gsqlparser.sqlenv.TSQLSchema owner = catalog == null ? null 129 : catalog.getSchema(declaredSchema(name, stmt.dbvendor, defaultSchema), false); 130 return owner == null ? null : owner.findTable(name.getObjectString()); 131 } 132 133 /** CREATE OR REPLACE without IF NOT EXISTS: the declaration takes the place of a table of that name. */ 134 public static boolean replacesTable(TCreateTableSqlStatement stmt) { 135 if (stmt.isIfNotExists()) return false; 136 for (int i = 0; i < stmt.sourcetokenlist.size(); i++) { 137 TSourceToken token = stmt.sourcetokenlist.get(i); 138 if (token.tokencode == TBaseType.rrw_table) break; 139 if (token.tokencode == TBaseType.rrw_replace) return true; 140 } 141 return false; 142 } 143 144 private static boolean hasDeclaredShape(TCreateTableSqlStatement stmt, TSQLEnv env) { 145 if (env == null || !env.isEnableGetMetadataFromDDL() || stmt.getTargetTable() == null) return false; 146 boolean explicit = stmt.getColumnList() != null && stmt.getColumnList().size() > 0; 147 return explicit || stmt.getTableSourceType() == TCreateTableSqlStatement.TableSourceType.normal; 148 } 149 150 private static String declaredCatalog(TObjectName name, String defaultCatalog) { 151 String catalog = name.getDatabaseString(); 152 if (catalog == null || catalog.isEmpty()) catalog = defaultCatalog; 153 return catalog == null ? "DEFAULT" : catalog; 154 } 155 156 private static String declaredSchema(TObjectName name, EDbVendor vendor, String defaultSchema) { 157 String schema = name.getSchemaString(); 158 if (schema == null || schema.isEmpty()) schema = defaultSchema; 159 if (schema != null) return schema; 160 return (vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql) ? "dbo" : "DEFAULT"; 161 } 162 163 public void preVisit(TCreateTableSqlStatement stmt) { 164 if (stmt.getColumnList().size() > 0 || stmt.getTableSourceType() == TCreateTableSqlStatement.TableSourceType.normal) { 165 registerDeclaredTable(stmt, globalScope.getSqlEnv()); 166 return; 167 } 168 if (stmt.getTableSourceType() != TCreateTableSqlStatement.TableSourceType.subquery) return; 169 170 TSQLTable sqlTable = null; 171 TTable createTable = stmt.getTargetTable(); 172 if (globalScope.getSqlEnv() == null) return; 173 174 // Create new table entry 175 if (stmt.getTargetTable() != null) { 176 sqlTable = globalScope.getSqlEnv().addTable(createTable.getFullName(), true); 177 } 178 if (sqlTable == null) return; 179 createTable.setResolvedTable(sqlTable); 180 181 // Set SQL environment for table name 182 createTable.getTableName().setSqlEnv(globalScope.getSqlEnv()); 183 184 // Process columns 185 if (stmt.getColumnList().size() != 0) { 186 // Handle explicit column definitions 187 for(TColumnDefinition cd : stmt.getColumnList()) { 188 sqlTable.addColumn(cd.getColumnName().toString(), cd.getDatatype()); 189 } 190 } else if (stmt.getSubQuery() != null) { 191 // Handle CREATE TABLE AS SELECT 192 TSelectSqlStatement viewQuery = stmt.getSubQuery(); 193 if (viewQuery.isCombinedQuery()) { 194 viewQuery = viewQuery.getRightStmt(); 195 } 196 197 TResultColumnList resultColumnList = stmt.getSubQuery().getResultColumnList(); 198 if (resultColumnList != null) { 199 for (TResultColumn resultColumn : resultColumnList) { 200 if (resultColumn.getAliasNameList().size() > 0){ 201 // select t.a1, t.a2, stack(2, 'T', t.a1, t.a2, t.a3/t.a4, t.a5/t.a6, 'T+0', t.a7, t.a8, t.a9/t.a10, t.a11/t.a12) as (b1, b2, b3, b4, b5) from db1.table1 t 202 for(int j = 0; j < resultColumn.getAliasNameList().size(); j++){ 203 TObjectName column = resultColumn.getAliasNameList().get(j); 204 sqlTable.addColumn(TSQLEnv.getObjectName( TBaseType.removeQuoteChar(column.toString()))); 205 createTable.getLinkedColumns().addObjectName(column); // for getTableColumn work correctly 206 } 207 208 }else{ 209 sqlTable.addColumn(TSQLEnv.getObjectName(resultColumn.getDisplayName())); 210 } 211 212 } 213 } 214 } 215 } 216 217 /** 218 * Processes CREATE FUNCTION statements to collect function metadata. 219 * Captures function name, parameters, and return type information. 220 * 221 * @param stmt The CREATE FUNCTION statement to process 222 * 223 * Actions performed: 224 * 1. Creates new function entry in SQL environment 225 * 2. Processes function parameters 226 * 3. Sets function return type 227 */ 228 public void preVisit(TCreateFunctionStmt stmt) { 229 if (globalScope.getSqlEnv() == null) return; 230 231 // Create new function entry 232 TSQLFunction sqlFunction = globalScope.getSqlEnv().addFunction(stmt.getFunctionName().toString(), true); 233 if (sqlFunction == null) return; 234 if (stmt.getParameterDeclarations() == null) return; 235 236 // Process function parameters 237 int c = 0; 238 for(TParameterDeclaration p : stmt.getParameterDeclarations()) { 239 if (p.getParameterName() != null) { 240 sqlFunction.addParameter(c, p.getDataType(), p.getParameterName().toString()); 241 } else { 242 sqlFunction.addParameter(c, p.getDataType()); 243 } 244 } 245 246 // Set function return type 247 sqlFunction.setReturnType(stmt.getReturnDataType()); 248 } 249}