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