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