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}