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 - Couchbase 优化)
012 *
013 * <p>用于全大小写敏感数据库(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>数据库厂商: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.dbvcouchbase);
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    // 全局复合键索引(仅用于 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>Couchbase: 全部 SENSITIVE
080     * </ul>
081     *
082     * @param vendor 数据库厂商
083     * @return true 如果支持
084     */
085    private boolean isSupportedVendor(EDbVendor vendor) {
086        return vendor == EDbVendor.dbvcouchbase;
087    }
088
089    /**
090     * 判断是否可以使用复合键快速路径
091     *
092     * <p>条件(必须全部满足):
093     * <ol>
094     * <li>复合键优化已启用(USE_COMPOSITE_KEY_OPT = true)
095     * <li>厂商支持(Couchbase)
096     * <li>限定名没有引号字符(引号会改变大小写规则)
097     * </ol>
098     *
099     * @param qualifiedName 完整限定名(如 "db.schema.table")
100     * @return true 如果可以使用复合键
101     */
102    public boolean canUseCompositeKey(String qualifiedName) {
103        if (!enabled) {
104            return false;
105        }
106
107        if (qualifiedName == null || qualifiedName.isEmpty()) {
108            return false;
109        }
110
111        // 检查是否包含引号字符(", ', `, [, ])
112        return !containsQuoteChar(qualifiedName);
113    }
114
115    /**
116     * 构建复合键(使用长度前缀编码避免冲突)
117     *
118     * <p>格式: {@code len1#segment1|len2#segment2|len3#segment3|objectType}
119     *
120     * <p>例如: {@code "3#db1|6#schema|5#table|dotTable"}
121     *
122     * <p><strong>优势:</strong>避免分隔符冲突(标识符可能包含 '.' 或 '|')
123     *
124     * @param qualifiedName 完整限定名(如 "db.schema.table")
125     * @param objectType 对象类型
126     * @return 复合键
127     */
128    public String buildCompositeKey(String qualifiedName, ESQLDataObjectType objectType) {
129        if (qualifiedName == null || objectType == null) {
130            return null;
131        }
132
133        List<String> segments = SQLUtil.parseNames(qualifiedName);
134        if (segments.isEmpty()) {
135            return null;
136        }
137
138        StringBuilder sb = new StringBuilder(segments.size() * 20);
139        for (int i = 0; i < segments.size(); i++) {
140            String seg = segments.get(i);
141            if (i > 0) {
142                sb.append('|');
143            }
144            sb.append(seg.length()).append('#').append(seg);
145        }
146        sb.append('|').append(objectType.name());
147        return sb.toString();
148    }
149
150    /**
151     * 添加对象到复合键索引
152     *
153     * @param qualifiedName 完整限定名
154     * @param objectType 对象类型
155     * @param object schema 对象
156     */
157    public void put(String qualifiedName, ESQLDataObjectType objectType, TSQLSchemaObject object) {
158        if (!enabled || object == null) {
159            return;
160        }
161
162        String key = buildCompositeKey(qualifiedName, objectType);
163        if (key != null) {
164            compositeKeyMap.put(key, object);
165        }
166    }
167
168    /**
169     * 从复合键索引查找对象
170     *
171     * @param qualifiedName 完整限定名
172     * @param objectType 对象类型
173     * @return 找到的对象,未找到时返回 null
174     */
175    public TSQLSchemaObject get(String qualifiedName, ESQLDataObjectType objectType) {
176        if (!enabled) {
177            return null;
178        }
179
180        String key = buildCompositeKey(qualifiedName, objectType);
181        if (key == null) {
182            return null;
183        }
184
185        TSQLSchemaObject result = compositeKeyMap.get(key);
186        if (result != null && TBaseType.LOG_INDEX_HIT_RATE) {
187            System.out.println("[CompositeKey] Hit: " + objectType + " - " + qualifiedName);
188        }
189        return result;
190    }
191
192    /**
193     * 移除对象
194     *
195     * @param qualifiedName 完整限定名
196     * @param objectType 对象类型
197     * @return 被移除的对象,未找到时返回 null
198     */
199    public TSQLSchemaObject remove(String qualifiedName, ESQLDataObjectType objectType) {
200        if (!enabled) {
201            return null;
202        }
203
204        String key = buildCompositeKey(qualifiedName, objectType);
205        if (key == null) {
206            return null;
207        }
208
209        return compositeKeyMap.remove(key);
210    }
211
212    /**
213     * 获取索引大小
214     *
215     * @return 索引中的对象数量
216     */
217    public int size() {
218        return compositeKeyMap.size();
219    }
220
221    /**
222     * 清空索引
223     */
224    public void clear() {
225        compositeKeyMap.clear();
226    }
227
228    /**
229     * 检查字符串是否包含引号字符
230     *
231     * <p>检查常见引号字符:", ', `, [, ]
232     *
233     * @param str 字符串
234     * @return true 如果包含引号字符
235     */
236    private boolean containsQuoteChar(String str) {
237        for (int i = 0; i < str.length(); i++) {
238            char c = str.charAt(i);
239            if (c == '"' || c == '\'' || c == '`' || c == '[' || c == ']') {
240                return true;
241            }
242        }
243        return false;
244    }
245
246    /**
247     * 获取是否启用
248     *
249     * @return true 如果已启用
250     */
251    public boolean isEnabled() {
252        return enabled;
253    }
254
255    /**
256     * 获取厂商
257     *
258     * @return 数据库厂商
259     */
260    public EDbVendor getVendor() {
261        return vendor;
262    }
263
264    /**
265     * 获取统计信息(用于性能分析)
266     *
267     * @return 统计信息字符串
268     */
269    public String getStats() {
270        return String.format(
271            "CompositeKeyOptimizer[vendor=%s, enabled=%s, objects=%d]",
272            vendor, enabled, compositeKeyMap.size()
273        );
274    }
275}