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        // same monitor as getSQLCatalog so the check-then-add is atomic under
895        // concurrent registration; readers iterate the COW list lock-free
896        synchronized (catalogList) {
897            boolean isFound = false;
898            for(TSQLCatalog c : catalogList){
899                if ( sqlCatalog.getName().compareTo(c.getName()) == 0){
900                    isFound = true;
901                    break;
902                }
903            }
904            if (!isFound){
905                catalogList.add(sqlCatalog);
906            }
907            return !isFound;
908        }
909    }
910
911    private Map<String, TSQLSchemaObject> schemaObjectList = new ConcurrentHashMap<>();
912
913    // Fast canonical index for fully-qualified lookups (additive, keeps existing map intact)
914    private final Map<NameKey, TSQLSchemaObject> objectIndex = new ConcurrentHashMap<NameKey, TSQLSchemaObject>();
915    // Reverse index for tables by object name only (speeds up ..table lookups).
916    // COW lists: registration appends while other threads' name resolution reads
917    private final Map<String, java.util.concurrent.CopyOnWriteArrayList<TSQLTable>> tablesByName =
918            new ConcurrentHashMap<String, java.util.concurrent.CopyOnWriteArrayList<TSQLTable>>();
919
920    /**
921     * put a schema object into the hashmap which can be used to find a schema object in a more efficient way.
922     *
923     * @param schemaObjectName  name of schema object
924     * @param schemaObject instance of a schema object
925     * @return always return true.
926     */
927    protected boolean putSchemaObject(String schemaObjectName, TSQLSchemaObject schemaObject){
928        String newSchemaName = schemaObjectName;
929        if (schemaObject.getDataObjectType() == dotTable){
930            if (!tableCollationCaseSensitive.get(this.getDBVendor())){
931                newSchemaName = newSchemaName.toUpperCase();
932            }
933        }else {
934            if (!defaultCollationCaseSensitive.get(this.getDBVendor())){
935                newSchemaName = newSchemaName.toUpperCase();
936            }
937        }
938
939        if (schemaObject.getDataObjectType() == dotFunction){
940            newSchemaName = newSchemaName+"$function";
941        }else if (schemaObject.getDataObjectType() == dotProcedure){
942            newSchemaName = newSchemaName+"$procedure";
943        }
944
945        schemaObjectList.put(newSchemaName,schemaObject);
946
947        // ===== Phase 3.5: Write to catalog provider (no redundancy) =====
948        try {
949            catalogProvider.addObject(schemaObject);
950        } catch (Throwable e) {
951            // Log error but maintain backward compatibility
952            logger.error("[CatalogProvider] Error adding object: " + schemaObject + ", error=" + e.getMessage(), e);
953        }
954
955        // Maintain fast index
956        try {
957            List<String> parts = SQLUtil.parseNames(schemaObjectName);
958            if (parts.size() >= 3) {
959                String catalog = this.normalizeIdentifier(ESQLDataObjectType.dotCatalog, parts.get(0));
960                String schema = this.normalizeIdentifier(ESQLDataObjectType.dotSchema, parts.get(1));
961                String object = this.normalizeIdentifier(
962                        (schemaObject.getDataObjectType() == ESQLDataObjectType.dotColumn) ? ESQLDataObjectType.dotTable : schemaObject.getDataObjectType(),
963                        SQLUtil.mergeSegments(parts, 2));
964                String server = (getDefaultServerName() == null) ? DEFAULT_SERVER_NAME : getDefaultServerName();
965                NameKey key = new NameKey(schemaObject.getDataObjectType(), server, catalog, schema, object);
966                objectIndex.put(key, schemaObject);
967
968                if (schemaObject instanceof TSQLTable) {
969                    String objOnly = this.normalizeIdentifier(ESQLDataObjectType.dotTable, getObjectName(schemaObjectName));
970                    // COW list + atomic get-or-create: concurrent registration must not
971                    // clobber the list or corrupt it under a concurrent reader
972                    tablesByName.computeIfAbsent(objOnly,
973                            k -> new java.util.concurrent.CopyOnWriteArrayList<TSQLTable>())
974                            .addIfAbsent((TSQLTable) schemaObject);
975                }
976            }
977        } catch (Throwable ignore) {
978            // keep backward compatibility even if fast index building fails
979        }
980        return true;
981    }
982
983    /**
984     * add a table
985     *
986     * @param qualifiedTableName, must be in syntax like: catalog.schema.table
987     * @param sqlTable, table instance
988     */
989//    public void addSQLTable(String qualifiedTableName, TSQLTable sqlTable){
990//        schemaObjectList.put(qualifiedTableName,sqlTable);
991//    }
992//
993//    public void addRoutine(String qualifiedRoutineName, TSQLRoutine sqlRoutine){
994//        schemaObjectList.put(qualifiedRoutineName,sqlRoutine);
995//    }
996
997    public static final String DEFAULT_SERVER_NAME = "DEFAULT_SERVER";
998    public static final String DEFAULT_DB_NAME = "DEFAULT";
999    public static final String DEFAULT_SCHEMA_NAME = "DEFAULT";
1000
1001    private String defaultCatalogName = null;
1002    private String defaultSchemaName = null;
1003    private String defaultServerName = null;
1004
1005    protected TSQLSchemaObject doSearchSchemaObject(String catalog, String schema, String table, ESQLDataObjectType objectType){
1006        TSQLSchemaObject result = null;
1007
1008        String normalizedCurrentCatalogName = this.normalizeIdentifier(ESQLDataObjectType.dotCatalog, defaultCatalogName);
1009        String normalizedCurrentSchemaName = this.normalizeIdentifier(ESQLDataObjectType.dotSchema, defaultSchemaName);
1010
1011        if ((catalog.length()>0)&&(schema.length()>0)){ // catalog.schema.table
1012            result = doSearchSchemaObject(catalog+"."+schema+"."+table,objectType);
1013        }else if (schema.length()>0){ //.schema.table
1014            if (defaultCatalogName != null){
1015                result = doSearchSchemaObject(normalizedCurrentCatalogName+"."+schema+"."+table,objectType);
1016            }else{
1017                for(TSQLCatalog c : catalogList){
1018                    result = doSearchSchemaObject(c.name+"."+schema+"."+table,objectType);
1019                    if (result != null) break;
1020                }
1021            }
1022        }else if (catalog.length()>0){ // catalog..table
1023            if (defaultSchemaName != null){
1024                result = doSearchSchemaObject(catalog+"."+normalizedCurrentSchemaName+"."+table,objectType);
1025            }else{
1026                for(TSQLCatalog c : catalogList){
1027                    if ( c.compareTo(catalog) != 0) continue;
1028                    for(TSQLSchema s: c.getSchemaList()){
1029                        result = doSearchSchemaObject(s.getQualifiedName()+"."+table,objectType);
1030                        if (result != null) break;
1031                    }
1032                    if (result != null) break;
1033                }
1034            }
1035        }else{ // ..table, search under the current default database and schema.
1036            // The current default database and schema can be set by the user.
1037            // if current default database and schema is not set, then search in all databases and schemas
1038            if ((objectType == dotTable) && (defaultCatalogName == null) && (defaultSchemaName == null)){
1039                String canonicalTable = this.normalizeIdentifier(ESQLDataObjectType.dotTable, table);
1040                List<TSQLTable> candidates = tablesByName.get(canonicalTable);
1041                if (candidates != null && !candidates.isEmpty()){
1042                    return candidates.get(0);
1043                }
1044            }
1045            for(TSQLCatalog c : catalogList){
1046                if ((defaultCatalogName != null) && ( c.compareTo(defaultCatalogName) != 0)) continue;
1047                for(TSQLSchema s: c.getSchemaList()){
1048                    if ((defaultSchemaName != null)&&(s.compareTo(defaultSchemaName) != 0)) continue;
1049                    result = doSearchSchemaObject(s.getQualifiedName()+"."+table,objectType);
1050                    if (result != null) break;
1051                }
1052                if (result != null) break;
1053            }
1054        }
1055
1056        return result;
1057    }
1058
1059    protected TSQLSchemaObject searchSchemaObject(TObjectName qualifiedName, ESQLDataObjectType objectType){
1060
1061        String catalog = this.normalizeIdentifier(ESQLDataObjectType.dotCatalog, qualifiedName.getDatabaseString());
1062        String schema = this.normalizeIdentifier(ESQLDataObjectType.dotSchema, qualifiedName.getSchemaString());
1063        String table = this.normalizeIdentifier(ESQLDataObjectType.dotTable, qualifiedName.getTableString());
1064
1065        schema = (schema == null)?getDefaultSchemaName():schema;
1066        schema = (schema == null)?"":schema;
1067
1068        catalog = (catalog == null)?getDefaultCatalogName():catalog;
1069        catalog = (catalog == null)?"":catalog;
1070
1071        return doSearchSchemaObject(catalog,schema,table,objectType);
1072    }
1073
1074    /**
1075     * Phase 1: 使用 IdentifierService 的新方法处理多段名
1076     */
1077    public TSQLSchemaObject searchSchemaObject(String qualifiedName, ESQLDataObjectType objectType){
1078        // Phase 1: 使用 IdentifierService 的新方法
1079        IdentifierService svc = getIdentifierService();
1080
1081        // 1. 解析多段名
1082        List<String> parts = svc.parseQualifiedName(qualifiedName);
1083
1084        // 2. 展开厂商特定语法(MSSQL "..")
1085        // Pass the default schema name to avoid relying on global state
1086        parts = svc.expandVendorSpecific(parts, dbVendor, getDefaultSchemaName());
1087
1088                if (parts.size() < 3) {
1089                        return null;
1090                }
1091
1092                boolean supportCatalog = TSQLEnv.supportCatalog(dbVendor);
1093                boolean supportSchema = TSQLEnv.supportSchema(dbVendor);
1094
1095        // 3. 使用 normalizeSegment 规范化各段(Phase 1: 单段处理)
1096        String catalog = svc.normalizeSegment(parts.get(0), ESQLDataObjectType.dotCatalog);
1097        String schema = svc.normalizeSegment(parts.get(1), ESQLDataObjectType.dotSchema);
1098        String table = svc.normalizeSegment(SQLUtil.mergeSegments(parts, 2), ESQLDataObjectType.dotTable);
1099
1100        TSQLSchemaObject object = doSearchSchemaObject(catalog,schema,table,objectType);
1101
1102        //如果不是同时支持database和schema,且没有找到object,交换database和schema顺序查找
1103                if (!(supportCatalog && supportSchema) && object == null) {
1104                        catalog = svc.normalizeSegment(parts.get(1), ESQLDataObjectType.dotCatalog);
1105                        schema = svc.normalizeSegment(parts.get(0), ESQLDataObjectType.dotSchema);
1106                        object = doSearchSchemaObject(catalog, schema, table, objectType);
1107                }
1108
1109                return object;
1110
1111//        String normalizedCurrentCatalogName = TSQLObject.normalizeIdentifier(this,ESQLDataObjectType.dotCatalog, defaultCatalogName);
1112//        String normalizedCurrentSchemaName = TSQLObject.normalizeIdentifier(this,ESQLDataObjectType.dotSchema, defaultSchemaName);
1113//
1114//        if ((catalog.length()>0)&&(schema.length()>0)){ // catalog.schema.table
1115//            result = doSearchSchemaObject(catalog+"."+schema+"."+table,objectType);
1116//        }else if (schema.length()>0){ //.schema.table
1117//            if (defaultCatalogName != null){
1118//                result = doSearchSchemaObject(normalizedCurrentCatalogName+"."+schema+"."+table,objectType);
1119//            }else{
1120//                for(TSQLCatalog c : catalogList){
1121//                    result = doSearchSchemaObject(c.name+"."+schema+"."+table,objectType);
1122//                    if (result != null) break;
1123//                }
1124//            }
1125//        }else if (catalog.length()>0){ // catalog..table
1126//            if (defaultSchemaName != null){
1127//                result = doSearchSchemaObject(catalog+"."+normalizedCurrentSchemaName+"."+table,objectType);
1128//            }else{
1129//                for(TSQLCatalog c : catalogList){
1130//                    if ( c.compareTo(catalog) != 0) continue;
1131//                    for(TSQLSchema s: c.getSchemaList()){
1132//                        result = doSearchSchemaObject(s.getQualifiedName()+"."+table,objectType);
1133//                        if (result != null) break;
1134//                    }
1135//                    if (result != null) break;
1136//                }
1137//            }
1138//        }else{ // ..table
1139//            for(TSQLCatalog c : catalogList){
1140//                if ((defaultCatalogName != null) && ( c.compareTo(defaultCatalogName) != 0)) continue;
1141//                for(TSQLSchema s: c.getSchemaList()){
1142//                    if ((defaultSchemaName != null)&&(s.compareTo(defaultSchemaName) != 0)) continue;
1143//                    result = doSearchSchemaObject(s.getQualifiedName()+"."+table,objectType);
1144//                    if (result != null) break;
1145//                }
1146//                if (result != null) break;
1147//            }
1148//        }
1149//
1150//        return result;
1151
1152    }
1153    /**
1154     * find a table in the SQL environment by using a qualified table name: catalogName.schemaName.tableName
1155     *
1156     * @param qualifiedTablename, can be catalog.schema.table,
1157     *                            or .schema.table, use currentCatalogName or iterate all catalogs
1158     *                            or catalog..table, use current schema name or iterate all schema under catalog
1159     *                            or ..table, use currentCatalogName or iterate all catalogs, use current schema
1160     *                                          or iterate all schema
1161     * @return a table
1162     */
1163    public TSQLTable searchTable(String qualifiedTablename){
1164        TSQLSchemaObject result = searchSchemaObject(qualifiedTablename,dotTable);
1165
1166        if (result instanceof TSQLTable){
1167            return (TSQLTable)result;
1168        }
1169
1170        // The name may be a synonym; dereference it to its base table/view so a
1171        // synonym reference follows through to its base for column lineage.
1172        TSQLSchemaObject synonym = searchSchemaObject(qualifiedTablename, ESQLDataObjectType.dotSynonyms);
1173        if (synonym instanceof TSQLSynonyms){
1174            return ((TSQLSynonyms) synonym).resolveBaseTable();
1175        }
1176        return null;
1177    }
1178
1179    public TSQLTable searchTable(TObjectName tableName){
1180        if ((tableName.getSchemaToken() == null) && (tableName.getDatabaseToken() == null)) return searchTable(".."+tableName.getTableString());
1181        if (tableName.getDatabaseToken() == null) return searchTable("."+tableName.getSchemaString()+"."+tableName.getTableString());
1182        if (tableName.getSchemaToken() == null) return searchTable(tableName.getDatabaseString()+".."+tableName.getTableString());
1183        return searchTable(tableName.toString());
1184    }
1185    /**
1186     * called by {@link #searchTable(String)} method internally.
1187     *
1188     * @param qualifiedName table name
1189     * @return return a table instance if found, otherwise, return null
1190     */
1191//    TSQLTable doSearchTable(String qualifiedTablename){
1192//        TSQLTable result = null;
1193//        TSQLSchemaObject schemaObject = schemaObjectList.get(qualifiedTablename);
1194//
1195//        if (schemaObject instanceof TSQLTable){
1196//            result = (TSQLTable)schemaObject;
1197//        }
1198//        return result;
1199//    }
1200
1201    /**
1202     * Phase 1: 使用 IdentifierService 新方法 + 双写日志
1203     */
1204    private TSQLSchemaObject doSearchSchemaObject( String qualifiedName, ESQLDataObjectType objectType){
1205        // Phase 1: 使用 IdentifierService
1206        IdentifierService svc = getIdentifierService();
1207
1208        // ===== Phase 3: Try catalog provider first =====
1209        try {
1210            List<String> parts = svc.parseQualifiedName(qualifiedName);
1211            parts = svc.expandVendorSpecific(parts, dbVendor);
1212
1213            if (parts.size() >= 3) {
1214                // Phase 1: 使用 normalizeSegment 规范化单段名
1215                String catalog = svc.normalizeSegment(parts.get(0), ESQLDataObjectType.dotCatalog);
1216                String schema = svc.normalizeSegment(parts.get(1), ESQLDataObjectType.dotSchema);
1217                String object = svc.normalizeSegment(SQLUtil.mergeSegments(parts, 2), objectType);
1218
1219                TSQLSchemaObject result = catalogProvider.findObject(catalog, schema, object, objectType);
1220                if (result != null) {
1221                    return result;
1222                }
1223            }
1224        } catch (Throwable e) {
1225            // Log error but continue to fallback paths
1226            // System.err.println("[CatalogProvider] Search error: " + e.getMessage());
1227        }
1228
1229        // Fast-path using canonical index when fully qualified
1230        try {
1231            List<String> parts = svc.parseQualifiedName(qualifiedName);
1232            parts = svc.expandVendorSpecific(parts, dbVendor);
1233
1234            if (parts.size() >= 3) {
1235                String server = (getDefaultServerName() == null) ? DEFAULT_SERVER_NAME : getDefaultServerName();
1236
1237                // Phase 1: 使用新方法规范化
1238                String catalogNew = svc.normalizeSegment(parts.get(0), ESQLDataObjectType.dotCatalog);
1239                String schemaNew = svc.normalizeSegment(parts.get(1), ESQLDataObjectType.dotSchema);
1240                String objectNew = svc.normalizeSegment(SQLUtil.mergeSegments(parts, 2),
1241                        (objectType == ESQLDataObjectType.dotColumn) ? ESQLDataObjectType.dotTable : objectType);
1242
1243                // Phase 1: 双写日志 - 比较新旧键
1244                if (TBaseType.LOG_KEY_COMPARISON) {
1245                    String catalogOld = this.normalizeIdentifier(ESQLDataObjectType.dotCatalog, parts.get(0));
1246                    String schemaOld = this.normalizeIdentifier(ESQLDataObjectType.dotSchema, parts.get(1));
1247                    String objectOld = this.normalizeIdentifier(
1248                            (objectType == ESQLDataObjectType.dotColumn) ? ESQLDataObjectType.dotTable : objectType,
1249                            SQLUtil.mergeSegments(parts, 2));
1250
1251                    if (!catalogNew.equals(catalogOld) || !schemaNew.equals(schemaOld) || !objectNew.equals(objectOld)) {
1252                        System.err.println("[Phase1] Key mismatch in " + qualifiedName + " (" + objectType + ")");
1253                        System.err.println("  catalog: OLD=" + catalogOld + " NEW=" + catalogNew);
1254                        System.err.println("  schema:  OLD=" + schemaOld + " NEW=" + schemaNew);
1255                        System.err.println("  object:  OLD=" + objectOld + " NEW=" + objectNew);
1256                    }
1257                }
1258
1259                NameKey key = new NameKey(objectType, server, catalogNew, schemaNew, objectNew);
1260                TSQLSchemaObject hit = objectIndex.get(key);
1261                if (hit != null) {
1262                    return hit;
1263                }
1264            }
1265        } catch (Throwable ignore) {
1266            // fallback to legacy map below
1267        }
1268        String newSchemaName = qualifiedName;
1269        if (objectType == dotTable){
1270            if (!tableCollationCaseSensitive.get(this.getDBVendor())){
1271                newSchemaName = newSchemaName.toUpperCase();
1272            }
1273        }else {
1274            if (!defaultCollationCaseSensitive.get(this.getDBVendor())){
1275                newSchemaName = newSchemaName.toUpperCase();
1276            }
1277        }
1278
1279        if (objectType == dotFunction){
1280            newSchemaName = newSchemaName+"$function";
1281        }else if (objectType == dotProcedure){
1282            newSchemaName = newSchemaName+"$procedure";
1283        }
1284        return  schemaObjectList.get(newSchemaName);
1285    }
1286
1287    /**
1288     * the current active catalog/database in the SQL environment.
1289     * If search a table in syntax like this:  .schemaName.tableName and this current catalog name is not null,
1290     * then, search the table in this syntax: currentCatalogName.schemaName.tableName.
1291     * If the current catalog name is null, search all catalogs in the catalog list.
1292     *
1293     * @return the name of the current active catalog/database
1294     */
1295    public String getDefaultCatalogName() {
1296        return defaultCatalogName;
1297    }
1298
1299    /**
1300     * the current schema name in the SQL environment.
1301     * <br>
1302     * If search a table in syntax like this:  catalogname..tableName and this current schema name is not null,
1303     * then, search the table in this syntax: catalogName.currentSchemaName.tableName.
1304     * If the current schema name is null, search all schemas under catalogName
1305     *
1306     * @return the default schema name
1307     */
1308    public String getDefaultSchemaName() {
1309//        if ((dbVendor == EDbVendor.dbvmssql)&&(defaultSchemaName == null)){
1310//            return "dbo";
1311//        }
1312        return defaultSchemaName;
1313    }
1314
1315    public void setDefaultCatalogName(String defaultCatalogName) {
1316        this.defaultCatalogName = defaultCatalogName;
1317    }
1318
1319    public void setDefaultSchemaName(String defaultSchemaName) {
1320        this.defaultSchemaName = defaultSchemaName;
1321    }
1322
1323    public String getDefaultServerName() {
1324        return defaultServerName;
1325    }
1326
1327    public void setDefaultServerName(String defaultServerName) {
1328        this.defaultServerName = defaultServerName;
1329    }
1330
1331
1332
1333    /**
1334     * create a new catalog/database in the SQL environment.
1335     *
1336     * @param catalogName catalog name
1337     * @return instance of the created catalog
1338     */
1339    public TSQLCatalog createSQLCatalog(String catalogName){
1340        return getSQLCatalog(catalogName,true);
1341    }
1342
1343    /**
1344     * get a catalog from the SQL environment if already exists, otherwise, create a new catalog.
1345     *
1346     * @param catalogName catalog name
1347     * @param createIfNotExist if this value is true, then create a new catalog if it's not exists
1348     * @return a catalog instance
1349     */
1350    public TSQLCatalog getSQLCatalog(String catalogName, boolean createIfNotExist){
1351        TSQLCatalog result = searchCatalog(catalogName);
1352        if ((result == null)&&(createIfNotExist)){
1353            // synchronized so concurrent get-or-create of the same catalog can't
1354            // produce duplicate twins (searchCatalog stays lock-free on the COW list)
1355            synchronized (catalogList) {
1356                result = searchCatalog(catalogName);
1357                if (result == null){
1358                    result = new TSQLCatalog(this,catalogName);
1359                }
1360            }
1361        }
1362        return result;
1363    }
1364
1365    /**
1366     * search catalog in the catalog list, return null if not found.
1367     *
1368     * @param catalogName catalog name
1369     * @return null if not found.
1370     */
1371    public TSQLCatalog searchCatalog(String catalogName){
1372        TSQLCatalog result = null;
1373        for(TSQLCatalog c : catalogList){
1374            if (c.compareTo(catalogName)==0){
1375                result = c;
1376                break;
1377            }
1378        }
1379        return result;
1380    }
1381
1382    /**
1383     * create a new schema and add to the catalog
1384     *
1385     * @param qualifiedSchemaName must be a qualified name like: catalog.schema. Otherwise, a null exception will be raised.
1386     * @return a new schema instance
1387     */
1388    public TSQLSchema createSQLSchema(String qualifiedSchemaName){
1389        return  getSQLSchema(qualifiedSchemaName,true);
1390    }
1391
1392    /**
1393     * get a schema from the specified catalog, if not exists, create a new schema in the catalog.
1394     *
1395     * @param qualifiedSchemaName must be a qualified name like: catalog.schema. Otherwise, a null exception will be raised.
1396     * @param createIfNotExist  if this value is true, then create a new schema if it's not exists
1397     * @return a schema instance
1398     */
1399    public TSQLSchema getSQLSchema(String qualifiedSchemaName, boolean createIfNotExist){
1400        TSQLSchema result = null;
1401        String[] parts = SQLUtil.parseNames(qualifiedSchemaName).toArray(new String[0]);
1402        String catalogName = parts[0];
1403        String schemaName = parts[1];
1404        TSQLCatalog catalog = getSQLCatalog(catalogName,createIfNotExist);
1405        return  catalog.getSchema(schemaName,createIfNotExist);
1406    }
1407
1408
1409    /**
1410     *
1411     * @param qualifiedTablename 需要完整的tablename,例如 catalog.schema.table, 如果仅传入 table name,
1412     *                           需要利用 default catalog, default schema 拼接完整的名称: catalog.schema.table
1413     * @param columnNameOnly
1414     * @return
1415     */
1416    public  ArrayList<String> getColumnsInTable(String qualifiedTablename,boolean columnNameOnly){
1417
1418        //TSQLTable tsqlTable = searchTable(getAFullQualifiedSchemaObjectName(qualifiedTablename));
1419        TSQLTable tsqlTable = searchTable(qualifiedTablename);
1420        if (tsqlTable == null) return null;
1421        //return tsqlTable.searchColumn(columnName);
1422        return  tsqlTable.getColumns(columnNameOnly);
1423    }
1424
1425    public boolean columnInTable(String qualifiedTablename, String columnName){
1426        TSQLTable tsqlTable = searchTable(qualifiedTablename);
1427        if (tsqlTable == null) return false;
1428        return tsqlTable.searchColumn(columnName);
1429    }
1430
1431    public TSQLColumn getColumnInTable(String qualifiedTablename, String columnName){
1432        StringBuilder builder = new StringBuilder();
1433        String db = (getDatabaseName(qualifiedTablename) == null)?getDefaultCatalogName():getDatabaseName(qualifiedTablename);
1434        if((db == null) || (db.length() == 0)) {
1435                builder.append(DEFAULT_DB_NAME).append(".");
1436        }
1437        
1438        String schema = (getSchemaName(qualifiedTablename) == null)?getDefaultSchemaName():getSchemaName(qualifiedTablename);
1439        if ((schema == null)||(schema.length() == 0)) {
1440                builder.append(DEFAULT_SCHEMA_NAME).append(".");
1441        }
1442        
1443        builder.append(qualifiedTablename);
1444        
1445        TSQLTable tsqlTable = searchTable(builder.toString());
1446        if (tsqlTable == null) return null;
1447        return tsqlTable.getColumn(columnName);
1448    }
1449
1450//    String[] getCatalogSchemaNameWithDefault(String qualifiedObjectName){
1451//        String[] catalogSchemaName = new String[2];
1452//
1453//        if (qualifiedObjectName.startsWith("`") && qualifiedObjectName.endsWith("`")){
1454//            qualifiedObjectName = qualifiedObjectName.substring(1,qualifiedObjectName.length()-1);
1455//        }
1456//
1457//        String db = (getDatabaseName(qualifiedObjectName) == null)?getDefaultCatalogName():getDatabaseName(qualifiedObjectName);
1458//        if((db == null) || (db.length() == 0)) db = DEFAULT_DB_NAME;
1459//        String schema = (getSchemaName(qualifiedObjectName) == null)?getDefaultSchemaName():getSchemaName(qualifiedObjectName);
1460//        if ((schema == null)||(schema.length() == 0)) schema = DEFAULT_SCHEMA_NAME;
1461//
1462//        catalogSchemaName[0] = db;
1463//        catalogSchemaName[1] = schema;
1464//
1465//        return  catalogSchemaName;
1466//    }
1467    public TSQLSchemaObject doAddSchemaObject(String qualifiedObjectName,ESQLDataObjectType objectType){
1468
1469        if (qualifiedObjectName.startsWith("`") && qualifiedObjectName.endsWith("`")){
1470            qualifiedObjectName = SQLUtil.trimColumnStringQuote(qualifiedObjectName);
1471        }
1472
1473        String db = (getDatabaseName(qualifiedObjectName) == null)?getDefaultCatalogName():getDatabaseName(qualifiedObjectName);
1474        if((db == null) || (db.length() == 0)) db = DEFAULT_DB_NAME;
1475        String schema = (getSchemaName(qualifiedObjectName) == null)?getDefaultSchemaName():getSchemaName(qualifiedObjectName);
1476        if ((schema == null)||(schema.length() == 0)) schema = DEFAULT_SCHEMA_NAME;
1477
1478        // getCatalogSchemaNameWithDefault 这个函数有问题,为导致测试用例失败
1479        //        String[] catalogSchemaName = getCatalogSchemaNameWithDefault(qualifiedObjectName);
1480//        String db = catalogSchemaName[0];
1481//        String schema = catalogSchemaName[1];
1482
1483        TSQLCatalog sqlCatalog = createSQLCatalog(db);
1484        TSQLSchema sqlSchema = sqlCatalog.createSchema(schema);
1485        TSQLSchemaObject created = sqlSchema.createSchemaObject(getObjectName(qualifiedObjectName),objectType);
1486        // Ensure fast index contains the new object as well (already handled via putSchemaObject called by schema)
1487        return created;
1488    }
1489
1490    public TSQLSchemaObject doAddSchemaObject(TObjectName qualifiedObjectName,ESQLDataObjectType objectType){
1491        String db = (getDatabaseName(qualifiedObjectName) == null)?getDefaultCatalogName():getDatabaseName(qualifiedObjectName);
1492        if((db == null) || (db.length() == 0)) db = DEFAULT_DB_NAME;
1493        String schema = (getSchemaName(qualifiedObjectName) == null)?getDefaultSchemaName():getSchemaName(qualifiedObjectName);
1494        if ((schema == null)||(schema.length() == 0)) schema = DEFAULT_SCHEMA_NAME;
1495        TSQLCatalog sqlCatalog = createSQLCatalog(db);
1496        TSQLSchema sqlSchema = sqlCatalog.createSchema(schema);
1497        return sqlSchema.createSchemaObject(getObjectName(qualifiedObjectName),objectType);
1498    }
1499
1500    /**
1501     * Add a function to SQLEnv, if a function with the same already exists, just return the existing one.
1502     *
1503     * @param qualifiedFunctionName
1504     * @return function
1505     */
1506    public TSQLFunction addFunction(String qualifiedFunctionName, boolean fromDDL){
1507        if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return  null;
1508        return (TSQLFunction)doAddSchemaObject(qualifiedFunctionName,ESQLDataObjectType.dotFunction);
1509    }
1510
1511    public TSQLFunction addFunction(TObjectName qualifiedFunctionName, boolean fromDDL){
1512        if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return  null;
1513        return (TSQLFunction)doAddSchemaObject(qualifiedFunctionName,ESQLDataObjectType.dotFunction);
1514    }
1515
1516    public TSQLProcedure addOraclePackage(String qualifiedProcedureName, boolean fromDDL){
1517        if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return  null;
1518        return (TSQLProcedure)doAddSchemaObject(qualifiedProcedureName, ESQLDataObjectType.dotOraclePackage);
1519    }
1520
1521    public TSQLProcedure addProcedure(String qualifiedProcedureName, boolean fromDDL){
1522        if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return  null;
1523        return (TSQLProcedure)doAddSchemaObject(qualifiedProcedureName,ESQLDataObjectType.dotProcedure);
1524    }
1525
1526    public TSQLRoutine addSQLRoutine(String qualifiedProcedureName, boolean fromDDL, ESQLDataObjectType type){
1527        if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return  null;
1528        return (TSQLRoutine)doAddSchemaObject(qualifiedProcedureName,type);
1529    }
1530
1531    public TSQLTrigger addTrigger(String qualifiedTriggerName, boolean fromDDL){
1532        if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return  null;
1533        return (TSQLTrigger)doAddSchemaObject(qualifiedTriggerName,ESQLDataObjectType.dotTrigger);
1534    }
1535
1536    /**
1537     * Add a new table to the SQLEnv. If the table with the same name already exists in the SQLEnv,
1538     * the existing table will be returned.
1539     * <br>dbName.schemaName.tablename. If dbName is not specified, {@link #getDefaultCatalogName()} will be used to put this table in.
1540     * If schemaName is not specified, {@link #getDefaultSchemaName()} will be used to put this table in.
1541     * <br>If the specified database and schema are not already exists, a new database/schema will be generated automatically.
1542     *
1543     * @param qualifiedTableName qualified table name
1544     * @param fromDDL is this table generated from a create table statement
1545     *
1546     * @return SQL Table
1547     */
1548    public TSQLTable addTable(String qualifiedTableName, boolean fromDDL){
1549        if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return  null;
1550        return (TSQLTable)doAddSchemaObject(qualifiedTableName,ESQLDataObjectType.dotTable);
1551    }
1552
1553    public TSQLTable addView(String qualifiedViewName, boolean fromDDL){
1554        if ((fromDDL)&&(!isEnableGetMetadataFromDDL())) return  null;
1555        TSQLTable tsqlTable = (TSQLTable)doAddSchemaObject(qualifiedViewName,ESQLDataObjectType.dotTable);
1556        // doAddSchemaObject returns null on an object type/name conflict
1557        if (tsqlTable != null) {
1558            tsqlTable.setView(true);
1559        }
1560        return tsqlTable;
1561    }
1562
1563    public static String getObjectName(TObjectName schemaObjectName){
1564        if (schemaObjectName.getObjectString().length() == 0) return null;
1565        else
1566          return schemaObjectName.getObjectString();
1567    }
1568
1569    public static String getObjectName(String schemaObjectName){
1570        String[] names = SQLUtil.parseNames(schemaObjectName).toArray(new String[0]);
1571        return names[names.length-1];
1572    }
1573
1574    public static String getDatabaseName(TObjectName schemaObjectName){
1575        if (schemaObjectName.getDatabaseString().length() == 0)
1576            return null;
1577        else
1578           return schemaObjectName.getDatabaseString();
1579    }
1580
1581    public static String getDatabaseName(String schemaObjectName){
1582        String[] names = SQLUtil.parseNames(schemaObjectName).toArray(new String[0]);
1583        if (names.length == 3){
1584            return names[0];
1585        }else if (names.length == 4){
1586            return names[1];
1587        }else{
1588            return null;
1589        }
1590    }
1591
1592    public static String getSchemaName(TObjectName schemaObjectName){
1593        if (schemaObjectName.getSchemaString().length() == 0) return null;
1594        else
1595          return schemaObjectName.getSchemaString();
1596    }
1597
1598    public static String getSchemaName(String schemaObjectName){
1599        String[] names = SQLUtil.parseNames(schemaObjectName).toArray(new String[0]);
1600        if (names.length == 2){
1601            return names[0];
1602        }else if (names.length == 3){
1603            return names[1];
1604        }else if (names.length == 4){
1605            return names[2];
1606        }else{
1607            return null;
1608        }
1609    }
1610
1611    public TSQLFunction searchFunction(TObjectName qualifiedTablename) {
1612        TSQLSchemaObject result = searchSchemaObject(qualifiedTablename,dotFunction);
1613
1614        if (result instanceof TSQLFunction) {
1615            return (TSQLFunction) result;
1616        } else return null;
1617    }
1618
1619    public TSQLFunction searchFunction(String qualifiedTablename) {
1620        TSQLSchemaObject result = searchSchemaObject( getAFullQualifiedSchemaObjectName(qualifiedTablename),dotFunction);
1621
1622        if (result instanceof TSQLFunction) {
1623            return (TSQLFunction) result;
1624        } else return null;
1625    }
1626
1627    /**
1628     * If the input name is fully qualified with db and schema, just return it without any modification.
1629     * If the schema name is missed, use {@link #getDefaultSchemaName()} instead
1630     * If the database name is missed, use {@link #getDefaultCatalogName()} instead
1631     *
1632     * some sample result:
1633     * <br>
1634     * db.schema.table
1635     * .schema.table,  {@link #getDefaultCatalogName()} is null
1636     * ..table, both {@link #getDefaultCatalogName()} and {@link #getDefaultSchemaName()} are null.
1637     *
1638     * @param schemaObjectName
1639     * @return
1640     */
1641    public String getAFullQualifiedSchemaObjectName(String schemaObjectName){
1642        if (SQLUtil.parseNames(schemaObjectName).size() == 3) return schemaObjectName;
1643        String schema = (getDefaultSchemaName() == null)?"":getDefaultSchemaName();
1644        String db = (getDefaultCatalogName() == null)?"":getDefaultCatalogName();
1645
1646        String[] names = SQLUtil.parseNames(schemaObjectName).toArray(new String[0]);
1647        if (names.length == 1){ // no prefixed schema, db
1648            return db+"."+schema+"."+schemaObjectName;
1649        }else if (names.length == 2){ // prefix with schema, no database
1650            return db+"."+names[0]+"."+names[1];
1651        }else {
1652            return schemaObjectName;
1653        }
1654    }
1655
1656
1657
1658    public int getNumberOfTables(){
1659        int numOfTables = 0;
1660        for(int i=0;i<getCatalogList().size();i++){
1661            TSQLCatalog tsqlCatalog = getCatalogList().get(i);
1662            for(TSQLSchema tsqlSchema: tsqlCatalog.getSchemaList()){
1663                for(TSQLSchemaObject schemaObject: tsqlSchema.getSchemaObjectList()){
1664                    switch (schemaObject.getDataObjectType()){
1665                        case dotTable:
1666                            numOfTables++;
1667                            break;
1668                        case dotProcedure:
1669                            break;
1670                        case dotFunction:
1671                            break;
1672                        case dotOraclePackage:
1673                            break;
1674                    }
1675                } //schema object list
1676            } // schema list
1677        } //catalog list
1678        return numOfTables;
1679    }
1680
1681    public String toString(){
1682        String lcResult = "";
1683        StringBuilder tables = new StringBuilder();
1684        StringBuilder views = new StringBuilder();
1685        StringBuilder functions = new StringBuilder();
1686        StringBuilder oraclePackages = new StringBuilder();
1687        StringBuilder procedures = new StringBuilder();
1688        for(int i=0;i<getCatalogList().size();i++){
1689            TSQLCatalog tsqlCatalog = getCatalogList().get(i);
1690            lcResult = lcResult+"\ndatabase:"+tsqlCatalog.getName();
1691            for(TSQLSchema tsqlSchema: tsqlCatalog.getSchemaList()){
1692                lcResult = lcResult+"\n\t"+"schema:"+tsqlSchema.getName();
1693                for(TSQLSchemaObject schemaObject: tsqlSchema.getSchemaObjectList()){
1694                    switch (schemaObject.getDataObjectType()){
1695                        case dotTable:
1696                            TSQLTable tsqlTable = (TSQLTable)schemaObject;
1697                            if (tsqlTable.isView()){
1698                                views.append("\n\t\t\t"+schemaObject.getName());
1699                                for(TSQLColumn column: tsqlTable.getColumnList()){
1700                                    views.append("\n\t\t\t\t"+column.getName());
1701
1702                                }
1703                            }else{
1704                                tables.append("\n\t\t\t"+schemaObject.getName());
1705                                for(TSQLColumn column: tsqlTable.getColumnList()){
1706                                    tables.append("\n\t\t\t\t"+column.getName());
1707                                    if (column.getColumnDataType() != null){
1708                                        tables.append(",\t"+column.getColumnDataType().toString());
1709                                        TTypeName subType;
1710                                        switch (column.getColumnDataType().getDataType()){
1711                                            case array_t:
1712                                                subType = column.getColumnDataType().getTypeOfList();
1713
1714                                                if (subType.getDataType() == EDataType.struct_t){
1715
1716                                                    for(int k=0;k<subType.getColumnDefList().size();k++){
1717                                                        //System.out.println(subType.getColumnDefList().getColumn(k).getColumnName());
1718                                                        //System.out.println(subType.getColumnDefList().getColumn(k).getDatatype());
1719                                                        tables.append("\n\t\t\t\t\t\t"+ subType.getColumnDefList().getColumn(k).getColumnName().toString()
1720                                                                +",\t"+ subType.getColumnDefList().getColumn(k).getDatatype().toString());
1721                                                    }
1722                                                }
1723                                                break;
1724                                            case struct_t:
1725                                                subType = column.getColumnDataType();
1726                                                for(int k=0;k<subType.getColumnDefList().size();k++){
1727                                                    tables.append("\n\t\t\t\t\t\t"+ subType.getColumnDefList().getColumn(k).getColumnName().toString()
1728                                                            +",\t"+ subType.getColumnDefList().getColumn(k).getDatatype().toString());
1729                                                }
1730                                                break;
1731                                        }
1732                                    }
1733                                }
1734                            }
1735
1736                            break;
1737                        case dotProcedure:
1738                            procedures.append("\n\t\t\t"+schemaObject.getName());
1739                            break;
1740                        case dotFunction:
1741                            functions.append("\n\t\t\t"+schemaObject.toString());
1742                            //TSQLFunction f = (TSQLFunction) schemaObject;
1743
1744                            break;
1745                        case dotOraclePackage:
1746                            oraclePackages.append("\n\t\t\t"+schemaObject.getName());
1747                            break;
1748                    }
1749                } //schema object list
1750                lcResult = lcResult+"\n\t\t"+"tables:"+tables.toString();
1751                lcResult = lcResult+"\n\t\t"+"views:"+views.toString();
1752                if(oraclePackages.length()>0) {
1753                    lcResult = lcResult + "\n\t\t" + "oracle package:" + oraclePackages.toString();
1754                }
1755                lcResult = lcResult+"\n\t\t"+"procedure:"+procedures.toString();
1756                lcResult = lcResult+"\n\t\t"+"function:"+functions.toString();
1757                tables.setLength(0);
1758                views.setLength(0);
1759                procedures.setLength(0);
1760                functions.setLength(0);
1761            } // schema list
1762        } //catalog list
1763        return lcResult;
1764    }
1765
1766    public String getProcedureParameterValue(String procedureName, int paramPos){
1767        return "";
1768    }
1769    public void getVariableValue(String variableName, String variableValue){}
1770
1771    private static boolean usedBySqlflow = false;
1772
1773    public static boolean isUsedBySqlflow() {
1774        return usedBySqlflow;
1775    }
1776}