001package gudusoft.gsqlparser.sqlenv; 002 003import gudusoft.gsqlparser.TBaseType; 004import gudusoft.gsqlparser.nodes.TTypeName; 005import gudusoft.gsqlparser.util.SQLUtil; 006 007import java.util.*; 008 009/** 010 * SQL table, includes a list of columns. 011 */ 012public class TSQLTable extends TSQLSchemaObject { 013 014 // IdentifierService for consistent key generation (lazy initialization) 015 private IdentifierService identifierService; 016 017 // ===== Phase 6: Table seal mechanism for thread-safety hardening ===== 018 /** 019 * Seal state flag (volatile for thread visibility). 020 */ 021 private volatile boolean sealed = false; 022 023 /** 024 * Seal this table to prevent further modifications (Phase 6). 025 * 026 * <p>See {@link TSQLEnv#seal()} for detailed documentation. 027 */ 028 public void seal() { 029 this.sealed = true; 030 } 031 032 /** 033 * Check if this table is sealed (Phase 6). 034 * 035 * @return true if sealed, false otherwise 036 */ 037 public boolean isSealed() { 038 return sealed; 039 } 040 041 /** 042 * Check if table is not sealed, throw exception if sealed and enforcement is enabled. 043 * 044 * @throws IllegalStateException if sealed and TBaseType.ENFORCE_CATALOG_SEAL is true 045 */ 046 private void checkNotSealed() { 047 if (sealed && TBaseType.ENFORCE_CATALOG_SEAL) { 048 throw new IllegalStateException("Cannot modify sealed table. Call seal() marks table as read-only."); 049 } 050 } 051 052 /** 053 * Get or create IdentifierService (lazy initialization) 054 */ 055 private IdentifierService getIdentifierService() { 056 if (identifierService == null) { 057 IdentifierProfile profile = IdentifierProfile.forVendor( 058 this.sqlEnv.getDBVendor(), 059 IdentifierProfile.VendorFlags.defaults() 060 ); 061 identifierService = new IdentifierService(profile, null); 062 } 063 return identifierService; 064 } 065 066 private boolean isView = false; 067 068 public void setView(boolean view) { 069 isView = view; 070 } 071 072 /** 073 * Used to check whether this is a view. 074 * 075 * @return true if this is a view. 076 */ 077 public boolean isView() { 078 return isView; 079 } 080 081 private String definition; 082 083 public void setDefinition(String definition) { 084 this.definition = definition; 085 } 086 087 /** 088 * This is the script that used to create this view( {@link #isView()} returns true). 089 * 090 * @return sql script that used to create this view. 091 */ 092 public String getDefinition() { 093 return definition; 094 } 095 096 /** 097 * create a new table belong to a schema 098 * 099 * @param sqlSchema schema 100 * @param tableName table name 101 */ 102 public TSQLTable(TSQLSchema sqlSchema, String tableName){ 103 super(sqlSchema,tableName,ESQLDataObjectType.dotTable); 104 } 105 106 /** 107 * column list 108 * @return a column list 109 */ 110 public List<TSQLColumn> getColumnList() { 111 List<TSQLColumn> columnList = new LinkedList<>(); 112 synchronized (columnMap) { 113 for(String column: columnMap.keySet()){ 114 columnList.add(columnMap.get(column)); 115 } 116 } 117 return columnList; 118 } 119 120 private Map<String, TSQLColumn> columnMap = Collections.synchronizedMap(new LinkedHashMap<String, TSQLColumn>( )); 121 122 /** 123 * add a new column to the table 124 * 125 * @param columnName column name 126 */ 127 public void addColumn(String columnName){ 128 checkNotSealed(); // Phase 6: Prevent modification after seal 129 if (!searchColumn(columnName)){ 130 TSQLColumn newColumn = new TSQLColumn(this, columnName); 131 132 // ===== Legacy path: Update columnMap with SQLUtil normalization ===== 133 String legacyKey = SQLUtil.getIdentifierNormalColumnName(this.sqlEnv.getDBVendor(), columnName); 134 columnMap.put(legacyKey, newColumn); 135 136 // ===== New path: Also write with IdentifierService key ===== 137 if (TBaseType.USE_HIERARCHICAL_INDEX) { 138 try { 139 String key = getIdentifierService().keyForMap(columnName, ESQLDataObjectType.dotColumn); 140 // Only write if the key is different from legacy key (avoid double-write) 141 if (!key.equals(legacyKey)) { 142 columnMap.put(key, newColumn); 143 } 144 } catch (Throwable ignore) { 145 // Silently fail to maintain backward compatibility 146 } 147 } 148 149 } 150 } 151 152 public void addColumn(String columnName, TTypeName columnDataType){ 153 checkNotSealed(); // Phase 6: Prevent modification after seal 154 if (!searchColumn(columnName)){ 155 TSQLColumn newColumn = new TSQLColumn(this, columnName, columnDataType); 156 157 // ===== Legacy path: Update columnMap with SQLUtil normalization ===== 158 String legacyKey = SQLUtil.getIdentifierNormalColumnName(this.sqlEnv.getDBVendor(), columnName); 159 columnMap.put(legacyKey, newColumn); 160 161 // ===== New path: Also write with IdentifierService key ===== 162 if (TBaseType.USE_HIERARCHICAL_INDEX) { 163 try { 164 String key = getIdentifierService().keyForMap(columnName, ESQLDataObjectType.dotColumn); 165 // Only write if the key is different from legacy key (avoid double-write) 166 if (!key.equalsIgnoreCase(legacyKey)) { 167 columnMap.put(key, newColumn); 168 } 169 } catch (Throwable ignore) { 170 // Silently fail to maintain backward compatibility 171 } 172 } 173 } 174 } 175 176 public boolean searchColumn(String columnName){ 177 // Phase 2: Use IdentifierService when hierarchical index is enabled 178 if (TBaseType.USE_HIERARCHICAL_INDEX) { 179 try { 180 String key = getIdentifierService().keyForMap(columnName, ESQLDataObjectType.dotColumn); 181 if (columnMap.containsKey(key)) { 182 if (TBaseType.LOG_INDEX_HIT_RATE) { 183 System.out.println("[HierarchicalIndex] Table hit: column - " + columnName); 184 } 185 return true; 186 } 187 } catch (Throwable ignore) { 188 // Fall through to legacy path 189 } 190 } 191 192 // Legacy path: use SQLUtil normalization 193 return columnMap.containsKey(SQLUtil.getIdentifierNormalColumnName(this.sqlEnv.getDBVendor(), columnName)); 194 } 195 196 public TSQLColumn getColumn(String columnName){ 197 // Phase 2: Use IdentifierService when hierarchical index is enabled 198 if (TBaseType.USE_HIERARCHICAL_INDEX) { 199 try { 200 String key = getIdentifierService().keyForMap(columnName, ESQLDataObjectType.dotColumn); 201 TSQLColumn result = columnMap.get(key); 202 if (result != null) { 203 if (TBaseType.LOG_INDEX_HIT_RATE) { 204 System.out.println("[HierarchicalIndex] Table hit: column - " + columnName); 205 } 206 return result; 207 } 208 } catch (Throwable ignore) { 209 // Fall through to legacy path 210 } 211 } 212 213 // Legacy path: use SQLUtil normalization 214 return columnMap.get(SQLUtil.getIdentifierNormalColumnName(this.sqlEnv.getDBVendor(), columnName)); 215 } 216 217 218 public ArrayList<String> getColumns(boolean columnNameOnly ){ 219 ArrayList<String> columns = new ArrayList<>(); 220 synchronized (columnMap) { 221 for(String column: columnMap.keySet()){ 222 TSQLColumn s = columnMap.get(column); 223 if (columnNameOnly){ 224 columns.add(s.name); 225 }else{ 226 columns.add(this.name+"."+ s.name); 227 } 228 229 // columns.add(s.name); 230 } 231 } 232 233 return columns; 234 } 235 236}