001package gudusoft.gsqlparser.sqlenv; 002 003import gudusoft.gsqlparser.EDataType; 004import gudusoft.gsqlparser.EDbVendor; 005import gudusoft.gsqlparser.TBaseType; 006import gudusoft.gsqlparser.TGSqlParser; 007import gudusoft.gsqlparser.nodes.TObjectName; 008import gudusoft.gsqlparser.nodes.TTypeName; 009import gudusoft.gsqlparser.sqlenv.catalog.CatalogStoreProvider; 010import gudusoft.gsqlparser.sqlenv.catalog.ICatalogProvider; 011import gudusoft.gsqlparser.util.Logger; 012import gudusoft.gsqlparser.util.LoggerFactory; 013import gudusoft.gsqlparser.util.SQLUtil; 014 015import java.util.ArrayList; 016import java.util.EnumMap; 017import java.util.List; 018import java.util.Map; 019import java.util.concurrent.ConcurrentHashMap; 020import java.util.concurrent.CopyOnWriteArrayList; 021 022import static gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.*; 023 024/** 025 * SQL environment includes the metadata of a list of databases. The typical scenario is there is one database includes 026 * some schemas, and each schema includes some tables, views, procedures and etc. 027 * <br> 028 * <br>Database known as catalog in ANSI SQL. 029 * <br>Each catalog including a list of schemas. 030 * <br>Each schema including a list of schema objects such as table, procedure, function, trigger and more. 031 * <br> 032 * Implement your own concrete class derived from this class to get the metadata from a real database. 033 * Usually, this is done by querying the INFORMATION_SCHEMA in the {@link #initSQLEnv()} method which should be override 034 * in your own class. 035 * <br><br> 036 * 037 */ 038public abstract class TSQLEnv { 039 040 private static final Logger logger = LoggerFactory.getLogger(TSQLEnv.class); 041 042 public static final Map<EDbVendor, Boolean> columnCollationCaseSensitive; 043 044 static { 045 columnCollationCaseSensitive = new EnumMap<>(EDbVendor.class); 046 columnCollationCaseSensitive.put(EDbVendor.dbvaccess, false); 047 columnCollationCaseSensitive.put(EDbVendor.dbvansi, false); 048 columnCollationCaseSensitive.put(EDbVendor.dbvathena, false); 049 columnCollationCaseSensitive.put(EDbVendor.dbvazuresql, false); 050 columnCollationCaseSensitive.put(EDbVendor.dbvbigquery, false); 051 columnCollationCaseSensitive.put(EDbVendor.dbvclickhouse, false); 052 columnCollationCaseSensitive.put(EDbVendor.dbvcouchbase, false); 053 columnCollationCaseSensitive.put(EDbVendor.dbvdax, false); 054 columnCollationCaseSensitive.put(EDbVendor.dbvdb2, false); 055 columnCollationCaseSensitive.put(EDbVendor.dbvexasol, false); 056 columnCollationCaseSensitive.put(EDbVendor.dbvfirebird, false); 057 columnCollationCaseSensitive.put(EDbVendor.dbvgeneric, false); 058 columnCollationCaseSensitive.put(EDbVendor.dbvgreenplum, false); 059 columnCollationCaseSensitive.put(EDbVendor.dbvhana, false); 060 columnCollationCaseSensitive.put(EDbVendor.dbvhive, false); 061 columnCollationCaseSensitive.put(EDbVendor.dbvimpala, false); 062 columnCollationCaseSensitive.put(EDbVendor.dbvinformix, false); 063 columnCollationCaseSensitive.put(EDbVendor.dbvmdx, false); 064 columnCollationCaseSensitive.put(EDbVendor.dbvmysql, false); 065 columnCollationCaseSensitive.put(EDbVendor.dbvmssql, false); 066 columnCollationCaseSensitive.put(EDbVendor.dbvnetezza, false); 067 columnCollationCaseSensitive.put(EDbVendor.dbvodbc, false); 068 columnCollationCaseSensitive.put(EDbVendor.dbvopenedge, false); 069 columnCollationCaseSensitive.put(EDbVendor.dbvoracle, true); 070 columnCollationCaseSensitive.put(EDbVendor.dbvdameng, true); 071 columnCollationCaseSensitive.put(EDbVendor.dbvoceanbase, false); // mirrors MySQL — default tenant mode is MYSQL 072 columnCollationCaseSensitive.put(EDbVendor.dbvpostgresql, false); 073 columnCollationCaseSensitive.put(EDbVendor.dbvduckdb, false); 074 columnCollationCaseSensitive.put(EDbVendor.dbvpresto, false); 075 columnCollationCaseSensitive.put(EDbVendor.dbvflink, false); 076 columnCollationCaseSensitive.put(EDbVendor.dbvredshift, false); 077 columnCollationCaseSensitive.put(EDbVendor.dbvsnowflake, false); 078 columnCollationCaseSensitive.put(EDbVendor.dbvsoql, false); 079 columnCollationCaseSensitive.put(EDbVendor.dbvsparksql, false); 080 columnCollationCaseSensitive.put(EDbVendor.dbvsybase, false); 081 columnCollationCaseSensitive.put(EDbVendor.dbvteradata, false); 082 columnCollationCaseSensitive.put(EDbVendor.dbvtrino, false); 083 columnCollationCaseSensitive.put(EDbVendor.dbvvertica, false); 084 columnCollationCaseSensitive.put(EDbVendor.dbvdatabricks, false); 085 columnCollationCaseSensitive.put(EDbVendor.dbvgaussdb, false); 086 columnCollationCaseSensitive.put(EDbVendor.dbvedb, false); 087 columnCollationCaseSensitive.put(EDbVendor.dbvdoris, false); // Doris is MySQL-compatible, case-insensitive 088 columnCollationCaseSensitive.put(EDbVendor.dbvstarrocks, false); // StarRocks is MySQL-compatible, case-insensitive 089 columnCollationCaseSensitive.put(EDbVendor.dbvsqlite, false); // SQLite is case-insensitive for identifiers 090 columnCollationCaseSensitive.put(EDbVendor.dbvpowerquery, true); // M identifiers are case-sensitive 091 092 // Compile-time check to ensure all enum values are covered 093 if (columnCollationCaseSensitive.size() != EDbVendor.values().length) { 094 throw new IllegalStateException("columnCollationCaseSensitive map must contain all EDbVendor values"); 095 } 096 } 097 098 public static final Map<EDbVendor, Boolean> functionCollationCaseSensitive; 099 100 static { 101 functionCollationCaseSensitive = new EnumMap<>(EDbVendor.class); 102 functionCollationCaseSensitive.put(EDbVendor.dbvaccess, false); 103 functionCollationCaseSensitive.put(EDbVendor.dbvansi, false); 104 functionCollationCaseSensitive.put(EDbVendor.dbvathena, false); 105 functionCollationCaseSensitive.put(EDbVendor.dbvazuresql, false); 106 functionCollationCaseSensitive.put(EDbVendor.dbvbigquery, false); 107 functionCollationCaseSensitive.put(EDbVendor.dbvclickhouse, false); 108 functionCollationCaseSensitive.put(EDbVendor.dbvcouchbase, false); 109 functionCollationCaseSensitive.put(EDbVendor.dbvdax, false); 110 functionCollationCaseSensitive.put(EDbVendor.dbvdb2, false); 111 functionCollationCaseSensitive.put(EDbVendor.dbvexasol, false); 112 functionCollationCaseSensitive.put(EDbVendor.dbvfirebird, false); 113 functionCollationCaseSensitive.put(EDbVendor.dbvgeneric, false); 114 functionCollationCaseSensitive.put(EDbVendor.dbvgreenplum, false); 115 functionCollationCaseSensitive.put(EDbVendor.dbvhana, false); 116 functionCollationCaseSensitive.put(EDbVendor.dbvhive, false); 117 functionCollationCaseSensitive.put(EDbVendor.dbvimpala, false); 118 functionCollationCaseSensitive.put(EDbVendor.dbvinformix, false); 119 functionCollationCaseSensitive.put(EDbVendor.dbvmdx, false); 120 functionCollationCaseSensitive.put(EDbVendor.dbvmysql, false); 121 functionCollationCaseSensitive.put(EDbVendor.dbvmssql, false); 122 functionCollationCaseSensitive.put(EDbVendor.dbvnetezza, false); 123 functionCollationCaseSensitive.put(EDbVendor.dbvodbc, false); 124 functionCollationCaseSensitive.put(EDbVendor.dbvopenedge, false); 125 functionCollationCaseSensitive.put(EDbVendor.dbvoracle, true); 126 functionCollationCaseSensitive.put(EDbVendor.dbvdameng, true); 127 functionCollationCaseSensitive.put(EDbVendor.dbvoceanbase, false); // mirrors MySQL — default tenant mode is MYSQL 128 functionCollationCaseSensitive.put(EDbVendor.dbvpostgresql, true); 129 functionCollationCaseSensitive.put(EDbVendor.dbvduckdb, false); 130 functionCollationCaseSensitive.put(EDbVendor.dbvpresto, false); 131 functionCollationCaseSensitive.put(EDbVendor.dbvflink, false); 132 functionCollationCaseSensitive.put(EDbVendor.dbvredshift, false); 133 functionCollationCaseSensitive.put(EDbVendor.dbvsnowflake, false); 134 functionCollationCaseSensitive.put(EDbVendor.dbvsoql, false); 135 functionCollationCaseSensitive.put(EDbVendor.dbvsparksql, false); 136 functionCollationCaseSensitive.put(EDbVendor.dbvsybase, false); 137 functionCollationCaseSensitive.put(EDbVendor.dbvteradata, false); 138 functionCollationCaseSensitive.put(EDbVendor.dbvtrino, false); 139 functionCollationCaseSensitive.put(EDbVendor.dbvvertica, false); 140 functionCollationCaseSensitive.put(EDbVendor.dbvdatabricks, false); 141 functionCollationCaseSensitive.put(EDbVendor.dbvgaussdb, false); 142 functionCollationCaseSensitive.put(EDbVendor.dbvedb, false); 143 functionCollationCaseSensitive.put(EDbVendor.dbvdoris, false); // Doris is MySQL-compatible, case-insensitive 144 functionCollationCaseSensitive.put(EDbVendor.dbvstarrocks, false); // StarRocks is MySQL-compatible, case-insensitive 145 functionCollationCaseSensitive.put(EDbVendor.dbvsqlite, false); // SQLite is case-insensitive for identifiers 146 functionCollationCaseSensitive.put(EDbVendor.dbvpowerquery, true); // M is case-sensitive 147 148 // Compile-time check to ensure all enum values are covered 149 if (functionCollationCaseSensitive.size() != EDbVendor.values().length) { 150 throw new IllegalStateException("functionCollationCaseSensitive map must contain all EDbVendor values"); 151 } 152 } 153 public static final Map<EDbVendor, Boolean> tableCollationCaseSensitive; 154 155 static { 156 tableCollationCaseSensitive = new EnumMap<>(EDbVendor.class); 157 tableCollationCaseSensitive.put(EDbVendor.dbvaccess, false); 158 tableCollationCaseSensitive.put(EDbVendor.dbvansi, false); 159 tableCollationCaseSensitive.put(EDbVendor.dbvathena, false); 160 tableCollationCaseSensitive.put(EDbVendor.dbvazuresql, false); 161 tableCollationCaseSensitive.put(EDbVendor.dbvbigquery, false); 162 tableCollationCaseSensitive.put(EDbVendor.dbvclickhouse, false); 163 tableCollationCaseSensitive.put(EDbVendor.dbvcouchbase, false); 164 tableCollationCaseSensitive.put(EDbVendor.dbvdax, false); 165 tableCollationCaseSensitive.put(EDbVendor.dbvdb2, true); 166 tableCollationCaseSensitive.put(EDbVendor.dbvexasol, false); 167 tableCollationCaseSensitive.put(EDbVendor.dbvfirebird, false); 168 tableCollationCaseSensitive.put(EDbVendor.dbvgeneric, false); 169 tableCollationCaseSensitive.put(EDbVendor.dbvgreenplum, false); 170 tableCollationCaseSensitive.put(EDbVendor.dbvhana, false); 171 tableCollationCaseSensitive.put(EDbVendor.dbvhive, false); 172 tableCollationCaseSensitive.put(EDbVendor.dbvimpala, false); 173 tableCollationCaseSensitive.put(EDbVendor.dbvinformix, false); 174 tableCollationCaseSensitive.put(EDbVendor.dbvmdx, false); 175 tableCollationCaseSensitive.put(EDbVendor.dbvmysql, false); 176 tableCollationCaseSensitive.put(EDbVendor.dbvmssql, false); 177 tableCollationCaseSensitive.put(EDbVendor.dbvnetezza, false); 178 tableCollationCaseSensitive.put(EDbVendor.dbvodbc, false); 179 tableCollationCaseSensitive.put(EDbVendor.dbvopenedge, false); 180 tableCollationCaseSensitive.put(EDbVendor.dbvoracle, true); 181 tableCollationCaseSensitive.put(EDbVendor.dbvdameng, true); 182 tableCollationCaseSensitive.put(EDbVendor.dbvoceanbase, false); // mirrors MySQL — default tenant mode is MYSQL 183 tableCollationCaseSensitive.put(EDbVendor.dbvpostgresql, true); 184 tableCollationCaseSensitive.put(EDbVendor.dbvduckdb, false); 185 tableCollationCaseSensitive.put(EDbVendor.dbvpresto, false); 186 tableCollationCaseSensitive.put(EDbVendor.dbvflink, false); 187 tableCollationCaseSensitive.put(EDbVendor.dbvredshift, false); 188 tableCollationCaseSensitive.put(EDbVendor.dbvsnowflake, false); 189 tableCollationCaseSensitive.put(EDbVendor.dbvsoql, false); 190 tableCollationCaseSensitive.put(EDbVendor.dbvsparksql, false); 191 tableCollationCaseSensitive.put(EDbVendor.dbvsybase, false); 192 tableCollationCaseSensitive.put(EDbVendor.dbvteradata, false); 193 tableCollationCaseSensitive.put(EDbVendor.dbvtrino, false); 194 tableCollationCaseSensitive.put(EDbVendor.dbvvertica, false); 195 tableCollationCaseSensitive.put(EDbVendor.dbvdatabricks, false); 196 tableCollationCaseSensitive.put(EDbVendor.dbvgaussdb, false); 197 tableCollationCaseSensitive.put(EDbVendor.dbvedb, false); 198 tableCollationCaseSensitive.put(EDbVendor.dbvdoris, false); // Doris is MySQL-compatible, case-insensitive 199 tableCollationCaseSensitive.put(EDbVendor.dbvstarrocks, false); // StarRocks is MySQL-compatible, case-insensitive 200 tableCollationCaseSensitive.put(EDbVendor.dbvsqlite, false); // SQLite is case-insensitive for identifiers 201 tableCollationCaseSensitive.put(EDbVendor.dbvpowerquery, true); // M is case-sensitive 202 203 // Compile-time check to ensure all enum values are covered 204 if (tableCollationCaseSensitive.size() != EDbVendor.values().length) { 205 throw new IllegalStateException("tableCollationCaseSensitive map must contain all EDbVendor values"); 206 } 207 } 208 209 public static final Map<EDbVendor, Boolean> catalogCollationCaseSensitive; 210 211 static { 212 catalogCollationCaseSensitive = new EnumMap<>(EDbVendor.class); 213 catalogCollationCaseSensitive.put(EDbVendor.dbvaccess, false); 214 catalogCollationCaseSensitive.put(EDbVendor.dbvansi, false); 215 catalogCollationCaseSensitive.put(EDbVendor.dbvathena, false); 216 catalogCollationCaseSensitive.put(EDbVendor.dbvazuresql, false); 217 catalogCollationCaseSensitive.put(EDbVendor.dbvbigquery, false); 218 catalogCollationCaseSensitive.put(EDbVendor.dbvclickhouse, false); 219 catalogCollationCaseSensitive.put(EDbVendor.dbvcouchbase, false); 220 catalogCollationCaseSensitive.put(EDbVendor.dbvdax, false); 221 catalogCollationCaseSensitive.put(EDbVendor.dbvdb2, true); 222 catalogCollationCaseSensitive.put(EDbVendor.dbvexasol, false); 223 catalogCollationCaseSensitive.put(EDbVendor.dbvfirebird, false); 224 catalogCollationCaseSensitive.put(EDbVendor.dbvgeneric, false); 225 catalogCollationCaseSensitive.put(EDbVendor.dbvgreenplum, false); 226 catalogCollationCaseSensitive.put(EDbVendor.dbvhana, false); 227 catalogCollationCaseSensitive.put(EDbVendor.dbvhive, false); 228 catalogCollationCaseSensitive.put(EDbVendor.dbvimpala, false); 229 catalogCollationCaseSensitive.put(EDbVendor.dbvinformix, false); 230 catalogCollationCaseSensitive.put(EDbVendor.dbvmdx, false); 231 catalogCollationCaseSensitive.put(EDbVendor.dbvmysql, true); 232 catalogCollationCaseSensitive.put(EDbVendor.dbvmssql, false); 233 catalogCollationCaseSensitive.put(EDbVendor.dbvnetezza, false); 234 catalogCollationCaseSensitive.put(EDbVendor.dbvodbc, false); 235 catalogCollationCaseSensitive.put(EDbVendor.dbvopenedge, false); 236 catalogCollationCaseSensitive.put(EDbVendor.dbvoracle, false); 237 catalogCollationCaseSensitive.put(EDbVendor.dbvdameng, false); 238 catalogCollationCaseSensitive.put(EDbVendor.dbvoceanbase, true); // mirrors MySQL — catalog (database) is case-sensitive 239 catalogCollationCaseSensitive.put(EDbVendor.dbvpostgresql, true); 240 catalogCollationCaseSensitive.put(EDbVendor.dbvduckdb, false); 241 catalogCollationCaseSensitive.put(EDbVendor.dbvpresto, false); 242 catalogCollationCaseSensitive.put(EDbVendor.dbvflink, false); 243 catalogCollationCaseSensitive.put(EDbVendor.dbvredshift, false); 244 catalogCollationCaseSensitive.put(EDbVendor.dbvsnowflake, false); 245 catalogCollationCaseSensitive.put(EDbVendor.dbvsoql, false); 246 catalogCollationCaseSensitive.put(EDbVendor.dbvsparksql, false); 247 catalogCollationCaseSensitive.put(EDbVendor.dbvsybase, false); 248 catalogCollationCaseSensitive.put(EDbVendor.dbvteradata, false); 249 catalogCollationCaseSensitive.put(EDbVendor.dbvtrino, false); 250 catalogCollationCaseSensitive.put(EDbVendor.dbvvertica, false); 251 catalogCollationCaseSensitive.put(EDbVendor.dbvdatabricks, false); 252 catalogCollationCaseSensitive.put(EDbVendor.dbvgaussdb, false); 253 catalogCollationCaseSensitive.put(EDbVendor.dbvedb, false); 254 catalogCollationCaseSensitive.put(EDbVendor.dbvdoris, true); // Doris is MySQL-compatible, catalog (database) is case-sensitive 255 catalogCollationCaseSensitive.put(EDbVendor.dbvstarrocks, true); // StarRocks is MySQL-compatible, catalog (database) is case-sensitive 256 catalogCollationCaseSensitive.put(EDbVendor.dbvsqlite, true); // SQLite database names (file paths) are case-sensitive on most OS 257 catalogCollationCaseSensitive.put(EDbVendor.dbvpowerquery, true); // M catalog references are case-sensitive 258 259 // Compile-time check to ensure all enum values are covered 260 if (catalogCollationCaseSensitive.size() != EDbVendor.values().length) { 261 throw new IllegalStateException("catalogCollationCaseSensitive map must contain all EDbVendor values"); 262 } 263 } 264 265 public static final Map<EDbVendor, Boolean> defaultCollationCaseSensitive; 266 267 static { 268 defaultCollationCaseSensitive = new EnumMap<>(EDbVendor.class); 269 defaultCollationCaseSensitive.put(EDbVendor.dbvaccess, false); 270 defaultCollationCaseSensitive.put(EDbVendor.dbvansi, false); 271 defaultCollationCaseSensitive.put(EDbVendor.dbvathena, false); 272 defaultCollationCaseSensitive.put(EDbVendor.dbvazuresql, false); 273 defaultCollationCaseSensitive.put(EDbVendor.dbvbigquery, false); 274 defaultCollationCaseSensitive.put(EDbVendor.dbvclickhouse, false); 275 defaultCollationCaseSensitive.put(EDbVendor.dbvcouchbase, false); 276 defaultCollationCaseSensitive.put(EDbVendor.dbvdax, false); 277 defaultCollationCaseSensitive.put(EDbVendor.dbvdb2, false); 278 defaultCollationCaseSensitive.put(EDbVendor.dbvexasol, false); 279 defaultCollationCaseSensitive.put(EDbVendor.dbvfirebird, false); 280 defaultCollationCaseSensitive.put(EDbVendor.dbvgeneric, false); 281 defaultCollationCaseSensitive.put(EDbVendor.dbvgreenplum, false); 282 defaultCollationCaseSensitive.put(EDbVendor.dbvhana, false); 283 defaultCollationCaseSensitive.put(EDbVendor.dbvhive, false); 284 defaultCollationCaseSensitive.put(EDbVendor.dbvimpala, false); 285 defaultCollationCaseSensitive.put(EDbVendor.dbvinformix, false); 286 defaultCollationCaseSensitive.put(EDbVendor.dbvmdx, false); 287 defaultCollationCaseSensitive.put(EDbVendor.dbvmysql, false); 288 defaultCollationCaseSensitive.put(EDbVendor.dbvmssql, false); 289 defaultCollationCaseSensitive.put(EDbVendor.dbvnetezza, false); 290 defaultCollationCaseSensitive.put(EDbVendor.dbvodbc, false); 291 defaultCollationCaseSensitive.put(EDbVendor.dbvopenedge, false); 292 defaultCollationCaseSensitive.put(EDbVendor.dbvoracle, false); 293 defaultCollationCaseSensitive.put(EDbVendor.dbvdameng, false); 294 defaultCollationCaseSensitive.put(EDbVendor.dbvoceanbase, false); // mirrors MySQL — default tenant mode is MYSQL 295 defaultCollationCaseSensitive.put(EDbVendor.dbvpostgresql, false); 296 defaultCollationCaseSensitive.put(EDbVendor.dbvduckdb, false); 297 defaultCollationCaseSensitive.put(EDbVendor.dbvpresto, false); 298 defaultCollationCaseSensitive.put(EDbVendor.dbvflink, false); 299 defaultCollationCaseSensitive.put(EDbVendor.dbvredshift, false); 300 defaultCollationCaseSensitive.put(EDbVendor.dbvsnowflake, false); 301 defaultCollationCaseSensitive.put(EDbVendor.dbvsoql, false); 302 defaultCollationCaseSensitive.put(EDbVendor.dbvsparksql, false); 303 defaultCollationCaseSensitive.put(EDbVendor.dbvsybase, false); 304 defaultCollationCaseSensitive.put(EDbVendor.dbvteradata, false); 305 defaultCollationCaseSensitive.put(EDbVendor.dbvtrino, false); 306 defaultCollationCaseSensitive.put(EDbVendor.dbvvertica, false); 307 defaultCollationCaseSensitive.put(EDbVendor.dbvdatabricks, false); 308 defaultCollationCaseSensitive.put(EDbVendor.dbvgaussdb, false); 309 defaultCollationCaseSensitive.put(EDbVendor.dbvedb, false); 310 defaultCollationCaseSensitive.put(EDbVendor.dbvdoris, false); // Doris is MySQL-compatible, case-insensitive 311 defaultCollationCaseSensitive.put(EDbVendor.dbvstarrocks, false); // StarRocks is MySQL-compatible, case-insensitive 312 defaultCollationCaseSensitive.put(EDbVendor.dbvsqlite, false); // SQLite is case-insensitive by default 313 defaultCollationCaseSensitive.put(EDbVendor.dbvpowerquery, true); // M is case-sensitive by default 314 315 // Compile-time check to ensure all enum values are covered 316 if (defaultCollationCaseSensitive.size() != EDbVendor.values().length) { 317 throw new IllegalStateException("defaultCollationCaseSensitive map must contain all EDbVendor values"); 318 } 319 } 320 321 322 323 public static final Map<EDbVendor, Boolean> isAliasReferenceForbidden; 324 325 static { 326 isAliasReferenceForbidden = new EnumMap<>(EDbVendor.class); 327 isAliasReferenceForbidden.put(EDbVendor.dbvaccess, false); 328 isAliasReferenceForbidden.put(EDbVendor.dbvansi, false); 329 isAliasReferenceForbidden.put(EDbVendor.dbvathena, false); 330 isAliasReferenceForbidden.put(EDbVendor.dbvazuresql, true); 331 isAliasReferenceForbidden.put(EDbVendor.dbvbigquery, true); 332 isAliasReferenceForbidden.put(EDbVendor.dbvclickhouse, false); 333 isAliasReferenceForbidden.put(EDbVendor.dbvcouchbase, false); 334 isAliasReferenceForbidden.put(EDbVendor.dbvdax, false); 335 isAliasReferenceForbidden.put(EDbVendor.dbvdb2, true); 336 isAliasReferenceForbidden.put(EDbVendor.dbvexasol, false); // Exasol disallows forward references 337 isAliasReferenceForbidden.put(EDbVendor.dbvfirebird, false); // Firebird follows standard SQL behavior 338 isAliasReferenceForbidden.put(EDbVendor.dbvgeneric, false); 339 isAliasReferenceForbidden.put(EDbVendor.dbvgreenplum, true); 340 isAliasReferenceForbidden.put(EDbVendor.dbvhana, false); // SAP HANA follows standard SQL behavior 341 isAliasReferenceForbidden.put(EDbVendor.dbvhive, true); 342 isAliasReferenceForbidden.put(EDbVendor.dbvimpala, false); // Impala follows Hive's behavior 343 isAliasReferenceForbidden.put(EDbVendor.dbvinformix, true); 344 isAliasReferenceForbidden.put(EDbVendor.dbvmdx, false); 345 isAliasReferenceForbidden.put(EDbVendor.dbvmysql, true); 346 isAliasReferenceForbidden.put(EDbVendor.dbvmssql, true); 347 isAliasReferenceForbidden.put(EDbVendor.dbvnetezza, true); 348 isAliasReferenceForbidden.put(EDbVendor.dbvodbc, false); 349 isAliasReferenceForbidden.put(EDbVendor.dbvopenedge, false); 350 isAliasReferenceForbidden.put(EDbVendor.dbvoracle, true); 351 isAliasReferenceForbidden.put(EDbVendor.dbvdameng, true); 352 isAliasReferenceForbidden.put(EDbVendor.dbvoceanbase, true); // mirrors MySQL — alias forward reference forbidden 353 isAliasReferenceForbidden.put(EDbVendor.dbvpostgresql, true); 354 isAliasReferenceForbidden.put(EDbVendor.dbvduckdb, true); 355 isAliasReferenceForbidden.put(EDbVendor.dbvpresto, true); 356 isAliasReferenceForbidden.put(EDbVendor.dbvflink, true); // Flink follows standard SQL behavior like SparkSQL 357 isAliasReferenceForbidden.put(EDbVendor.dbvredshift, true); 358 isAliasReferenceForbidden.put(EDbVendor.dbvsnowflake, false); // Snowflake disallows column alias references 359 isAliasReferenceForbidden.put(EDbVendor.dbvsoql, false); 360 isAliasReferenceForbidden.put(EDbVendor.dbvsparksql, true); 361 isAliasReferenceForbidden.put(EDbVendor.dbvsybase, true); 362 isAliasReferenceForbidden.put(EDbVendor.dbvteradata, false); // Teradata follows standard SQL behavior 363 isAliasReferenceForbidden.put(EDbVendor.dbvtrino, true); 364 isAliasReferenceForbidden.put(EDbVendor.dbvvertica, false); // Vertica disallows forward references 365 isAliasReferenceForbidden.put(EDbVendor.dbvdatabricks, true); 366 isAliasReferenceForbidden.put(EDbVendor.dbvgaussdb, true); 367 isAliasReferenceForbidden.put(EDbVendor.dbvedb, true); 368 isAliasReferenceForbidden.put(EDbVendor.dbvdoris, true); // Doris is MySQL-compatible, alias reference forbidden 369 isAliasReferenceForbidden.put(EDbVendor.dbvstarrocks, true); // StarRocks is MySQL-compatible, alias reference forbidden 370 isAliasReferenceForbidden.put(EDbVendor.dbvsqlite, true); // SQLite follows PostgreSQL behavior, alias reference forbidden in same clause 371 isAliasReferenceForbidden.put(EDbVendor.dbvpowerquery, true); // M step-to-step references are forbidden before binding 372 373 // Compile-time check to ensure all enum values are covered 374 if (isAliasReferenceForbidden.size() != EDbVendor.values().length) { 375 throw new IllegalStateException("isAliasReferenceForbidden map must contain all EDbVendor values"); 376 } 377 } 378 379 public static String getStmtSeparatorChar(EDbVendor dbVendor){ 380 String ret = ";"; 381 switch (dbVendor){ 382 case dbvoracle: 383 case dbvteradata: 384 case dbvpostgresql: 385 case dbvduckdb: 386 case dbvredshift: 387 case dbvgreenplum: 388 ret = "/"; 389 break; 390 case dbvdameng: 391 ret = "/"; 392 break; 393 case dbvdb2: 394 ret = "@"; 395 break; 396 case dbvmysql: 397 ret = "$"; 398 break; 399 default: 400 break; 401 } 402 return ret; 403 } 404 405 406 /** 407 * Whether this database support schema or not? 408 * 409 * @param dbVendor 410 * @return 411 */ 412 public static boolean supportSchema(EDbVendor dbVendor){ 413 return (!((dbVendor == EDbVendor.dbvmysql) 414 ||(dbVendor == EDbVendor.dbvteradata)||(dbVendor == EDbVendor.dbvhive)||(dbVendor == EDbVendor.dbvimpala) 415 )); 416 } 417 418 /** 419 * Whether this database support catalog or not? 420 * 421 * @param dbVendor 422 * @return 423 */ 424 public static boolean supportCatalog(EDbVendor dbVendor) { 425// return (!((dbVendor == EDbVendor.dbvoracle) 426// )); 427 return true; 428 } 429 430 /** 431 * used to delimit a database identifier, such as [ used in SQL Server, ` used in MySQL 432 * 433 * @param dbVendor 434 * @return 435 */ 436 public static String delimitedChar(EDbVendor dbVendor){ 437 String ret = "\""; 438 switch (dbVendor){ 439 case dbvmssql: 440 case dbvazuresql: 441 ret = "["; 442 break; 443 case dbvathena: 444 case dbvmysql: 445 case dbvbigquery: 446 case dbvcouchbase: 447 case dbvhive: 448 case dbvimpala: 449 case dbvdatabricks: 450 ret = "`"; 451 break; 452 case dbvdax: 453 ret = "'"; 454 break; 455 default: 456 break; 457 } 458 return ret; 459 } 460 461 public static boolean isDelimitedIdentifier(EDbVendor dbVendor, String identifier){ 462 boolean ret = false; 463 switch (dbVendor){ 464 case dbvmssql: 465 case dbvazuresql: 466 ret = identifier.startsWith("[")||identifier.startsWith("\"")||identifier.startsWith("'"); 467 break; 468 case dbvmysql: 469 case dbvbigquery: 470 case dbvcouchbase: 471 case dbvhive: 472 case dbvimpala: 473 ret = identifier.startsWith("`"); 474 break; 475 case dbvdax: 476 ret = identifier.startsWith("'"); 477 break; 478 default: 479 ret = identifier.startsWith("\""); 480 break; 481 } 482 return ret; 483 } 484 485 public static boolean endsWithDelimitedIdentifier(EDbVendor dbVendor, String identifier){ 486 boolean ret = false; 487 switch (dbVendor){ 488 case dbvmssql: 489 case dbvazuresql: 490 ret = identifier.endsWith("]")||identifier.endsWith("\""); 491 break; 492 case dbvmysql: 493 case dbvbigquery: 494 case dbvcouchbase: 495 case dbvhive: 496 case dbvimpala: 497 ret = identifier.endsWith("`"); 498 break; 499 case dbvdax: 500 ret = identifier.endsWith("'"); 501 break; 502 default: 503 ret = identifier.endsWith("\""); 504 break; 505 } 506 return ret; 507 } 508 509 public boolean isDelimitedIdentifier(String identifier){ 510 return TSQLEnv.isDelimitedIdentifier(this.getDBVendor(),identifier); 511 } 512 513 514 public boolean compareColumn(String ident1, String ident2){ 515 // Route through NameService when flag is enabled 516 if (TBaseType.USE_EXT_NAME_MATCHER) { 517 return nameService.equals(ESQLDataObjectType.dotColumn, ident1, ident2); 518 } 519 return compareIdentifier(this.getDBVendor(),ESQLDataObjectType.dotColumn,ident1,ident2); 520 } 521 522 public boolean compareTable(String ident1, String ident2){ 523 // Route through NameService when flag is enabled 524 if (TBaseType.USE_EXT_NAME_MATCHER) { 525 return nameService.equals(ESQLDataObjectType.dotTable, ident1, ident2); 526 } 527 return compareIdentifier(this.getDBVendor(),ESQLDataObjectType.dotTable,ident1,ident2); 528 } 529 530 public boolean compareIdentifier(ESQLDataObjectType objectType, String ident1, String ident2){ 531 // Route through NameService when flag is enabled 532 if (TBaseType.USE_EXT_NAME_MATCHER) { 533 return nameService.equals(objectType, ident1, ident2); 534 } 535 536 // Legacy path: use existing logic 537 switch (objectType){ 538 case dotTable: 539 return compareTable(ident1,ident2); 540 case dotColumn: 541 return compareColumn(ident1,ident2); 542 default: 543 return compareIdentifier(this.getDBVendor(),objectType,ident1,ident2); 544 } 545 } 546 547 public static boolean compareColumn( EDbVendor dbVendor, TObjectName sourceColumn, TObjectName targetColumn){ 548 return compareQualifiedColumn(dbVendor,sourceColumn.getColumnNameOnly(),targetColumn.getColumnNameOnly(), 549 sourceColumn.getTableString(),targetColumn.getTableString(), 550 sourceColumn.getSchemaString(),targetColumn.getSchemaString(), 551 sourceColumn.getDatabaseString(),targetColumn.getDatabaseString()); 552 } 553 554 public static boolean compareTable( EDbVendor dbVendor, TObjectName sourceTable, TObjectName targetTable){ 555 return compareQualifiedTable(dbVendor, 556 sourceTable.getTableString(),targetTable.getTableString(), 557 sourceTable.getSchemaString(),targetTable.getSchemaString(), 558 sourceTable.getDatabaseString(),targetTable.getDatabaseString() 559 ); 560 } 561 562 public static boolean compareQualifiedTable( EDbVendor dbVendor, String sourceTable, String targetTable, String sourceSchema, String targetSchema, String sourceDatabase, String targetDatabase){ 563 boolean ret = compareIdentifier(dbVendor,dotTable,sourceTable,targetTable); 564 if (!ret) return ret; 565 566 // compare schema 567 ret = compareIdentifier(dbVendor,dotSchema,sourceSchema,targetSchema); 568 if (!ret) return ret; 569 570 // compare database 571 return compareIdentifier(dbVendor,dotCatalog,sourceDatabase,targetDatabase); 572 } 573 574 public static boolean compareQualifiedColumn( EDbVendor dbVendor, String sourceColumn, String targetColumn, String sourceTable, String targetTable, String sourceSchema, String targetSchema, String sourceDatabase, String targetDatabase){ 575 576 boolean ret = compareIdentifier(dbVendor,dotColumn,sourceColumn,targetColumn); 577 if (!ret) return ret; 578 579 return compareQualifiedTable(dbVendor,sourceTable,targetTable,sourceSchema,targetSchema,sourceDatabase,targetDatabase); 580 } 581 582 public static boolean compareIdentifier( EDbVendor dbVendor, ESQLDataObjectType objectType, TObjectName source, TObjectName target){ 583 return compareIdentifier(dbVendor,objectType,source.toString(),target.toString()); 584 } 585 586 public static boolean compareIdentifier( EDbVendor dbVendor, ESQLDataObjectType objectType, String ident1, String ident2){ 587 588// ident1 = IdentifierService.normalizeStatic(dbVendor,objectType,ident1); 589// ident2 = IdentifierService.normalizeStatic(dbVendor,objectType,ident1); 590// 591// return IdentifierService.areEqualStatic(dbVendor,objectType,ident1,ident2); 592 593 ident1 = normalizeIdentifier(dbVendor,objectType,ident1); // IdentifierService.normalizeStatic(dbVendor,objectType,ident1); 594 ident2 = normalizeIdentifier(dbVendor,objectType,ident2); // IdentifierService.normalizeStatic(dbVendor,objectType,ident1); 595 596 boolean collationSensitive = false; 597 switch (objectType){ 598 case dotCatalog: 599 case dotSchema: 600 collationSensitive = catalogCollationCaseSensitive.get(dbVendor); 601 break; 602 case dotOraclePackage: 603 case dotProcedure: 604 case dotTrigger: 605 case dotTable: 606 collationSensitive = tableCollationCaseSensitive.get(dbVendor); 607 break; 608 case dotFunction: 609 collationSensitive = functionCollationCaseSensitive.get(dbVendor); 610 break; 611 case dotColumn: 612 collationSensitive = columnCollationCaseSensitive.get(dbVendor); 613 break; 614 case dotDblink: // oracle dblink 615 collationSensitive = false; 616 break; 617 default: 618 collationSensitive = defaultCollationCaseSensitive.get(dbVendor); 619 break; 620 } 621 622 if (collationSensitive) { 623 return ident1.equals(ident2); 624 } else { 625 return ident1.equalsIgnoreCase(ident2); 626 } 627 628 } 629 630 public String normalizeIdentifier(ESQLDataObjectType sqlDataObjectType, String identifier) { 631 return this.getIdentifierService().normalize(identifier,sqlDataObjectType); 632 } 633 634 /** 635 * 1. remove delimited char if it's delimited/quoted identifier 636 * 2. change the case of the name in the same way as it saved to the information_schema 637 * 638 * @param dbVendor 639 * @param sqlDataObjectType 640 * @param identifier 641 * @return 642 */ 643 public static String normalizeIdentifier(EDbVendor dbVendor, ESQLDataObjectType sqlDataObjectType, String identifier){ 644 String ret; 645 646 if (identifier == null) return identifier; 647 if (identifier.length() == 0) return identifier; 648 649 if (TSQLEnv.isDelimitedIdentifier(dbVendor,identifier)){ 650 ret = TBaseType.getTextWithoutQuoted(identifier); 651 }else { 652 switch (dbVendor){ 653 case dbvdb2: 654 case dbvoracle: 655 ret = identifier.toUpperCase(); 656 break; 657 case dbvdameng: 658 ret = identifier.toUpperCase(); 659 break; 660 case dbvpostgresql: 661 case dbvduckdb: 662 case dbvgreenplum: 663 case dbvredshift: 664 ret = identifier.toLowerCase(); 665 break; 666 default: 667 ret = identifier; 668 break; 669 } 670 } 671 return ret; 672 } 673 674 /** 675 * 比较 一个数据库对象名是否等于或者属于另一个对象 676 * 等于 就是完全相等(根据不同数据库的比较规则) 677 * 属于 表示如下情况: 678 * 1. column1 -> 属于 -> table1.column1 679 * 2. table1 -> 属于 -> db1.schema1.table1 680 * 3. `schema1.table1` -> 属于 -> `db1`.`schema1`.`table1` 681 * 4. `schema1.table1` -> 不属于 -> `db1`.`schema2`.`table1` 682 * 683 * @param sub 684 * @param whole 685 * @return 686 */ 687 public static boolean matchSubObjectNameToWhole(EDbVendor dbVendor, ESQLDataObjectType sqlDataObjectType,String sub, String whole){ 688 List<String> subParts = SQLUtil.parseNames(sub); 689 // `data.RETAIL_PROD_EXCEPTIONS_SOURCE` 没有被分开,需要去掉 `` 后再次拆分 690 if ((subParts.size() == 1)&&(sub.indexOf(".") != -1)){ 691 subParts = SQLUtil.parseNames(IdentifierService.normalizeStatic(dbVendor,sqlDataObjectType,sub)); 692 } 693 694 List<String> wholeParts = SQLUtil.parseNames(whole); 695 if ((wholeParts.size() == 1)&&(whole.indexOf(".") != -1)){ 696 wholeParts = SQLUtil.parseNames(IdentifierService.normalizeStatic(dbVendor,sqlDataObjectType,whole)); 697 } 698 699 700 if(subParts.size() >wholeParts.size()) return false; 701 int k=0; 702 for(String s:subParts){ 703 subParts.set(k,TBaseType.removePrefixOrSuffixQuoteChar(normalizeIdentifier(dbVendor,sqlDataObjectType,s))); 704 k++; 705 } 706 k=0; 707 for(String s:wholeParts){ 708 wholeParts.set(k, TBaseType.removePrefixOrSuffixQuoteChar(normalizeIdentifier(dbVendor,sqlDataObjectType,s))); 709 k++; 710 } 711 712 boolean ret = false; 713 int i= subParts.size() -1; 714 int j = wholeParts.size() -1; 715 while (i >= 0){ 716 if ( !subParts.get(i).toUpperCase().equals(wholeParts.get(j).toUpperCase())) break; 717 if (i == 0) ret = true; 718 i--; 719 j--; 720 } 721 722 return ret; 723 } 724 725 private boolean enableGetMetadataFromDDL = true; 726 727 public void setEnableGetMetadataFromDDL(boolean enableGetMetadataFromDDL) { 728 this.enableGetMetadataFromDDL = enableGetMetadataFromDDL; 729 } 730 731 /** 732 * If this option is enabled, SQLEnv will collect table/view/function/procedure metadata 733 * from the create table/create view/create function/create procedure statement during the parse of the SQL script. 734 * <br>A TSQLEnv object instance must be passed to {@link TGSqlParser} before parsing the SQL script. And this TSQLEnv 735 * instance can be passed to another {@link TGSqlParser} object with the collected database metadata. 736 * <br>Default value is true. 737 * 738 * @return 739 */ 740 public boolean isEnableGetMetadataFromDDL() { 741 return enableGetMetadataFromDDL; 742 } 743 744 /** 745 * create a SQL environment. 746 * 747 * @param dbVendor the database vendor 748 */ 749 public TSQLEnv(EDbVendor dbVendor){ 750 this.dbVendor = dbVendor; 751 this.nameService = new NameService(dbVendor); 752 753 // ===== Phase 0: Initialize IdentifierService first (required by CatalogStore) ===== 754 // Create IdentifierProfile and IdentifierService 755 this.identifierProfile = IdentifierProfile.forVendor( 756 dbVendor, 757 IdentifierProfile.VendorFlags.defaults() // Default configuration 758 ); 759 this.collatorProvider = createCollatorProvider(dbVendor); // SQL Server needs this 760 this.identifierService = new IdentifierService(identifierProfile, collatorProvider); 761 762 // Phase 3.5: Create CatalogStoreProvider (which internally creates CatalogStore) 763 this.catalogProvider = new CatalogStoreProvider(dbVendor, IdentifierProfile.VendorFlags.defaults()); 764 } 765 766 private EDbVendor dbVendor; 767 768 // Name matching service (Phase 2 integration) 769 private final NameService nameService; 770 771 // ===== Phase 3.5: Final catalog implementation ===== 772 private IdentifierProfile identifierProfile; 773 private IdentifierService identifierService; 774 private CollatorProvider collatorProvider; // SQL Server specific 775 private ICatalogProvider catalogProvider; // Uses CatalogStoreProvider 776 777 /** 778 * the database vendor where this SQL environment is generated from. 779 * 780 * @return the database vendor 781 */ 782 public EDbVendor getDBVendor() { 783 return dbVendor; 784 } 785 786 /** 787 * Returns the catalog store (Phase 3.5 integration). 788 * 789 * <p>Note: This method delegates to the underlying CatalogStoreProvider. 790 * Direct access to CatalogStore is provided for backward compatibility 791 * with existing test code.</p> 792 * 793 * @return the catalog store 794 */ 795 public CatalogStore getCatalogStore() { 796 if (catalogProvider instanceof CatalogStoreProvider) { 797 return ((CatalogStoreProvider) catalogProvider).getCatalogStore(); 798 } 799 return null; 800 } 801 802 // ===== Phase 0: Helper methods and getters ===== 803 804 /** 805 * Create CollatorProvider for SQL Server 806 * 807 * @param vendor database vendor 808 * @return CollatorProvider instance, null for non-SQL Server databases 809 */ 810 private CollatorProvider createCollatorProvider(EDbVendor vendor) { 811 if (vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql) { 812 return new CollatorProvider(); // SQL Server collation 813 } 814 return null; 815 } 816 817 /** 818 * Get IdentifierService (Phase 0) 819 * 820 * @return IdentifierService instance 821 */ 822 public IdentifierService getIdentifierService() { 823 return identifierService; 824 } 825 826 /** 827 * Get IdentifierProfile (Phase 0) 828 * 829 * @return IdentifierProfile instance 830 */ 831 public IdentifierProfile getIdentifierProfile() { 832 return identifierProfile; 833 } 834 835 /** 836 * Get ICatalogProvider (Phase 3) 837 * 838 * @return ICatalogProvider instance 839 */ 840 public ICatalogProvider getCatalogProvider() { 841 return catalogProvider; 842 } 843 844 /** 845 * Catalog-input compatibility hook (Phase 1D). 846 * 847 * <p>Lets a {@code TSQLEnv} subclass in {@code gudusoft.gsqlparser.sqlenv.compat} 848 * install an alternative {@link ICatalogProvider} (typically one backed by a 849 * {@code CatalogRuntime}) without reflection or constructor re-entry. See spike 850 * memo {@code docs/designs/catalog-input-interface-spikes/tsqlenv-hook.md}.</p> 851 * 852 * <p>{@code protected final} keeps the surface tight — only subclasses may invoke 853 * it, and they cannot override it. The intended caller is 854 * {@code CatalogRuntimeToSQLEnvBridge}. End users should continue to use the 855 * legacy {@code TSQLEnv} mutation API; this hook is part of the catalog-input 856 * compatibility bridge and is not a general extensibility point.</p> 857 * 858 * @param provider the replacement catalog provider; must not be {@code null}. 859 */ 860 protected final void setCatalogProviderForCompatibility(ICatalogProvider provider) { 861 if (provider == null) { 862 throw new IllegalArgumentException( 863 "TSQLEnv.setCatalogProviderForCompatibility: provider must not be null"); 864 } 865 this.catalogProvider = provider; 866 } 867 868 /** 869 * This method must be override in the subclass to build a SQL environment with real metadata. 870 * <br>this usually done by querying the INFORMATION_SCHEMA 871 */ 872 public abstract void initSQLEnv(); 873 874 /** 875 * a list of catalog/database in this SQL environment. 876 * 877 * @return a list of catalog/database 878 */ 879 public List<TSQLCatalog> getCatalogList() { 880 return catalogList; 881 } 882 883 private String serverName = null; // SQL Server 884 885 private List<TSQLCatalog> catalogList = new CopyOnWriteArrayList<>(); 886 887 /** 888 * add a catalog to the SQL environment, called internally. 889 * 890 * @param sqlCatalog catalog 891 * @return return false if a catalog with the same name already exists. 892 */ 893 protected boolean doAddCatalog(TSQLCatalog sqlCatalog){ 894 // same monitor as getSQLCatalog so the check-then-add is atomic under 895 // concurrent registration; readers iterate the COW list lock-free 896 synchronized (catalogList) { 897 boolean isFound = false; 898 for(TSQLCatalog c : catalogList){ 899 if ( sqlCatalog.getName().compareTo(c.getName()) == 0){ 900 isFound = true; 901 break; 902 } 903 } 904 if (!isFound){ 905 catalogList.add(sqlCatalog); 906 } 907 return !isFound; 908 } 909 } 910 911 private Map<String, TSQLSchemaObject> schemaObjectList = new ConcurrentHashMap<>(); 912 913 // Fast canonical index for fully-qualified lookups (additive, keeps existing map intact) 914 private final Map<NameKey, TSQLSchemaObject> objectIndex = new ConcurrentHashMap<NameKey, TSQLSchemaObject>(); 915 // Reverse index for tables by object name only (speeds up ..table lookups). 916 // COW lists: registration appends while other threads' name resolution reads 917 private final Map<String, java.util.concurrent.CopyOnWriteArrayList<TSQLTable>> tablesByName = 918 new ConcurrentHashMap<String, java.util.concurrent.CopyOnWriteArrayList<TSQLTable>>(); 919 920 /** 921 * put a schema object into the hashmap which can be used to find a schema object in a more efficient way. 922 * 923 * @param schemaObjectName name of schema object 924 * @param schemaObject instance of a schema object 925 * @return always return true. 926 */ 927 protected boolean putSchemaObject(String schemaObjectName, TSQLSchemaObject schemaObject){ 928 String newSchemaName = schemaObjectName; 929 if (schemaObject.getDataObjectType() == dotTable){ 930 if (!tableCollationCaseSensitive.get(this.getDBVendor())){ 931 newSchemaName = newSchemaName.toUpperCase(); 932 } 933 }else { 934 if (!defaultCollationCaseSensitive.get(this.getDBVendor())){ 935 newSchemaName = newSchemaName.toUpperCase(); 936 } 937 } 938 939 if (schemaObject.getDataObjectType() == dotFunction){ 940 newSchemaName = newSchemaName+"$function"; 941 }else if (schemaObject.getDataObjectType() == dotProcedure){ 942 newSchemaName = newSchemaName+"$procedure"; 943 } 944 945 schemaObjectList.put(newSchemaName,schemaObject); 946 947 // ===== Phase 3.5: Write to catalog provider (no redundancy) ===== 948 try { 949 catalogProvider.addObject(schemaObject); 950 } catch (Throwable e) { 951 // Log error but maintain backward compatibility 952 logger.error("[CatalogProvider] Error adding object: " + schemaObject + ", error=" + e.getMessage(), e); 953 } 954 955 // Maintain fast index 956 try { 957 List<String> parts = SQLUtil.parseNames(schemaObjectName); 958 if (parts.size() >= 3) { 959 String catalog = this.normalizeIdentifier(ESQLDataObjectType.dotCatalog, parts.get(0)); 960 String schema = this.normalizeIdentifier(ESQLDataObjectType.dotSchema, parts.get(1)); 961 String object = this.normalizeIdentifier( 962 (schemaObject.getDataObjectType() == ESQLDataObjectType.dotColumn) ? ESQLDataObjectType.dotTable : schemaObject.getDataObjectType(), 963 SQLUtil.mergeSegments(parts, 2)); 964 String server = (getDefaultServerName() == null) ? DEFAULT_SERVER_NAME : getDefaultServerName(); 965 NameKey key = new NameKey(schemaObject.getDataObjectType(), server, catalog, schema, object); 966 objectIndex.put(key, schemaObject); 967 968 if (schemaObject instanceof TSQLTable) { 969 String objOnly = this.normalizeIdentifier(ESQLDataObjectType.dotTable, getObjectName(schemaObjectName)); 970 // COW list + atomic get-or-create: concurrent registration must not 971 // clobber the list or corrupt it under a concurrent reader 972 tablesByName.computeIfAbsent(objOnly, 973 k -> new java.util.concurrent.CopyOnWriteArrayList<TSQLTable>()) 974 .addIfAbsent((TSQLTable) schemaObject); 975 } 976 } 977 } catch (Throwable ignore) { 978 // keep backward compatibility even if fast index building fails 979 } 980 return true; 981 } 982 983 /** 984 * add a table 985 * 986 * @param qualifiedTableName, must be in syntax like: catalog.schema.table 987 * @param sqlTable, table instance 988 */ 989// public void addSQLTable(String qualifiedTableName, TSQLTable sqlTable){ 990// schemaObjectList.put(qualifiedTableName,sqlTable); 991// } 992// 993// public void addRoutine(String qualifiedRoutineName, TSQLRoutine sqlRoutine){ 994// schemaObjectList.put(qualifiedRoutineName,sqlRoutine); 995// } 996 997 public static final String DEFAULT_SERVER_NAME = "DEFAULT_SERVER"; 998 public static final String DEFAULT_DB_NAME = "DEFAULT"; 999 public static final String DEFAULT_SCHEMA_NAME = "DEFAULT"; 1000 1001 private String defaultCatalogName = null; 1002 private String defaultSchemaName = null; 1003 private String defaultServerName = null; 1004 1005 protected TSQLSchemaObject doSearchSchemaObject(String catalog, String schema, String table, ESQLDataObjectType objectType){ 1006 TSQLSchemaObject result = null; 1007 1008 String normalizedCurrentCatalogName = this.normalizeIdentifier(ESQLDataObjectType.dotCatalog, defaultCatalogName); 1009 String normalizedCurrentSchemaName = this.normalizeIdentifier(ESQLDataObjectType.dotSchema, defaultSchemaName); 1010 1011 if ((catalog.length()>0)&&(schema.length()>0)){ // catalog.schema.table 1012 result = doSearchSchemaObject(catalog+"."+schema+"."+table,objectType); 1013 }else if (schema.length()>0){ //.schema.table 1014 if (defaultCatalogName != null){ 1015 result = doSearchSchemaObject(normalizedCurrentCatalogName+"."+schema+"."+table,objectType); 1016 }else{ 1017 for(TSQLCatalog c : catalogList){ 1018 result = doSearchSchemaObject(c.name+"."+schema+"."+table,objectType); 1019 if (result != null) break; 1020 } 1021 } 1022 }else if (catalog.length()>0){ // catalog..table 1023 if (defaultSchemaName != null){ 1024 result = doSearchSchemaObject(catalog+"."+normalizedCurrentSchemaName+"."+table,objectType); 1025 }else{ 1026 for(TSQLCatalog c : catalogList){ 1027 if ( c.compareTo(catalog) != 0) continue; 1028 for(TSQLSchema s: c.getSchemaList()){ 1029 result = doSearchSchemaObject(s.getQualifiedName()+"."+table,objectType); 1030 if (result != null) break; 1031 } 1032 if (result != null) break; 1033 } 1034 } 1035 }else{ // ..table, search under the current default database and schema. 1036 // The current default database and schema can be set by the user. 1037 // if current default database and schema is not set, then search in all databases and schemas 1038 if ((objectType == dotTable) && (defaultCatalogName == null) && (defaultSchemaName == null)){ 1039 String canonicalTable = this.normalizeIdentifier(ESQLDataObjectType.dotTable, table); 1040 List<TSQLTable> candidates = tablesByName.get(canonicalTable); 1041 if (candidates != null && !candidates.isEmpty()){ 1042 return candidates.get(0); 1043 } 1044 } 1045 for(TSQLCatalog c : catalogList){ 1046 if ((defaultCatalogName != null) && ( c.compareTo(defaultCatalogName) != 0)) continue; 1047 for(TSQLSchema s: c.getSchemaList()){ 1048 if ((defaultSchemaName != null)&&(s.compareTo(defaultSchemaName) != 0)) continue; 1049 result = doSearchSchemaObject(s.getQualifiedName()+"."+table,objectType); 1050 if (result != null) break; 1051 } 1052 if (result != null) break; 1053 } 1054 } 1055 1056 return result; 1057 } 1058 1059 protected TSQLSchemaObject searchSchemaObject(TObjectName qualifiedName, ESQLDataObjectType objectType){ 1060 1061 String catalog = this.normalizeIdentifier(ESQLDataObjectType.dotCatalog, qualifiedName.getDatabaseString()); 1062 String schema = this.normalizeIdentifier(ESQLDataObjectType.dotSchema, qualifiedName.getSchemaString()); 1063 String table = this.normalizeIdentifier(ESQLDataObjectType.dotTable, qualifiedName.getTableString()); 1064 1065 schema = (schema == null)?getDefaultSchemaName():schema; 1066 schema = (schema == null)?"":schema; 1067 1068 catalog = (catalog == null)?getDefaultCatalogName():catalog; 1069 catalog = (catalog == null)?"":catalog; 1070 1071 return doSearchSchemaObject(catalog,schema,table,objectType); 1072 } 1073 1074 /** 1075 * Phase 1: 使用 IdentifierService 的新方法处理多段名 1076 */ 1077 public TSQLSchemaObject searchSchemaObject(String qualifiedName, ESQLDataObjectType objectType){ 1078 // Phase 1: 使用 IdentifierService 的新方法 1079 IdentifierService svc = getIdentifierService(); 1080 1081 // 1. 解析多段名 1082 List<String> parts = svc.parseQualifiedName(qualifiedName); 1083 1084 // 2. 展开厂商特定语法(MSSQL "..") 1085 // Pass the default schema name to avoid relying on global state 1086 parts = svc.expandVendorSpecific(parts, dbVendor, getDefaultSchemaName()); 1087 1088 if (parts.size() < 3) { 1089 return null; 1090 } 1091 1092 boolean supportCatalog = TSQLEnv.supportCatalog(dbVendor); 1093 boolean supportSchema = TSQLEnv.supportSchema(dbVendor); 1094 1095 // 3. 使用 normalizeSegment 规范化各段(Phase 1: 单段处理) 1096 String catalog = svc.normalizeSegment(parts.get(0), ESQLDataObjectType.dotCatalog); 1097 String schema = svc.normalizeSegment(parts.get(1), ESQLDataObjectType.dotSchema); 1098 String table = svc.normalizeSegment(SQLUtil.mergeSegments(parts, 2), ESQLDataObjectType.dotTable); 1099 1100 TSQLSchemaObject object = doSearchSchemaObject(catalog,schema,table,objectType); 1101 1102 //如果不是同时支持database和schema,且没有找到object,交换database和schema顺序查找 1103 if (!(supportCatalog && supportSchema) && object == null) { 1104 catalog = svc.normalizeSegment(parts.get(1), ESQLDataObjectType.dotCatalog); 1105 schema = svc.normalizeSegment(parts.get(0), ESQLDataObjectType.dotSchema); 1106 object = doSearchSchemaObject(catalog, schema, table, objectType); 1107 } 1108 1109 return object; 1110 1111// String normalizedCurrentCatalogName = TSQLObject.normalizeIdentifier(this,ESQLDataObjectType.dotCatalog, defaultCatalogName); 1112// String normalizedCurrentSchemaName = TSQLObject.normalizeIdentifier(this,ESQLDataObjectType.dotSchema, defaultSchemaName); 1113// 1114// if ((catalog.length()>0)&&(schema.length()>0)){ // catalog.schema.table 1115// result = doSearchSchemaObject(catalog+"."+schema+"."+table,objectType); 1116// }else if (schema.length()>0){ //.schema.table 1117// if (defaultCatalogName != null){ 1118// result = doSearchSchemaObject(normalizedCurrentCatalogName+"."+schema+"."+table,objectType); 1119// }else{ 1120// for(TSQLCatalog c : catalogList){ 1121// result = doSearchSchemaObject(c.name+"."+schema+"."+table,objectType); 1122// if (result != null) break; 1123// } 1124// } 1125// }else if (catalog.length()>0){ // catalog..table 1126// if (defaultSchemaName != null){ 1127// result = doSearchSchemaObject(catalog+"."+normalizedCurrentSchemaName+"."+table,objectType); 1128// }else{ 1129// for(TSQLCatalog c : catalogList){ 1130// if ( c.compareTo(catalog) != 0) continue; 1131// for(TSQLSchema s: c.getSchemaList()){ 1132// result = doSearchSchemaObject(s.getQualifiedName()+"."+table,objectType); 1133// if (result != null) break; 1134// } 1135// if (result != null) break; 1136// } 1137// } 1138// }else{ // ..table 1139// for(TSQLCatalog c : catalogList){ 1140// if ((defaultCatalogName != null) && ( c.compareTo(defaultCatalogName) != 0)) continue; 1141// for(TSQLSchema s: c.getSchemaList()){ 1142// if ((defaultSchemaName != null)&&(s.compareTo(defaultSchemaName) != 0)) continue; 1143// result = doSearchSchemaObject(s.getQualifiedName()+"."+table,objectType); 1144// if (result != null) break; 1145// } 1146// if (result != null) break; 1147// } 1148// } 1149// 1150// return result; 1151 1152 } 1153 /** 1154 * find a table in the SQL environment by using a qualified table name: catalogName.schemaName.tableName 1155 * 1156 * @param qualifiedTablename, can be catalog.schema.table, 1157 * or .schema.table, use currentCatalogName or iterate all catalogs 1158 * or catalog..table, use current schema name or iterate all schema under catalog 1159 * or ..table, use currentCatalogName or iterate all catalogs, use current schema 1160 * or iterate all schema 1161 * @return a table 1162 */ 1163 public TSQLTable searchTable(String qualifiedTablename){ 1164 TSQLSchemaObject result = searchSchemaObject(qualifiedTablename,dotTable); 1165 1166 if (result instanceof TSQLTable){ 1167 return (TSQLTable)result; 1168 } 1169 1170 // The name may be a synonym; dereference it to its base table/view so a 1171 // synonym reference follows through to its base for column lineage. 1172 TSQLSchemaObject synonym = searchSchemaObject(qualifiedTablename, ESQLDataObjectType.dotSynonyms); 1173 if (synonym instanceof TSQLSynonyms){ 1174 return ((TSQLSynonyms) synonym).resolveBaseTable(); 1175 } 1176 return null; 1177 } 1178 1179 public TSQLTable searchTable(TObjectName tableName){ 1180 if ((tableName.getSchemaToken() == null) && (tableName.getDatabaseToken() == null)) return searchTable(".."+tableName.getTableString()); 1181 if (tableName.getDatabaseToken() == null) return searchTable("."+tableName.getSchemaString()+"."+tableName.getTableString()); 1182 if (tableName.getSchemaToken() == null) return searchTable(tableName.getDatabaseString()+".."+tableName.getTableString()); 1183 return searchTable(tableName.toString()); 1184 } 1185 /** 1186 * called by {@link #searchTable(String)} method internally. 1187 * 1188 * @param qualifiedName table name 1189 * @return return a table instance if found, otherwise, return null 1190 */ 1191// TSQLTable doSearchTable(String qualifiedTablename){ 1192// TSQLTable result = null; 1193// TSQLSchemaObject schemaObject = schemaObjectList.get(qualifiedTablename); 1194// 1195// if (schemaObject instanceof TSQLTable){ 1196// result = (TSQLTable)schemaObject; 1197// } 1198// return result; 1199// } 1200 1201 /** 1202 * Phase 1: 使用 IdentifierService 新方法 + 双写日志 1203 */ 1204 private TSQLSchemaObject doSearchSchemaObject( String qualifiedName, ESQLDataObjectType objectType){ 1205 // Phase 1: 使用 IdentifierService 1206 IdentifierService svc = getIdentifierService(); 1207 1208 // ===== Phase 3: Try catalog provider first ===== 1209 try { 1210 List<String> parts = svc.parseQualifiedName(qualifiedName); 1211 parts = svc.expandVendorSpecific(parts, dbVendor); 1212 1213 if (parts.size() >= 3) { 1214 // Phase 1: 使用 normalizeSegment 规范化单段名 1215 String catalog = svc.normalizeSegment(parts.get(0), ESQLDataObjectType.dotCatalog); 1216 String schema = svc.normalizeSegment(parts.get(1), ESQLDataObjectType.dotSchema); 1217 String object = svc.normalizeSegment(SQLUtil.mergeSegments(parts, 2), objectType); 1218 1219 TSQLSchemaObject result = catalogProvider.findObject(catalog, schema, object, objectType); 1220 if (result != null) { 1221 return result; 1222 } 1223 } 1224 } catch (Throwable e) { 1225 // Log error but continue to fallback paths 1226 // System.err.println("[CatalogProvider] Search error: " + e.getMessage()); 1227 } 1228 1229 // Fast-path using canonical index when fully qualified 1230 try { 1231 List<String> parts = svc.parseQualifiedName(qualifiedName); 1232 parts = svc.expandVendorSpecific(parts, dbVendor); 1233 1234 if (parts.size() >= 3) { 1235 String server = (getDefaultServerName() == null) ? DEFAULT_SERVER_NAME : getDefaultServerName(); 1236 1237 // Phase 1: 使用新方法规范化 1238 String catalogNew = svc.normalizeSegment(parts.get(0), ESQLDataObjectType.dotCatalog); 1239 String schemaNew = svc.normalizeSegment(parts.get(1), ESQLDataObjectType.dotSchema); 1240 String objectNew = svc.normalizeSegment(SQLUtil.mergeSegments(parts, 2), 1241 (objectType == ESQLDataObjectType.dotColumn) ? ESQLDataObjectType.dotTable : objectType); 1242 1243 // Phase 1: 双写日志 - 比较新旧键 1244 if (TBaseType.LOG_KEY_COMPARISON) { 1245 String catalogOld = this.normalizeIdentifier(ESQLDataObjectType.dotCatalog, parts.get(0)); 1246 String schemaOld = this.normalizeIdentifier(ESQLDataObjectType.dotSchema, parts.get(1)); 1247 String objectOld = this.normalizeIdentifier( 1248 (objectType == ESQLDataObjectType.dotColumn) ? ESQLDataObjectType.dotTable : objectType, 1249 SQLUtil.mergeSegments(parts, 2)); 1250 1251 if (!catalogNew.equals(catalogOld) || !schemaNew.equals(schemaOld) || !objectNew.equals(objectOld)) { 1252 System.err.println("[Phase1] Key mismatch in " + qualifiedName + " (" + objectType + ")"); 1253 System.err.println(" catalog: OLD=" + catalogOld + " NEW=" + catalogNew); 1254 System.err.println(" schema: OLD=" + schemaOld + " NEW=" + schemaNew); 1255 System.err.println(" object: OLD=" + objectOld + " NEW=" + objectNew); 1256 } 1257 } 1258 1259 NameKey key = new NameKey(objectType, server, catalogNew, schemaNew, objectNew); 1260 TSQLSchemaObject hit = objectIndex.get(key); 1261 if (hit != null) { 1262 return hit; 1263 } 1264 } 1265 } catch (Throwable ignore) { 1266 // fallback to legacy map below 1267 } 1268 String newSchemaName = qualifiedName; 1269 if (objectType == dotTable){ 1270 if (!tableCollationCaseSensitive.get(this.getDBVendor())){ 1271 newSchemaName = newSchemaName.toUpperCase(); 1272 } 1273 }else { 1274 if (!defaultCollationCaseSensitive.get(this.getDBVendor())){ 1275 newSchemaName = newSchemaName.toUpperCase(); 1276 } 1277 } 1278 1279 if (objectType == dotFunction){ 1280 newSchemaName = newSchemaName+"$function"; 1281 }else if (objectType == dotProcedure){ 1282 newSchemaName = newSchemaName+"$procedure"; 1283 } 1284 return schemaObjectList.get(newSchemaName); 1285 } 1286 1287 /** 1288 * the current active catalog/database in the SQL environment. 1289 * If search a table in syntax like this: .schemaName.tableName and this current catalog name is not null, 1290 * then, search the table in this syntax: currentCatalogName.schemaName.tableName. 1291 * If the current catalog name is null, search all catalogs in the catalog list. 1292 * 1293 * @return the name of the current active catalog/database 1294 */ 1295 public String getDefaultCatalogName() { 1296 return defaultCatalogName; 1297 } 1298 1299 /** 1300 * the current schema name in the SQL environment. 1301 * <br> 1302 * If search a table in syntax like this: catalogname..tableName and this current schema name is not null, 1303 * then, search the table in this syntax: catalogName.currentSchemaName.tableName. 1304 * If the current schema name is null, search all schemas under catalogName 1305 * 1306 * @return the default schema name 1307 */ 1308 public String getDefaultSchemaName() { 1309// if ((dbVendor == EDbVendor.dbvmssql)&&(defaultSchemaName == null)){ 1310// return "dbo"; 1311// } 1312 return defaultSchemaName; 1313 } 1314 1315 public void setDefaultCatalogName(String defaultCatalogName) { 1316 this.defaultCatalogName = defaultCatalogName; 1317 } 1318 1319 public void setDefaultSchemaName(String defaultSchemaName) { 1320 this.defaultSchemaName = defaultSchemaName; 1321 } 1322 1323 public String getDefaultServerName() { 1324 return defaultServerName; 1325 } 1326 1327 public void setDefaultServerName(String defaultServerName) { 1328 this.defaultServerName = defaultServerName; 1329 } 1330 1331 1332 1333 /** 1334 * create a new catalog/database in the SQL environment. 1335 * 1336 * @param catalogName catalog name 1337 * @return instance of the created catalog 1338 */ 1339 public TSQLCatalog createSQLCatalog(String catalogName){ 1340 return getSQLCatalog(catalogName,true); 1341 } 1342 1343 /** 1344 * get a catalog from the SQL environment if already exists, otherwise, create a new catalog. 1345 * 1346 * @param catalogName catalog name 1347 * @param createIfNotExist if this value is true, then create a new catalog if it's not exists 1348 * @return a catalog instance 1349 */ 1350 public TSQLCatalog getSQLCatalog(String catalogName, boolean createIfNotExist){ 1351 TSQLCatalog result = searchCatalog(catalogName); 1352 if ((result == null)&&(createIfNotExist)){ 1353 // synchronized so concurrent get-or-create of the same catalog can't 1354 // produce duplicate twins (searchCatalog stays lock-free on the COW list) 1355 synchronized (catalogList) { 1356 result = searchCatalog(catalogName); 1357 if (result == null){ 1358 result = new TSQLCatalog(this,catalogName); 1359 } 1360 } 1361 } 1362 return result; 1363 } 1364 1365 /** 1366 * search catalog in the catalog list, return null if not found. 1367 * 1368 * @param catalogName catalog name 1369 * @return null if not found. 1370 */ 1371 public TSQLCatalog searchCatalog(String catalogName){ 1372 TSQLCatalog result = null; 1373 for(TSQLCatalog c : catalogList){ 1374 if (c.compareTo(catalogName)==0){ 1375 result = c; 1376 break; 1377 } 1378 } 1379 return result; 1380 } 1381 1382 /** 1383 * create a new schema and add to the catalog 1384 * 1385 * @param qualifiedSchemaName must be a qualified name like: catalog.schema. Otherwise, a null exception will be raised. 1386 * @return a new schema instance 1387 */ 1388 public TSQLSchema createSQLSchema(String qualifiedSchemaName){ 1389 return getSQLSchema(qualifiedSchemaName,true); 1390 } 1391 1392 /** 1393 * get a schema from the specified catalog, if not exists, create a new schema in the catalog. 1394 * 1395 * @param qualifiedSchemaName must be a qualified name like: catalog.schema. Otherwise, a null exception will be raised. 1396 * @param createIfNotExist if this value is true, then create a new schema if it's not exists 1397 * @return a schema instance 1398 */ 1399 public TSQLSchema getSQLSchema(String qualifiedSchemaName, boolean createIfNotExist){ 1400 TSQLSchema result = null; 1401 String[] parts = SQLUtil.parseNames(qualifiedSchemaName).toArray(new String[0]); 1402 String catalogName = parts[0]; 1403 String schemaName = parts[1]; 1404 TSQLCatalog catalog = getSQLCatalog(catalogName,createIfNotExist); 1405 return catalog.getSchema(schemaName,createIfNotExist); 1406 } 1407 1408 1409 /** 1410 * 1411 * @param qualifiedTablename 需要完整的tablename,例如 catalog.schema.table, 如果仅传入 table name, 1412 * 需要利用 default catalog, default schema 拼接完整的名称: catalog.schema.table 1413 * @param columnNameOnly 1414 * @return 1415 */ 1416 public ArrayList<String> getColumnsInTable(String qualifiedTablename,boolean columnNameOnly){ 1417 1418 //TSQLTable tsqlTable = searchTable(getAFullQualifiedSchemaObjectName(qualifiedTablename)); 1419 TSQLTable tsqlTable = searchTable(qualifiedTablename); 1420 if (tsqlTable == null) return null; 1421 //return tsqlTable.searchColumn(columnName); 1422 return tsqlTable.getColumns(columnNameOnly); 1423 } 1424 1425 public boolean columnInTable(String qualifiedTablename, String columnName){ 1426 TSQLTable tsqlTable = searchTable(qualifiedTablename); 1427 if (tsqlTable == null) return false; 1428 return tsqlTable.searchColumn(columnName); 1429 } 1430 1431 public TSQLColumn getColumnInTable(String qualifiedTablename, String columnName){ 1432 StringBuilder builder = new StringBuilder(); 1433 String db = (getDatabaseName(qualifiedTablename) == null)?getDefaultCatalogName():getDatabaseName(qualifiedTablename); 1434 if((db == null) || (db.length() == 0)) { 1435 builder.append(DEFAULT_DB_NAME).append("."); 1436 } 1437 1438 String schema = (getSchemaName(qualifiedTablename) == null)?getDefaultSchemaName():getSchemaName(qualifiedTablename); 1439 if ((schema == null)||(schema.length() == 0)) { 1440 builder.append(DEFAULT_SCHEMA_NAME).append("."); 1441 } 1442 1443 builder.append(qualifiedTablename); 1444 1445 TSQLTable tsqlTable = searchTable(builder.toString()); 1446 if (tsqlTable == null) return null; 1447 return tsqlTable.getColumn(columnName); 1448 } 1449 1450// String[] getCatalogSchemaNameWithDefault(String qualifiedObjectName){ 1451// String[] catalogSchemaName = new String[2]; 1452// 1453// if (qualifiedObjectName.startsWith("`") && qualifiedObjectName.endsWith("`")){ 1454// qualifiedObjectName = qualifiedObjectName.substring(1,qualifiedObjectName.length()-1); 1455// } 1456// 1457// String db = (getDatabaseName(qualifiedObjectName) == null)?getDefaultCatalogName():getDatabaseName(qualifiedObjectName); 1458// if((db == null) || (db.length() == 0)) db = DEFAULT_DB_NAME; 1459// String schema = (getSchemaName(qualifiedObjectName) == null)?getDefaultSchemaName():getSchemaName(qualifiedObjectName); 1460// if ((schema == null)||(schema.length() == 0)) schema = DEFAULT_SCHEMA_NAME; 1461// 1462// catalogSchemaName[0] = db; 1463// catalogSchemaName[1] = schema; 1464// 1465// return catalogSchemaName; 1466// } 1467 public TSQLSchemaObject doAddSchemaObject(String qualifiedObjectName,ESQLDataObjectType objectType){ 1468 1469 if (qualifiedObjectName.startsWith("`") && qualifiedObjectName.endsWith("`")){ 1470 qualifiedObjectName = SQLUtil.trimColumnStringQuote(qualifiedObjectName); 1471 } 1472 1473 String db = (getDatabaseName(qualifiedObjectName) == null)?getDefaultCatalogName():getDatabaseName(qualifiedObjectName); 1474 if((db == null) || (db.length() == 0)) db = DEFAULT_DB_NAME; 1475 String schema = (getSchemaName(qualifiedObjectName) == null)?getDefaultSchemaName():getSchemaName(qualifiedObjectName); 1476 if ((schema == null)||(schema.length() == 0)) schema = DEFAULT_SCHEMA_NAME; 1477 1478 // getCatalogSchemaNameWithDefault 这个函数有问题,为导致测试用例失败 1479 // String[] catalogSchemaName = getCatalogSchemaNameWithDefault(qualifiedObjectName); 1480// String db = catalogSchemaName[0]; 1481// String schema = catalogSchemaName[1]; 1482 1483 TSQLCatalog sqlCatalog = createSQLCatalog(db); 1484 TSQLSchema sqlSchema = sqlCatalog.createSchema(schema); 1485 TSQLSchemaObject created = sqlSchema.createSchemaObject(getObjectName(qualifiedObjectName),objectType); 1486 // Ensure fast index contains the new object as well (already handled via putSchemaObject called by schema) 1487 return created; 1488 } 1489 1490 public TSQLSchemaObject doAddSchemaObject(TObjectName qualifiedObjectName,ESQLDataObjectType objectType){ 1491 String db = (getDatabaseName(qualifiedObjectName) == null)?getDefaultCatalogName():getDatabaseName(qualifiedObjectName); 1492 if((db == null) || (db.length() == 0)) db = DEFAULT_DB_NAME; 1493 String schema = (getSchemaName(qualifiedObjectName) == null)?getDefaultSchemaName():getSchemaName(qualifiedObjectName); 1494 if ((schema == null)||(schema.length() == 0)) schema = DEFAULT_SCHEMA_NAME; 1495 TSQLCatalog sqlCatalog = createSQLCatalog(db); 1496 TSQLSchema sqlSchema = sqlCatalog.createSchema(schema); 1497 return sqlSchema.createSchemaObject(getObjectName(qualifiedObjectName),objectType); 1498 } 1499 1500 /** 1501 * Add a function to SQLEnv, if a function with the same already exists, just return the existing one. 1502 * 1503 * @param qualifiedFunctionName 1504 * @return function 1505 */ 1506 public TSQLFunction addFunction(String qualifiedFunctionName, boolean fromDDL){ 1507 if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return null; 1508 return (TSQLFunction)doAddSchemaObject(qualifiedFunctionName,ESQLDataObjectType.dotFunction); 1509 } 1510 1511 public TSQLFunction addFunction(TObjectName qualifiedFunctionName, boolean fromDDL){ 1512 if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return null; 1513 return (TSQLFunction)doAddSchemaObject(qualifiedFunctionName,ESQLDataObjectType.dotFunction); 1514 } 1515 1516 public TSQLProcedure addOraclePackage(String qualifiedProcedureName, boolean fromDDL){ 1517 if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return null; 1518 return (TSQLProcedure)doAddSchemaObject(qualifiedProcedureName, ESQLDataObjectType.dotOraclePackage); 1519 } 1520 1521 public TSQLProcedure addProcedure(String qualifiedProcedureName, boolean fromDDL){ 1522 if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return null; 1523 return (TSQLProcedure)doAddSchemaObject(qualifiedProcedureName,ESQLDataObjectType.dotProcedure); 1524 } 1525 1526 public TSQLRoutine addSQLRoutine(String qualifiedProcedureName, boolean fromDDL, ESQLDataObjectType type){ 1527 if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return null; 1528 return (TSQLRoutine)doAddSchemaObject(qualifiedProcedureName,type); 1529 } 1530 1531 public TSQLTrigger addTrigger(String qualifiedTriggerName, boolean fromDDL){ 1532 if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return null; 1533 return (TSQLTrigger)doAddSchemaObject(qualifiedTriggerName,ESQLDataObjectType.dotTrigger); 1534 } 1535 1536 /** 1537 * Add a new table to the SQLEnv. If the table with the same name already exists in the SQLEnv, 1538 * the existing table will be returned. 1539 * <br>dbName.schemaName.tablename. If dbName is not specified, {@link #getDefaultCatalogName()} will be used to put this table in. 1540 * If schemaName is not specified, {@link #getDefaultSchemaName()} will be used to put this table in. 1541 * <br>If the specified database and schema are not already exists, a new database/schema will be generated automatically. 1542 * 1543 * @param qualifiedTableName qualified table name 1544 * @param fromDDL is this table generated from a create table statement 1545 * 1546 * @return SQL Table 1547 */ 1548 public TSQLTable addTable(String qualifiedTableName, boolean fromDDL){ 1549 if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return null; 1550 return (TSQLTable)doAddSchemaObject(qualifiedTableName,ESQLDataObjectType.dotTable); 1551 } 1552 1553 public TSQLTable addView(String qualifiedViewName, boolean fromDDL){ 1554 if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return null; 1555 TSQLTable tsqlTable = (TSQLTable)doAddSchemaObject(qualifiedViewName,ESQLDataObjectType.dotTable); 1556 // doAddSchemaObject returns null on an object type/name conflict 1557 if (tsqlTable != null) { 1558 tsqlTable.setView(true); 1559 } 1560 return tsqlTable; 1561 } 1562 1563 public static String getObjectName(TObjectName schemaObjectName){ 1564 if (schemaObjectName.getObjectString().length() == 0) return null; 1565 else 1566 return schemaObjectName.getObjectString(); 1567 } 1568 1569 public static String getObjectName(String schemaObjectName){ 1570 String[] names = SQLUtil.parseNames(schemaObjectName).toArray(new String[0]); 1571 return names[names.length-1]; 1572 } 1573 1574 public static String getDatabaseName(TObjectName schemaObjectName){ 1575 if (schemaObjectName.getDatabaseString().length() == 0) 1576 return null; 1577 else 1578 return schemaObjectName.getDatabaseString(); 1579 } 1580 1581 public static String getDatabaseName(String schemaObjectName){ 1582 String[] names = SQLUtil.parseNames(schemaObjectName).toArray(new String[0]); 1583 if (names.length == 3){ 1584 return names[0]; 1585 }else if (names.length == 4){ 1586 return names[1]; 1587 }else{ 1588 return null; 1589 } 1590 } 1591 1592 public static String getSchemaName(TObjectName schemaObjectName){ 1593 if (schemaObjectName.getSchemaString().length() == 0) return null; 1594 else 1595 return schemaObjectName.getSchemaString(); 1596 } 1597 1598 public static String getSchemaName(String schemaObjectName){ 1599 String[] names = SQLUtil.parseNames(schemaObjectName).toArray(new String[0]); 1600 if (names.length == 2){ 1601 return names[0]; 1602 }else if (names.length == 3){ 1603 return names[1]; 1604 }else if (names.length == 4){ 1605 return names[2]; 1606 }else{ 1607 return null; 1608 } 1609 } 1610 1611 public TSQLFunction searchFunction(TObjectName qualifiedTablename) { 1612 TSQLSchemaObject result = searchSchemaObject(qualifiedTablename,dotFunction); 1613 1614 if (result instanceof TSQLFunction) { 1615 return (TSQLFunction) result; 1616 } else return null; 1617 } 1618 1619 public TSQLFunction searchFunction(String qualifiedTablename) { 1620 TSQLSchemaObject result = searchSchemaObject( getAFullQualifiedSchemaObjectName(qualifiedTablename),dotFunction); 1621 1622 if (result instanceof TSQLFunction) { 1623 return (TSQLFunction) result; 1624 } else return null; 1625 } 1626 1627 /** 1628 * If the input name is fully qualified with db and schema, just return it without any modification. 1629 * If the schema name is missed, use {@link #getDefaultSchemaName()} instead 1630 * If the database name is missed, use {@link #getDefaultCatalogName()} instead 1631 * 1632 * some sample result: 1633 * <br> 1634 * db.schema.table 1635 * .schema.table, {@link #getDefaultCatalogName()} is null 1636 * ..table, both {@link #getDefaultCatalogName()} and {@link #getDefaultSchemaName()} are null. 1637 * 1638 * @param schemaObjectName 1639 * @return 1640 */ 1641 public String getAFullQualifiedSchemaObjectName(String schemaObjectName){ 1642 if (SQLUtil.parseNames(schemaObjectName).size() == 3) return schemaObjectName; 1643 String schema = (getDefaultSchemaName() == null)?"":getDefaultSchemaName(); 1644 String db = (getDefaultCatalogName() == null)?"":getDefaultCatalogName(); 1645 1646 String[] names = SQLUtil.parseNames(schemaObjectName).toArray(new String[0]); 1647 if (names.length == 1){ // no prefixed schema, db 1648 return db+"."+schema+"."+schemaObjectName; 1649 }else if (names.length == 2){ // prefix with schema, no database 1650 return db+"."+names[0]+"."+names[1]; 1651 }else { 1652 return schemaObjectName; 1653 } 1654 } 1655 1656 1657 1658 public int getNumberOfTables(){ 1659 int numOfTables = 0; 1660 for(int i=0;i<getCatalogList().size();i++){ 1661 TSQLCatalog tsqlCatalog = getCatalogList().get(i); 1662 for(TSQLSchema tsqlSchema: tsqlCatalog.getSchemaList()){ 1663 for(TSQLSchemaObject schemaObject: tsqlSchema.getSchemaObjectList()){ 1664 switch (schemaObject.getDataObjectType()){ 1665 case dotTable: 1666 numOfTables++; 1667 break; 1668 case dotProcedure: 1669 break; 1670 case dotFunction: 1671 break; 1672 case dotOraclePackage: 1673 break; 1674 } 1675 } //schema object list 1676 } // schema list 1677 } //catalog list 1678 return numOfTables; 1679 } 1680 1681 public String toString(){ 1682 String lcResult = ""; 1683 StringBuilder tables = new StringBuilder(); 1684 StringBuilder views = new StringBuilder(); 1685 StringBuilder functions = new StringBuilder(); 1686 StringBuilder oraclePackages = new StringBuilder(); 1687 StringBuilder procedures = new StringBuilder(); 1688 for(int i=0;i<getCatalogList().size();i++){ 1689 TSQLCatalog tsqlCatalog = getCatalogList().get(i); 1690 lcResult = lcResult+"\ndatabase:"+tsqlCatalog.getName(); 1691 for(TSQLSchema tsqlSchema: tsqlCatalog.getSchemaList()){ 1692 lcResult = lcResult+"\n\t"+"schema:"+tsqlSchema.getName(); 1693 for(TSQLSchemaObject schemaObject: tsqlSchema.getSchemaObjectList()){ 1694 switch (schemaObject.getDataObjectType()){ 1695 case dotTable: 1696 TSQLTable tsqlTable = (TSQLTable)schemaObject; 1697 if (tsqlTable.isView()){ 1698 views.append("\n\t\t\t"+schemaObject.getName()); 1699 for(TSQLColumn column: tsqlTable.getColumnList()){ 1700 views.append("\n\t\t\t\t"+column.getName()); 1701 1702 } 1703 }else{ 1704 tables.append("\n\t\t\t"+schemaObject.getName()); 1705 for(TSQLColumn column: tsqlTable.getColumnList()){ 1706 tables.append("\n\t\t\t\t"+column.getName()); 1707 if (column.getColumnDataType() != null){ 1708 tables.append(",\t"+column.getColumnDataType().toString()); 1709 TTypeName subType; 1710 switch (column.getColumnDataType().getDataType()){ 1711 case array_t: 1712 subType = column.getColumnDataType().getTypeOfList(); 1713 1714 if (subType.getDataType() == EDataType.struct_t){ 1715 1716 for(int k=0;k<subType.getColumnDefList().size();k++){ 1717 //System.out.println(subType.getColumnDefList().getColumn(k).getColumnName()); 1718 //System.out.println(subType.getColumnDefList().getColumn(k).getDatatype()); 1719 tables.append("\n\t\t\t\t\t\t"+ subType.getColumnDefList().getColumn(k).getColumnName().toString() 1720 +",\t"+ subType.getColumnDefList().getColumn(k).getDatatype().toString()); 1721 } 1722 } 1723 break; 1724 case struct_t: 1725 subType = column.getColumnDataType(); 1726 for(int k=0;k<subType.getColumnDefList().size();k++){ 1727 tables.append("\n\t\t\t\t\t\t"+ subType.getColumnDefList().getColumn(k).getColumnName().toString() 1728 +",\t"+ subType.getColumnDefList().getColumn(k).getDatatype().toString()); 1729 } 1730 break; 1731 } 1732 } 1733 } 1734 } 1735 1736 break; 1737 case dotProcedure: 1738 procedures.append("\n\t\t\t"+schemaObject.getName()); 1739 break; 1740 case dotFunction: 1741 functions.append("\n\t\t\t"+schemaObject.toString()); 1742 //TSQLFunction f = (TSQLFunction) schemaObject; 1743 1744 break; 1745 case dotOraclePackage: 1746 oraclePackages.append("\n\t\t\t"+schemaObject.getName()); 1747 break; 1748 } 1749 } //schema object list 1750 lcResult = lcResult+"\n\t\t"+"tables:"+tables.toString(); 1751 lcResult = lcResult+"\n\t\t"+"views:"+views.toString(); 1752 if(oraclePackages.length()>0) { 1753 lcResult = lcResult + "\n\t\t" + "oracle package:" + oraclePackages.toString(); 1754 } 1755 lcResult = lcResult+"\n\t\t"+"procedure:"+procedures.toString(); 1756 lcResult = lcResult+"\n\t\t"+"function:"+functions.toString(); 1757 tables.setLength(0); 1758 views.setLength(0); 1759 procedures.setLength(0); 1760 functions.setLength(0); 1761 } // schema list 1762 } //catalog list 1763 return lcResult; 1764 } 1765 1766 public String getProcedureParameterValue(String procedureName, int paramPos){ 1767 return ""; 1768 } 1769 public void getVariableValue(String variableName, String variableValue){} 1770 1771 private static boolean usedBySqlflow = false; 1772 1773 public static boolean isUsedBySqlflow() { 1774 return usedBySqlflow; 1775 } 1776}