001package gudusoft.gsqlparser.sqlenv;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.util.SQLUtil;
006
007import java.util.List;
008import java.util.concurrent.ConcurrentHashMap;
009
010/**
011 * Composite Key Optimizer (Phase 4 - ClickHouse/Couchbase 优化)
012 *
013 * <p>用于全大小写敏感数据库(ClickHouse, Couchbase)的单次 Map 查找优化。
014 *
015 * <p><strong>核心思想:</strong>
016 * <ul>
017 * <li>将多级标识符编码为单个复合键(避免 3 次 Map 查找)
018 * <li>格式:{@code len1#seg1|len2#seg2|len3#seg3|objectType}
019 * <li>例如:{@code "3#db1|6#schema|5#table|dotTable"}
020 * <li>性能:分层索引 2-4μs → 复合键 <200ns(~10x 提升)
021 * </ul>
022 *
023 * <p><strong>适用条件(必须全部满足):</strong>
024 * <ol>
025 * <li>数据库厂商:ClickHouse 或 Couchbase(全标识符大小写敏感)
026 * <li>标识符无引号(引号会改变大小写规则)
027 * <li>USE_COMPOSITE_KEY_OPT = true(默认 false,谨慎开启)
028 * </ol>
029 *
030 * <p><strong>性能特性:</strong>
031 * <ul>
032 * <li>查找时间:~200ns(vs 2-4μs 分层索引)
033 * <li>内存开销:每个对象额外 ~32 bytes(复合键字符串)
034 * <li>构建时间:~100ns(长度前缀编码)
035 * </ul>
036 *
037 * <p>使用示例:
038 * <pre>
039 * CompositeKeyOptimizer optimizer = new CompositeKeyOptimizer(EDbVendor.dbvclickhouse);
040 *
041 * // 检查是否可以使用复合键
042 * if (optimizer.canUseCompositeKey("db.schema.table")) {
043 *     // 构建复合键
044 *     String key = optimizer.buildCompositeKey("db.schema.table", ESQLDataObjectType.dotTable);
045 *     // 结果: "2#db|6#schema|5#table|dotTable"
046 *
047 *     // 存储到单一 Map
048 *     compositeKeyMap.put(key, tableObject);
049 * }
050 * </pre>
051 *
052 * @since 3.1.0.9
053 */
054public class CompositeKeyOptimizer {
055
056    // 全局复合键索引(仅用于 ClickHouse/Couchbase)
057    // Key: compositeKey(如 "3#db1|6#schema|5#table|dotTable")
058    // Value: TSQLSchemaObject
059    private final ConcurrentHashMap<String, TSQLSchemaObject> compositeKeyMap = new ConcurrentHashMap<>();
060
061    private final EDbVendor vendor;
062    private final boolean enabled;
063
064    /**
065     * 构造复合键优化器
066     *
067     * @param vendor 数据库厂商
068     */
069    public CompositeKeyOptimizer(EDbVendor vendor) {
070        this.vendor = vendor;
071        this.enabled = TBaseType.USE_COMPOSITE_KEY_OPT && isSupportedVendor(vendor);
072    }
073
074    /**
075     * 检查厂商是否支持复合键优化
076     *
077     * <p>仅支持全标识符大小写敏感的数据库:
078     * <ul>
079     * <li>ClickHouse: 全部 SENSITIVE
080     * <li>Couchbase: 全部 SENSITIVE
081     * </ul>
082     *
083     * @param vendor 数据库厂商
084     * @return true 如果支持
085     */
086    private boolean isSupportedVendor(EDbVendor vendor) {
087        return vendor == EDbVendor.dbvclickhouse || vendor == EDbVendor.dbvcouchbase;
088    }
089
090    /**
091     * 判断是否可以使用复合键快速路径
092     *
093     * <p>条件(必须全部满足):
094     * <ol>
095     * <li>复合键优化已启用(USE_COMPOSITE_KEY_OPT = true)
096     * <li>厂商支持(ClickHouse 或 Couchbase)
097     * <li>限定名没有引号字符(引号会改变大小写规则)
098     * </ol>
099     *
100     * @param qualifiedName 完整限定名(如 "db.schema.table")
101     * @return true 如果可以使用复合键
102     */
103    public boolean canUseCompositeKey(String qualifiedName) {
104        if (!enabled) {
105            return false;
106        }
107
108        if (qualifiedName == null || qualifiedName.isEmpty()) {
109            return false;
110        }
111
112        // 检查是否包含引号字符(", ', `, [, ])
113        return !containsQuoteChar(qualifiedName);
114    }
115
116    /**
117     * 构建复合键(使用长度前缀编码避免冲突)
118     *
119     * <p>格式: {@code len1#segment1|len2#segment2|len3#segment3|objectType}
120     *
121     * <p>例如: {@code "3#db1|6#schema|5#table|dotTable"}
122     *
123     * <p><strong>优势:</strong>避免分隔符冲突(标识符可能包含 '.' 或 '|')
124     *
125     * @param qualifiedName 完整限定名(如 "db.schema.table")
126     * @param objectType 对象类型
127     * @return 复合键
128     */
129    public String buildCompositeKey(String qualifiedName, ESQLDataObjectType objectType) {
130        if (qualifiedName == null || objectType == null) {
131            return null;
132        }
133
134        List<String> segments = SQLUtil.parseNames(qualifiedName);
135        if (segments.isEmpty()) {
136            return null;
137        }
138
139        StringBuilder sb = new StringBuilder(segments.size() * 20);
140        for (int i = 0; i < segments.size(); i++) {
141            String seg = segments.get(i);
142            if (i > 0) {
143                sb.append('|');
144            }
145            sb.append(seg.length()).append('#').append(seg);
146        }
147        sb.append('|').append(objectType.name());
148        return sb.toString();
149    }
150
151    /**
152     * 添加对象到复合键索引
153     *
154     * @param qualifiedName 完整限定名
155     * @param objectType 对象类型
156     * @param object schema 对象
157     */
158    public void put(String qualifiedName, ESQLDataObjectType objectType, TSQLSchemaObject object) {
159        if (!enabled || object == null) {
160            return;
161        }
162
163        String key = buildCompositeKey(qualifiedName, objectType);
164        if (key != null) {
165            compositeKeyMap.put(key, object);
166        }
167    }
168
169    /**
170     * 从复合键索引查找对象
171     *
172     * @param qualifiedName 完整限定名
173     * @param objectType 对象类型
174     * @return 找到的对象,未找到时返回 null
175     */
176    public TSQLSchemaObject get(String qualifiedName, ESQLDataObjectType objectType) {
177        if (!enabled) {
178            return null;
179        }
180
181        String key = buildCompositeKey(qualifiedName, objectType);
182        if (key == null) {
183            return null;
184        }
185
186        TSQLSchemaObject result = compositeKeyMap.get(key);
187        if (result != null && TBaseType.LOG_INDEX_HIT_RATE) {
188            System.out.println("[CompositeKey] Hit: " + objectType + " - " + qualifiedName);
189        }
190        return result;
191    }
192
193    /**
194     * 移除对象
195     *
196     * @param qualifiedName 完整限定名
197     * @param objectType 对象类型
198     * @return 被移除的对象,未找到时返回 null
199     */
200    public TSQLSchemaObject remove(String qualifiedName, ESQLDataObjectType objectType) {
201        if (!enabled) {
202            return null;
203        }
204
205        String key = buildCompositeKey(qualifiedName, objectType);
206        if (key == null) {
207            return null;
208        }
209
210        return compositeKeyMap.remove(key);
211    }
212
213    /**
214     * 获取索引大小
215     *
216     * @return 索引中的对象数量
217     */
218    public int size() {
219        return compositeKeyMap.size();
220    }
221
222    /**
223     * 清空索引
224     */
225    public void clear() {
226        compositeKeyMap.clear();
227    }
228
229    /**
230     * 检查字符串是否包含引号字符
231     *
232     * <p>检查常见引号字符:", ', `, [, ]
233     *
234     * @param str 字符串
235     * @return true 如果包含引号字符
236     */
237    private boolean containsQuoteChar(String str) {
238        for (int i = 0; i < str.length(); i++) {
239            char c = str.charAt(i);
240            if (c == '"' || c == '\'' || c == '`' || c == '[' || c == ']') {
241                return true;
242            }
243        }
244        return false;
245    }
246
247    /**
248     * 获取是否启用
249     *
250     * @return true 如果已启用
251     */
252    public boolean isEnabled() {
253        return enabled;
254    }
255
256    /**
257     * 获取厂商
258     *
259     * @return 数据库厂商
260     */
261    public EDbVendor getVendor() {
262        return vendor;
263    }
264
265    /**
266     * 获取统计信息(用于性能分析)
267     *
268     * @return 统计信息字符串
269     */
270    public String getStats() {
271        return String.format(
272            "CompositeKeyOptimizer[vendor=%s, enabled=%s, objects=%d]",
273            vendor, enabled, compositeKeyMap.size()
274        );
275    }
276}