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