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}