001package gudusoft.gsqlparser.sqlenv; 002 003import gudusoft.gsqlparser.TBaseType; 004import gudusoft.gsqlparser.util.SQLUtil; 005 006import java.util.List; 007 008/** 009 * Hierarchical Resolver (Phase 2 - 分层索引解析器) 010 * 011 * <p>使用分层索引(LinkedHashMap)进行 O(1) schema 对象查找。 012 * 013 * <p><strong>核心设计:</strong> 014 * <ul> 015 * <li>每一层使用 IdentifierService 生成索引键 016 * <li>支持部分限定名(table, schema.table, catalog.schema.table) 017 * <li>当 USE_HIERARCHICAL_INDEX = false 时,回退到 legacy 查找 018 * </ul> 019 * 020 * <p>使用示例: 021 * <pre> 022 * TSQLEnv env = ...; 023 * HierarchicalResolver resolver = new HierarchicalResolver(); 024 * 025 * // 查找表 026 * TSQLTable table = resolver.findTable(env, "mydb.myschema.mytable"); 027 * 028 * // 查找列 029 * TSQLColumn column = resolver.findColumn(table, "mycolumn"); 030 * </pre> 031 * 032 * @since 3.1.0.9 033 */ 034public class HierarchicalResolver { 035 036 /** 037 * 查找 schema 对象(通用方法) 038 * 039 * <p>支持三种限定名格式: 040 * <ul> 041 * <li>单段:table(仅对象名) 042 * <li>双段:schema.table(schema + 对象名) 043 * <li>三段:catalog.schema.table(catalog + schema + 对象名) 044 * </ul> 045 * 046 * @param env SQL 环境 047 * @param qualifiedName 限定名(可能是部分限定) 048 * @param objectType 对象类型 049 * @return schema 对象,未找到时返回 null 050 */ 051 public TSQLSchemaObject findSchemaObject(TSQLEnv env, String qualifiedName, ESQLDataObjectType objectType) { 052 if (!TBaseType.USE_HIERARCHICAL_INDEX) { 053 // Feature flag disabled, use legacy search 054 return null; 055 } 056 057 if (qualifiedName == null || qualifiedName.isEmpty()) { 058 return null; 059 } 060 061 try { 062 // 解析限定名为段列表(catalog, schema, object) 063 List<String> segments = SQLUtil.parseNames(qualifiedName); 064 if (segments.isEmpty()) { 065 return null; 066 } 067 068 int segmentCount = segments.size(); 069 070 // 根据段数决定查找策略 071 if (segmentCount == 1) { 072 // 单段:仅对象名,需要在当前 schema 中查找 073 return findInCurrentSchema(env, segments.get(0), objectType); 074 } else if (segmentCount == 2) { 075 // 双段:schema.object 076 String schemaName = segments.get(0); 077 String objectName = segments.get(1); 078 return findInSchema(env, null, schemaName, objectName, objectType); 079 } else if (segmentCount == 3) { 080 // 三段:catalog.schema.object 081 String catalogName = segments.get(0); 082 String schemaName = segments.get(1); 083 String objectName = segments.get(2); 084 return findInSchema(env, catalogName, schemaName, objectName, objectType); 085 } else { 086 // 超过 3 段,不支持 087 return null; 088 } 089 } catch (Throwable ignore) { 090 // 任何异常都回退到 legacy 查找 091 return null; 092 } 093 } 094 095 /** 096 * 在当前 schema 中查找对象 097 * 098 * @param env SQL 环境 099 * @param objectName 对象名 100 * @param objectType 对象类型 101 * @return schema 对象,未找到时返回 null 102 */ 103 private TSQLSchemaObject findInCurrentSchema(TSQLEnv env, String objectName, ESQLDataObjectType objectType) { 104 // Get default catalog and schema names 105 String defaultCatalogName = env.getDefaultCatalogName(); 106 String defaultSchemaName = env.getDefaultSchemaName(); 107 108 if (defaultSchemaName == null) { 109 return null; 110 } 111 112 // Find the schema object in the default schema 113 return findInSchema(env, defaultCatalogName, defaultSchemaName, objectName, objectType); 114 } 115 116 /** 117 * 在指定 schema 中查找对象 118 * 119 * @param env SQL 环境 120 * @param catalogName catalog 名称(可为 null,表示当前 catalog) 121 * @param schemaName schema 名称 122 * @param objectName 对象名 123 * @param objectType 对象类型 124 * @return schema 对象,未找到时返回 null 125 */ 126 private TSQLSchemaObject findInSchema(TSQLEnv env, String catalogName, String schemaName, 127 String objectName, ESQLDataObjectType objectType) { 128 // 1. 找到 catalog 129 TSQLCatalog catalog; 130 if (catalogName == null || catalogName.isEmpty()) { 131 // Use default catalog 132 String defaultCatalog = env.getDefaultCatalogName(); 133 if (defaultCatalog == null) { 134 return null; 135 } 136 catalog = env.getSQLCatalog(defaultCatalog, false); 137 } else { 138 catalog = env.getSQLCatalog(catalogName, false); 139 } 140 141 if (catalog == null) { 142 return null; 143 } 144 145 // 2. 找到 schema 146 TSQLSchema schema = catalog.searchSchema(schemaName); 147 if (schema == null) { 148 return null; 149 } 150 151 // 3. 找到对象 152 return schema.findSchemaObject(objectType, objectName); 153 } 154 155 /** 156 * 查找表(便捷方法) 157 * 158 * @param env SQL 环境 159 * @param qualifiedName 限定名 160 * @return 表对象,未找到时返回 null 161 */ 162 public TSQLTable findTable(TSQLEnv env, String qualifiedName) { 163 TSQLSchemaObject obj = findSchemaObject(env, qualifiedName, ESQLDataObjectType.dotTable); 164 return (obj instanceof TSQLTable) ? (TSQLTable) obj : null; 165 } 166 167 /** 168 * 查找函数(便捷方法) 169 * 170 * @param env SQL 环境 171 * @param qualifiedName 限定名 172 * @return 函数对象,未找到时返回 null 173 */ 174 public TSQLFunction findFunction(TSQLEnv env, String qualifiedName) { 175 TSQLSchemaObject obj = findSchemaObject(env, qualifiedName, ESQLDataObjectType.dotFunction); 176 return (obj instanceof TSQLFunction) ? (TSQLFunction) obj : null; 177 } 178 179 /** 180 * 查找存储过程(便捷方法) 181 * 182 * @param env SQL 环境 183 * @param qualifiedName 限定名 184 * @return 存储过程对象,未找到时返回 null 185 */ 186 public TSQLProcedure findProcedure(TSQLEnv env, String qualifiedName) { 187 TSQLSchemaObject obj = findSchemaObject(env, qualifiedName, ESQLDataObjectType.dotProcedure); 188 return (obj instanceof TSQLProcedure) ? (TSQLProcedure) obj : null; 189 } 190 191 /** 192 * 查找列(便捷方法) 193 * 194 * @param table 表对象 195 * @param columnName 列名 196 * @return 列对象,未找到时返回 null 197 */ 198 public TSQLColumn findColumn(TSQLTable table, String columnName) { 199 if (!TBaseType.USE_HIERARCHICAL_INDEX) { 200 return null; 201 } 202 203 if (table == null || columnName == null || columnName.isEmpty()) { 204 return null; 205 } 206 207 try { 208 return table.getColumn(columnName); 209 } catch (Throwable ignore) { 210 return null; 211 } 212 } 213 214 /** 215 * 批量查找列(便捷方法) 216 * 217 * <p>用于 SELECT * 等场景,需要获取表的所有列。 218 * 219 * @param table 表对象 220 * @return 列列表,表不存在时返回空列表 221 */ 222 public List<TSQLColumn> findAllColumns(TSQLTable table) { 223 if (table == null) { 224 return java.util.Collections.emptyList(); 225 } 226 227 try { 228 return table.getColumnList(); 229 } catch (Throwable ignore) { 230 return java.util.Collections.emptyList(); 231 } 232 } 233}