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