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