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