001package gudusoft.gsqlparser.sqlenv;
002
003
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.util.SQLUtil;
006
007import java.util.*;
008
009/**
010 * In the most sql-implementation, Catalog is the anonymous of the Database.
011 * So, use this class represents a database.
012 */
013public class TSQLCatalog extends TSQLObject {
014
015    // IdentifierService for consistent key generation (lazy initialization)
016    private IdentifierService identifierService;
017
018    // ===== Phase 6: Catalog seal mechanism for thread-safety hardening =====
019    /**
020     * Seal state flag (volatile for thread visibility).
021     */
022    private volatile boolean sealed = false;
023
024    /**
025     * Seal this catalog to prevent further modifications (Phase 6).
026     *
027     * <p>See {@link TSQLEnv#seal()} for detailed documentation.
028     */
029    public void seal() {
030        this.sealed = true;
031    }
032
033    /**
034     * Check if this catalog is sealed (Phase 6).
035     *
036     * @return true if sealed, false otherwise
037     */
038    public boolean isSealed() {
039        return sealed;
040    }
041
042    /**
043     * Check if catalog is not sealed, throw exception if sealed and enforcement is enabled.
044     *
045     * @throws IllegalStateException if sealed and TBaseType.ENFORCE_CATALOG_SEAL is true
046     */
047    private void checkNotSealed() {
048        if (sealed && TBaseType.ENFORCE_CATALOG_SEAL) {
049            throw new IllegalStateException("Cannot modify sealed catalog. Call seal() marks catalog as read-only.");
050        }
051    }
052
053    /**
054     * Get or create IdentifierService (lazy initialization)
055     */
056    private IdentifierService getIdentifierService() {
057        if (identifierService == null) {
058            IdentifierProfile profile = IdentifierProfile.forVendor(
059                this.sqlEnv.getDBVendor(),
060                IdentifierProfile.VendorFlags.defaults()
061            );
062            identifierService = new IdentifierService(profile, null);
063        }
064        return identifierService;
065    }
066
067    /**
068     * create a catalog and add to the SQL environment.
069     *
070     * @param sqlEnv SQL environment
071     * @param catalogName catalog name
072     */
073    public TSQLCatalog(TSQLEnv sqlEnv, String catalogName){
074        super(sqlEnv,catalogName,ESQLDataObjectType.dotCatalog);
075        this.sqlEnv.doAddCatalog(this);
076    }
077
078    /**
079     * schema list in this catalog
080     *
081     * @return schema list
082     */
083    public List<TSQLSchema> getSchemaList() {
084        List<TSQLSchema> schemaList = new LinkedList<>();
085        synchronized (schemaMap) {
086            for(String schema: schemaMap.keySet()){
087                schemaList.add(schemaMap.get(schema));
088            }
089        }
090        return schemaList;
091    }
092
093    private Map<String, TSQLSchema> schemaMap = Collections.synchronizedMap(new LinkedHashMap<String, TSQLSchema>( ));
094
095    protected TSQLSchema searchSchema(String schemaName){
096        // Phase 2: Use IdentifierService when hierarchical index is enabled
097        if (TBaseType.USE_HIERARCHICAL_INDEX) {
098            try {
099                String key = getIdentifierService().keyForMap(schemaName, ESQLDataObjectType.dotSchema);
100                TSQLSchema result = schemaMap.get(key);
101                if (result != null) {
102                    if (TBaseType.LOG_INDEX_HIT_RATE) {
103                        System.out.println("[HierarchicalIndex] Catalog hit: schema - " + schemaName);
104                    }
105                    return result;
106                }
107            } catch (Throwable ignore) {
108                // Fall through to legacy path
109            }
110        }
111
112        // Legacy path: use SQLUtil normalization
113        String name = SQLUtil.getIdentifierNormalName(this.sqlEnv.getDBVendor(), schemaName, ESQLDataObjectType.dotSchema);
114        return schemaMap.get(name);
115    }
116
117    protected void addSchema(TSQLSchema sqlSchema) {
118        checkNotSealed();  // Phase 6: Prevent modification after seal
119        if (searchSchema(sqlSchema.name) == null) {
120            // ===== Legacy path: Update schemaMap with SQLUtil normalization =====
121            String name = SQLUtil.getIdentifierNormalName(this.sqlEnv.getDBVendor(), sqlSchema.name, ESQLDataObjectType.dotSchema);
122            schemaMap.put(name, sqlSchema);
123
124            // ===== New path: Also write with IdentifierService key =====
125            if (TBaseType.USE_HIERARCHICAL_INDEX) {
126                try {
127                    String key = getIdentifierService().keyForMap(sqlSchema.name, ESQLDataObjectType.dotSchema);
128                    // Only write if the key is different from legacy key (avoid double-write)
129                    if (!key.equalsIgnoreCase(name)) {
130                        schemaMap.put(key, sqlSchema);
131                    }
132                } catch (Throwable ignore) {
133                    // Silently fail to maintain backward compatibility
134                }
135            }
136        }
137    }
138
139    /**
140     * get a schema instance from the catalog, create a new schema if this schema is not exist and the createIfNotExist parameter is true.
141     *
142     * @param schemaName schema name
143     * @param createIfNotExist create a new schema if this schema is not exist and the createIfNotExist parameter is true.
144     * @return the schema instance
145     */
146    public TSQLSchema getSchema(String schemaName, boolean createIfNotExist){
147        TSQLSchema result = searchSchema(schemaName);
148        if ((result == null)&&(createIfNotExist)){
149            result = new TSQLSchema(this,schemaName);
150        }
151
152        return result;
153    }
154
155    /**
156     * create a schema under this catalog
157     *
158     * @param schemaName schema name
159     * @return schema instance
160     */
161    public TSQLSchema createSchema(String schemaName){
162        return getSchema(schemaName,true);
163    }
164
165}