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 // volatile: concurrent lazy init may build duplicate (equivalent) instances, 017 // but must never publish a partially constructed one to another thread. 018 private volatile IdentifierService identifierService; 019 020 // ===== Phase 6: Catalog seal mechanism for thread-safety hardening ===== 021 /** 022 * Seal state flag (volatile for thread visibility). 023 */ 024 private volatile boolean sealed = false; 025 026 /** 027 * Seal this catalog to prevent further modifications (Phase 6). 028 * 029 * <p>See {@link TSQLEnv#seal()} for detailed documentation. 030 */ 031 public void seal() { 032 this.sealed = true; 033 } 034 035 /** 036 * Check if this catalog is sealed (Phase 6). 037 * 038 * @return true if sealed, false otherwise 039 */ 040 public boolean isSealed() { 041 return sealed; 042 } 043 044 /** 045 * Check if catalog is not sealed, throw exception if sealed and enforcement is enabled. 046 * 047 * @throws IllegalStateException if sealed and TBaseType.ENFORCE_CATALOG_SEAL is true 048 */ 049 private void checkNotSealed() { 050 if (sealed && TBaseType.ENFORCE_CATALOG_SEAL) { 051 throw new IllegalStateException("Cannot modify sealed catalog. Call seal() marks catalog as read-only."); 052 } 053 } 054 055 /** 056 * Get or create IdentifierService (lazy initialization) 057 */ 058 private IdentifierService getIdentifierService() { 059 if (identifierService == null) { 060 IdentifierProfile profile = IdentifierProfile.forVendor( 061 this.sqlEnv.getDBVendor(), 062 IdentifierProfile.VendorFlags.defaults() 063 ); 064 identifierService = new IdentifierService(profile, null); 065 } 066 return identifierService; 067 } 068 069 /** 070 * create a catalog and add to the SQL environment. 071 * 072 * @param sqlEnv SQL environment 073 * @param catalogName catalog name 074 */ 075 public TSQLCatalog(TSQLEnv sqlEnv, String catalogName){ 076 super(sqlEnv,catalogName,ESQLDataObjectType.dotCatalog); 077 this.sqlEnv.doAddCatalog(this); 078 } 079 080 /** 081 * schema list in this catalog 082 * 083 * @return schema list 084 */ 085 public List<TSQLSchema> getSchemaList() { 086 List<TSQLSchema> schemaList = new LinkedList<>(); 087 synchronized (schemaMap) { 088 for(String schema: schemaMap.keySet()){ 089 schemaList.add(schemaMap.get(schema)); 090 } 091 } 092 return schemaList; 093 } 094 095 private Map<String, TSQLSchema> schemaMap = Collections.synchronizedMap(new LinkedHashMap<String, TSQLSchema>( )); 096 097 protected TSQLSchema searchSchema(String schemaName){ 098 // Phase 2: Use IdentifierService when hierarchical index is enabled 099 if (TBaseType.USE_HIERARCHICAL_INDEX) { 100 try { 101 String key = getIdentifierService().keyForMap(schemaName, ESQLDataObjectType.dotSchema); 102 TSQLSchema result = schemaMap.get(key); 103 if (result != null) { 104 if (TBaseType.LOG_INDEX_HIT_RATE) { 105 System.out.println("[HierarchicalIndex] Catalog hit: schema - " + schemaName); 106 } 107 return result; 108 } 109 } catch (Throwable ignore) { 110 // Fall through to legacy path 111 } 112 } 113 114 // Legacy path: use SQLUtil normalization 115 String name = SQLUtil.getIdentifierNormalName(this.sqlEnv.getDBVendor(), schemaName, ESQLDataObjectType.dotSchema); 116 return schemaMap.get(name); 117 } 118 119 protected void addSchema(TSQLSchema sqlSchema) { 120 // same monitor as getSchema: check-then-put must be atomic under 121 // concurrent DDL registration (reentrant when called from getSchema 122 // via the TSQLSchema constructor) 123 synchronized (schemaMap) { 124 checkNotSealed(); // Phase 6: checked under the mutation lock (seal() itself takes no monitor, so this narrows, not closes, the seal race) 125 if (searchSchema(sqlSchema.name) == null) { 126 // ===== Legacy path: Update schemaMap with SQLUtil normalization ===== 127 String name = SQLUtil.getIdentifierNormalName(this.sqlEnv.getDBVendor(), sqlSchema.name, ESQLDataObjectType.dotSchema); 128 schemaMap.put(name, sqlSchema); 129 130 // ===== New path: Also write with IdentifierService key ===== 131 if (TBaseType.USE_HIERARCHICAL_INDEX) { 132 try { 133 String key = getIdentifierService().keyForMap(sqlSchema.name, ESQLDataObjectType.dotSchema); 134 // Only write if the key is different from legacy key (avoid double-write) 135 if (!key.equalsIgnoreCase(name)) { 136 schemaMap.put(key, sqlSchema); 137 } 138 } catch (Throwable ignore) { 139 // Silently fail to maintain backward compatibility 140 } 141 } 142 } 143 } 144 } 145 146 /** 147 * get a schema instance from the catalog, create a new schema if this schema is not exist and the createIfNotExist parameter is true. 148 * 149 * @param schemaName schema name 150 * @param createIfNotExist create a new schema if this schema is not exist and the createIfNotExist parameter is true. 151 * @return the schema instance 152 */ 153 public TSQLSchema getSchema(String schemaName, boolean createIfNotExist){ 154 TSQLSchema result = searchSchema(schemaName); 155 if ((result == null)&&(createIfNotExist)){ 156 // synchronized so concurrent get-or-create of the same schema can't 157 // produce duplicate twins; the TSQLSchema constructor re-enters this 158 // monitor via addSchema (reentrant) 159 synchronized (schemaMap) { 160 result = searchSchema(schemaName); 161 if (result == null){ 162 result = new TSQLSchema(this,schemaName); 163 } 164 } 165 } 166 167 return result; 168 } 169 170 /** 171 * create a schema under this catalog 172 * 173 * @param schemaName schema name 174 * @return schema instance 175 */ 176 public TSQLSchema createSchema(String schemaName){ 177 return getSchema(schemaName,true); 178 } 179 180}