@yydb/light-db 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/LightDB.d.ts +194 -0
- package/dist/api/LightDB.d.ts.map +1 -0
- package/dist/api/LightDB.js +299 -0
- package/dist/api/LightDB.js.map +1 -0
- package/dist/api/Transaction.d.ts +100 -0
- package/dist/api/Transaction.d.ts.map +1 -0
- package/dist/api/Transaction.js +263 -0
- package/dist/api/Transaction.js.map +1 -0
- package/dist/api/index.d.ts +24 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +54 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/types.d.ts +154 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +7 -0
- package/dist/api/types.js.map +1 -0
- package/dist/collection/Collection.d.ts +235 -0
- package/dist/collection/Collection.d.ts.map +1 -0
- package/dist/collection/Collection.js +671 -0
- package/dist/collection/Collection.js.map +1 -0
- package/dist/collection/QueryBuilder.d.ts +225 -0
- package/dist/collection/QueryBuilder.d.ts.map +1 -0
- package/dist/collection/QueryBuilder.js +475 -0
- package/dist/collection/QueryBuilder.js.map +1 -0
- package/dist/collection/QueryParser.d.ts +117 -0
- package/dist/collection/QueryParser.d.ts.map +1 -0
- package/dist/collection/QueryParser.js +438 -0
- package/dist/collection/QueryParser.js.map +1 -0
- package/dist/collection/index.d.ts +42 -0
- package/dist/collection/index.d.ts.map +1 -0
- package/dist/collection/index.js +41 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/collection/types.d.ts +365 -0
- package/dist/collection/types.d.ts.map +1 -0
- package/dist/collection/types.js +7 -0
- package/dist/collection/types.js.map +1 -0
- package/dist/constants/index.d.ts +51 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +68 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +132 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +220 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +729 -0
- package/dist/index.js.map +1 -0
- package/dist/indexes/BPlusTree.d.ts +181 -0
- package/dist/indexes/BPlusTree.d.ts.map +1 -0
- package/dist/indexes/BPlusTree.js +528 -0
- package/dist/indexes/BPlusTree.js.map +1 -0
- package/dist/indexes/IndexManager.d.ts +236 -0
- package/dist/indexes/IndexManager.d.ts.map +1 -0
- package/dist/indexes/IndexManager.js +502 -0
- package/dist/indexes/IndexManager.js.map +1 -0
- package/dist/indexes/PrimaryIndex.d.ts +240 -0
- package/dist/indexes/PrimaryIndex.d.ts.map +1 -0
- package/dist/indexes/PrimaryIndex.js +354 -0
- package/dist/indexes/PrimaryIndex.js.map +1 -0
- package/dist/indexes/SecondaryIndex.d.ts +204 -0
- package/dist/indexes/SecondaryIndex.d.ts.map +1 -0
- package/dist/indexes/SecondaryIndex.js +480 -0
- package/dist/indexes/SecondaryIndex.js.map +1 -0
- package/dist/indexes/errors.d.ts +66 -0
- package/dist/indexes/errors.d.ts.map +1 -0
- package/dist/indexes/errors.js +117 -0
- package/dist/indexes/errors.js.map +1 -0
- package/dist/indexes/index.d.ts +52 -0
- package/dist/indexes/index.d.ts.map +1 -0
- package/dist/indexes/index.js +131 -0
- package/dist/indexes/index.js.map +1 -0
- package/dist/indexes/types.d.ts +246 -0
- package/dist/indexes/types.d.ts.map +1 -0
- package/dist/indexes/types.js +30 -0
- package/dist/indexes/types.js.map +1 -0
- package/dist/performance/BatchWALWriter.d.ts +185 -0
- package/dist/performance/BatchWALWriter.d.ts.map +1 -0
- package/dist/performance/BatchWALWriter.js +490 -0
- package/dist/performance/BatchWALWriter.js.map +1 -0
- package/dist/performance/ColumnarStorage.d.ts +138 -0
- package/dist/performance/ColumnarStorage.d.ts.map +1 -0
- package/dist/performance/ColumnarStorage.js +875 -0
- package/dist/performance/ColumnarStorage.js.map +1 -0
- package/dist/performance/LazyQuery.d.ts +307 -0
- package/dist/performance/LazyQuery.d.ts.map +1 -0
- package/dist/performance/LazyQuery.js +588 -0
- package/dist/performance/LazyQuery.js.map +1 -0
- package/dist/performance/OptimizedBuffer.d.ts +375 -0
- package/dist/performance/OptimizedBuffer.d.ts.map +1 -0
- package/dist/performance/OptimizedBuffer.js +730 -0
- package/dist/performance/OptimizedBuffer.js.map +1 -0
- package/dist/performance/RWLock.d.ts +260 -0
- package/dist/performance/RWLock.d.ts.map +1 -0
- package/dist/performance/RWLock.js +570 -0
- package/dist/performance/RWLock.js.map +1 -0
- package/dist/performance/index.d.ts +41 -0
- package/dist/performance/index.d.ts.map +1 -0
- package/dist/performance/index.js +162 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/performance/types.d.ts +247 -0
- package/dist/performance/types.d.ts.map +1 -0
- package/dist/performance/types.js +26 -0
- package/dist/performance/types.js.map +1 -0
- package/dist/storage/Checkpoint.d.ts +128 -0
- package/dist/storage/Checkpoint.d.ts.map +1 -0
- package/dist/storage/Checkpoint.js +251 -0
- package/dist/storage/Checkpoint.js.map +1 -0
- package/dist/storage/LightFile.d.ts +227 -0
- package/dist/storage/LightFile.d.ts.map +1 -0
- package/dist/storage/LightFile.js +526 -0
- package/dist/storage/LightFile.js.map +1 -0
- package/dist/storage/Recovery.d.ts +172 -0
- package/dist/storage/Recovery.d.ts.map +1 -0
- package/dist/storage/Recovery.js +399 -0
- package/dist/storage/Recovery.js.map +1 -0
- package/dist/storage/SHMFile.d.ts +243 -0
- package/dist/storage/SHMFile.d.ts.map +1 -0
- package/dist/storage/SHMFile.js +436 -0
- package/dist/storage/SHMFile.js.map +1 -0
- package/dist/storage/StorageEngine.d.ts +146 -0
- package/dist/storage/StorageEngine.d.ts.map +1 -0
- package/dist/storage/StorageEngine.js +353 -0
- package/dist/storage/StorageEngine.js.map +1 -0
- package/dist/storage/WALFile.d.ts +248 -0
- package/dist/storage/WALFile.d.ts.map +1 -0
- package/dist/storage/WALFile.js +597 -0
- package/dist/storage/WALFile.js.map +1 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +153 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types/index.d.ts +220 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/binary.d.ts +271 -0
- package/dist/utils/binary.d.ts.map +1 -0
- package/dist/utils/binary.js +454 -0
- package/dist/utils/binary.js.map +1 -0
- package/dist/utils/file.d.ts +166 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +346 -0
- package/dist/utils/file.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +39 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +37 -0
- package/readme.md +124 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LightDB 二级索引实现
|
|
3
|
+
* 使用 Map<索引值, Set<主键>> 结构,支持高效的等值查询
|
|
4
|
+
*/
|
|
5
|
+
import type { IndexKey, IndexValue, IndexStats, EqualityCondition } from "./types";
|
|
6
|
+
import { IndexType } from "./types";
|
|
7
|
+
/**
|
|
8
|
+
* 二级索引配置
|
|
9
|
+
*/
|
|
10
|
+
export interface SecondaryIndexConfig {
|
|
11
|
+
/** 索引名称 */
|
|
12
|
+
name: string;
|
|
13
|
+
/** 字段名 */
|
|
14
|
+
fieldName: string;
|
|
15
|
+
/** 是否唯一索引 */
|
|
16
|
+
unique?: boolean;
|
|
17
|
+
/** 是否稀疏索引(不索引 null/undefined 值) */
|
|
18
|
+
sparse?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 二级索引
|
|
22
|
+
* 使用 Map + Set 结构实现,支持等值查询和范围查询
|
|
23
|
+
*/
|
|
24
|
+
export declare class SecondaryIndex {
|
|
25
|
+
private readonly index;
|
|
26
|
+
private readonly config;
|
|
27
|
+
private keyCount;
|
|
28
|
+
/**
|
|
29
|
+
* 创建二级索引实例
|
|
30
|
+
* @param config 索引配置
|
|
31
|
+
*/
|
|
32
|
+
constructor(config: SecondaryIndexConfig);
|
|
33
|
+
/**
|
|
34
|
+
* 获取索引名称
|
|
35
|
+
*/
|
|
36
|
+
get Name(): string;
|
|
37
|
+
/**
|
|
38
|
+
* 获取字段名
|
|
39
|
+
*/
|
|
40
|
+
get FieldName(): string;
|
|
41
|
+
/**
|
|
42
|
+
* 获取索引类型
|
|
43
|
+
*/
|
|
44
|
+
get Type(): IndexType.Secondary | IndexType.Unique;
|
|
45
|
+
/**
|
|
46
|
+
* 获取是否唯一索引
|
|
47
|
+
*/
|
|
48
|
+
get IsUnique(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 获取是否稀疏索引
|
|
51
|
+
*/
|
|
52
|
+
get IsSparse(): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* 获取索引中的键数量
|
|
55
|
+
*/
|
|
56
|
+
get Size(): number;
|
|
57
|
+
/**
|
|
58
|
+
* 获取索引值数量
|
|
59
|
+
*/
|
|
60
|
+
get ValueCount(): number;
|
|
61
|
+
/**
|
|
62
|
+
* 检查索引是否为空
|
|
63
|
+
*/
|
|
64
|
+
isEmpty(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* 插入索引条目
|
|
67
|
+
* @param value 索引值
|
|
68
|
+
* @param primaryKey 主键
|
|
69
|
+
* @param throwOnDuplicate 如果是唯一索引且值已存在是否抛出异常
|
|
70
|
+
* @returns 是否插入成功
|
|
71
|
+
*/
|
|
72
|
+
insert(value: IndexValue, primaryKey: IndexKey, throwOnDuplicate?: boolean): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* 删除索引条目
|
|
75
|
+
* @param value 索引值
|
|
76
|
+
* @param primaryKey 主键
|
|
77
|
+
* @returns 是否删除成功
|
|
78
|
+
*/
|
|
79
|
+
delete(value: IndexValue, primaryKey: IndexKey): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* 更新索引条目
|
|
82
|
+
* @param oldValue 旧索引值
|
|
83
|
+
* @param newValue 新索引值
|
|
84
|
+
* @param primaryKey 主键
|
|
85
|
+
* @param throwOnDuplicate 如果是唯一索引且新值已存在是否抛出异常
|
|
86
|
+
* @returns 是否更新成功
|
|
87
|
+
*/
|
|
88
|
+
update(oldValue: IndexValue, newValue: IndexValue, primaryKey: IndexKey, throwOnDuplicate?: boolean): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* 等值查询
|
|
91
|
+
* @param value 索引值
|
|
92
|
+
* @returns 匹配的主键集合
|
|
93
|
+
*/
|
|
94
|
+
find(value: IndexValue): Set<IndexKey>;
|
|
95
|
+
/**
|
|
96
|
+
* 检查索引值是否存在
|
|
97
|
+
* @param value 索引值
|
|
98
|
+
*/
|
|
99
|
+
has(value: IndexValue): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* 检查索引值和主键组合是否存在
|
|
102
|
+
* @param value 索引值
|
|
103
|
+
* @param primaryKey 主键
|
|
104
|
+
*/
|
|
105
|
+
hasEntry(value: IndexValue, primaryKey: IndexKey): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* 条件查询
|
|
108
|
+
* @param condition 查询条件
|
|
109
|
+
* @returns 匹配的主键集合
|
|
110
|
+
*/
|
|
111
|
+
query(condition: EqualityCondition<IndexValue>): Set<IndexKey>;
|
|
112
|
+
/**
|
|
113
|
+
* 范围查询
|
|
114
|
+
* @param start 起始值
|
|
115
|
+
* @param end 结束值
|
|
116
|
+
* @param includeStart 是否包含起始值
|
|
117
|
+
* @param includeEnd 是否包含结束值
|
|
118
|
+
* @returns 匹配的主键集合
|
|
119
|
+
*/
|
|
120
|
+
range(start?: IndexValue, end?: IndexValue, includeStart?: boolean, includeEnd?: boolean): Set<IndexKey>;
|
|
121
|
+
/**
|
|
122
|
+
* 获取所有索引值
|
|
123
|
+
*/
|
|
124
|
+
values(): IndexValue[];
|
|
125
|
+
/**
|
|
126
|
+
* 获取所有主键
|
|
127
|
+
*/
|
|
128
|
+
keys(): IndexKey[];
|
|
129
|
+
/**
|
|
130
|
+
* 获取所有条目
|
|
131
|
+
*/
|
|
132
|
+
entries(): Array<{
|
|
133
|
+
value: IndexValue;
|
|
134
|
+
keys: Set<IndexKey>;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* 清空索引
|
|
138
|
+
*/
|
|
139
|
+
clear(): void;
|
|
140
|
+
/**
|
|
141
|
+
* 获取索引统计信息
|
|
142
|
+
*/
|
|
143
|
+
getStats(): IndexStats;
|
|
144
|
+
/**
|
|
145
|
+
* 批量插入
|
|
146
|
+
* @param entries 条目数组
|
|
147
|
+
* @param throwOnDuplicate 如果是唯一索引且值已存在是否抛出异常
|
|
148
|
+
*/
|
|
149
|
+
insertBatch(
|
|
150
|
+
entries: Array<{
|
|
151
|
+
value: IndexValue;
|
|
152
|
+
primaryKey: IndexKey;
|
|
153
|
+
}>,
|
|
154
|
+
throwOnDuplicate?: boolean,
|
|
155
|
+
): {
|
|
156
|
+
success: number;
|
|
157
|
+
failed: number;
|
|
158
|
+
errors: Array<{
|
|
159
|
+
primaryKey: IndexKey;
|
|
160
|
+
error: string;
|
|
161
|
+
}>;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* 批量删除
|
|
165
|
+
* @param entries 条目数组
|
|
166
|
+
*/
|
|
167
|
+
deleteBatch(
|
|
168
|
+
entries: Array<{
|
|
169
|
+
value: IndexValue;
|
|
170
|
+
primaryKey: IndexKey;
|
|
171
|
+
}>,
|
|
172
|
+
): {
|
|
173
|
+
success: number;
|
|
174
|
+
failed: number;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* 将索引值转换为存储键
|
|
178
|
+
*/
|
|
179
|
+
private valueToKey;
|
|
180
|
+
/**
|
|
181
|
+
* 将存储键转换为索引值
|
|
182
|
+
*/
|
|
183
|
+
private keyToValue;
|
|
184
|
+
/**
|
|
185
|
+
* 比较两个索引值
|
|
186
|
+
*/
|
|
187
|
+
private compareValues;
|
|
188
|
+
/**
|
|
189
|
+
* 获取排序后的条目
|
|
190
|
+
*/
|
|
191
|
+
private getSortedEntries;
|
|
192
|
+
/**
|
|
193
|
+
* 估算内存使用
|
|
194
|
+
*/
|
|
195
|
+
private estimateMemoryUsage;
|
|
196
|
+
/**
|
|
197
|
+
* 验证索引一致性
|
|
198
|
+
*/
|
|
199
|
+
validate(): {
|
|
200
|
+
valid: boolean;
|
|
201
|
+
errors: string[];
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=SecondaryIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecondaryIndex.d.ts","sourceRoot":"","sources":["../../src/indexes/SecondaryIndex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LightDB 二级索引实现
|
|
4
|
+
* 使用 Map<索引值, Set<主键>> 结构,支持高效的等值查询
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.SecondaryIndex = void 0;
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
const errors_1 = require("./errors");
|
|
10
|
+
/**
|
|
11
|
+
* 二级索引
|
|
12
|
+
* 使用 Map + Set 结构实现,支持等值查询和范围查询
|
|
13
|
+
*/
|
|
14
|
+
class SecondaryIndex {
|
|
15
|
+
/**
|
|
16
|
+
* 创建二级索引实例
|
|
17
|
+
* @param config 索引配置
|
|
18
|
+
*/
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.keyCount = 0;
|
|
21
|
+
this.config = {
|
|
22
|
+
name: config.name,
|
|
23
|
+
fieldName: config.fieldName,
|
|
24
|
+
unique: config.unique ?? false,
|
|
25
|
+
sparse: config.sparse ?? false,
|
|
26
|
+
};
|
|
27
|
+
this.index = new Map();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 获取索引名称
|
|
31
|
+
*/
|
|
32
|
+
get Name() {
|
|
33
|
+
return this.config.name;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 获取字段名
|
|
37
|
+
*/
|
|
38
|
+
get FieldName() {
|
|
39
|
+
return this.config.fieldName;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 获取索引类型
|
|
43
|
+
*/
|
|
44
|
+
get Type() {
|
|
45
|
+
return this.config.unique ? types_1.IndexType.Unique : types_1.IndexType.Secondary;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 获取是否唯一索引
|
|
49
|
+
*/
|
|
50
|
+
get IsUnique() {
|
|
51
|
+
return this.config.unique;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 获取是否稀疏索引
|
|
55
|
+
*/
|
|
56
|
+
get IsSparse() {
|
|
57
|
+
return this.config.sparse;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 获取索引中的键数量
|
|
61
|
+
*/
|
|
62
|
+
get Size() {
|
|
63
|
+
return this.keyCount;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 获取索引值数量
|
|
67
|
+
*/
|
|
68
|
+
get ValueCount() {
|
|
69
|
+
return this.index.size;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 检查索引是否为空
|
|
73
|
+
*/
|
|
74
|
+
isEmpty() {
|
|
75
|
+
return this.keyCount === 0;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 插入索引条目
|
|
79
|
+
* @param value 索引值
|
|
80
|
+
* @param primaryKey 主键
|
|
81
|
+
* @param throwOnDuplicate 如果是唯一索引且值已存在是否抛出异常
|
|
82
|
+
* @returns 是否插入成功
|
|
83
|
+
*/
|
|
84
|
+
insert(value, primaryKey, throwOnDuplicate = false) {
|
|
85
|
+
if (value === null || value === undefined) {
|
|
86
|
+
if (this.config.sparse) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const valueKey = this.valueToKey(value);
|
|
91
|
+
if (this.config.unique) {
|
|
92
|
+
const existing = this.index.get(valueKey);
|
|
93
|
+
if (existing && existing.size > 0) {
|
|
94
|
+
if (throwOnDuplicate) {
|
|
95
|
+
throw new errors_1.DuplicateKeyError(this.config.name, value);
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
let keySet = this.index.get(valueKey);
|
|
101
|
+
if (!keySet) {
|
|
102
|
+
keySet = new Set();
|
|
103
|
+
this.index.set(valueKey, keySet);
|
|
104
|
+
}
|
|
105
|
+
if (keySet.has(primaryKey)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
keySet.add(primaryKey);
|
|
109
|
+
this.keyCount++;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 删除索引条目
|
|
114
|
+
* @param value 索引值
|
|
115
|
+
* @param primaryKey 主键
|
|
116
|
+
* @returns 是否删除成功
|
|
117
|
+
*/
|
|
118
|
+
delete(value, primaryKey) {
|
|
119
|
+
if (value === null || value === undefined) {
|
|
120
|
+
if (this.config.sparse) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const valueKey = this.valueToKey(value);
|
|
125
|
+
const keySet = this.index.get(valueKey);
|
|
126
|
+
if (!keySet) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const deleted = keySet.delete(primaryKey);
|
|
130
|
+
if (deleted) {
|
|
131
|
+
this.keyCount--;
|
|
132
|
+
}
|
|
133
|
+
if (keySet.size === 0) {
|
|
134
|
+
this.index.delete(valueKey);
|
|
135
|
+
}
|
|
136
|
+
return deleted;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 更新索引条目
|
|
140
|
+
* @param oldValue 旧索引值
|
|
141
|
+
* @param newValue 新索引值
|
|
142
|
+
* @param primaryKey 主键
|
|
143
|
+
* @param throwOnDuplicate 如果是唯一索引且新值已存在是否抛出异常
|
|
144
|
+
* @returns 是否更新成功
|
|
145
|
+
*/
|
|
146
|
+
update(oldValue, newValue, primaryKey, throwOnDuplicate = false) {
|
|
147
|
+
const oldKey = this.valueToKey(oldValue);
|
|
148
|
+
const newKey = this.valueToKey(newValue);
|
|
149
|
+
if (oldKey === newKey) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (this.config.unique) {
|
|
153
|
+
const existing = this.index.get(newKey);
|
|
154
|
+
if (existing && existing.size > 0) {
|
|
155
|
+
if (throwOnDuplicate) {
|
|
156
|
+
throw new errors_1.DuplicateKeyError(this.config.name, newValue);
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
this.delete(oldValue, primaryKey);
|
|
162
|
+
this.insert(newValue, primaryKey, throwOnDuplicate);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 等值查询
|
|
167
|
+
* @param value 索引值
|
|
168
|
+
* @returns 匹配的主键集合
|
|
169
|
+
*/
|
|
170
|
+
find(value) {
|
|
171
|
+
const valueKey = this.valueToKey(value);
|
|
172
|
+
const keySet = this.index.get(valueKey);
|
|
173
|
+
return keySet ? new Set(keySet) : new Set();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 检查索引值是否存在
|
|
177
|
+
* @param value 索引值
|
|
178
|
+
*/
|
|
179
|
+
has(value) {
|
|
180
|
+
const valueKey = this.valueToKey(value);
|
|
181
|
+
const keySet = this.index.get(valueKey);
|
|
182
|
+
return keySet !== undefined && keySet.size > 0;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 检查索引值和主键组合是否存在
|
|
186
|
+
* @param value 索引值
|
|
187
|
+
* @param primaryKey 主键
|
|
188
|
+
*/
|
|
189
|
+
hasEntry(value, primaryKey) {
|
|
190
|
+
const valueKey = this.valueToKey(value);
|
|
191
|
+
const keySet = this.index.get(valueKey);
|
|
192
|
+
return keySet !== undefined && keySet.has(primaryKey);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 条件查询
|
|
196
|
+
* @param condition 查询条件
|
|
197
|
+
* @returns 匹配的主键集合
|
|
198
|
+
*/
|
|
199
|
+
query(condition) {
|
|
200
|
+
if (condition.$eq !== undefined) {
|
|
201
|
+
return this.find(condition.$eq);
|
|
202
|
+
}
|
|
203
|
+
const result = new Set();
|
|
204
|
+
if (condition.$in !== undefined) {
|
|
205
|
+
for (const value of condition.$in) {
|
|
206
|
+
const keys = this.find(value);
|
|
207
|
+
for (const key of keys) {
|
|
208
|
+
result.add(key);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (condition.$ne !== undefined) {
|
|
213
|
+
const excludeKeys = this.find(condition.$ne);
|
|
214
|
+
for (const [_, keySet] of this.index) {
|
|
215
|
+
for (const key of keySet) {
|
|
216
|
+
if (!excludeKeys.has(key)) {
|
|
217
|
+
result.add(key);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (condition.$nin !== undefined) {
|
|
223
|
+
const excludeKeys = new Set();
|
|
224
|
+
for (const value of condition.$nin) {
|
|
225
|
+
const keys = this.find(value);
|
|
226
|
+
for (const key of keys) {
|
|
227
|
+
excludeKeys.add(key);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const [_, keySet] of this.index) {
|
|
231
|
+
for (const key of keySet) {
|
|
232
|
+
if (!excludeKeys.has(key)) {
|
|
233
|
+
result.add(key);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 范围查询
|
|
242
|
+
* @param start 起始值
|
|
243
|
+
* @param end 结束值
|
|
244
|
+
* @param includeStart 是否包含起始值
|
|
245
|
+
* @param includeEnd 是否包含结束值
|
|
246
|
+
* @returns 匹配的主键集合
|
|
247
|
+
*/
|
|
248
|
+
range(start, end, includeStart = true, includeEnd = true) {
|
|
249
|
+
const result = new Set();
|
|
250
|
+
const sortedEntries = this.getSortedEntries();
|
|
251
|
+
for (const { value, keys } of sortedEntries) {
|
|
252
|
+
if (start !== undefined) {
|
|
253
|
+
const cmp = this.compareValues(value, start);
|
|
254
|
+
if (cmp < 0 || (cmp === 0 && !includeStart)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (end !== undefined) {
|
|
259
|
+
const cmp = this.compareValues(value, end);
|
|
260
|
+
if (cmp > 0 || (cmp === 0 && !includeEnd)) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
for (const key of keys) {
|
|
265
|
+
result.add(key);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* 获取所有索引值
|
|
272
|
+
*/
|
|
273
|
+
values() {
|
|
274
|
+
const result = [];
|
|
275
|
+
for (const key of this.index.keys()) {
|
|
276
|
+
result.push(this.keyToValue(key));
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 获取所有主键
|
|
282
|
+
*/
|
|
283
|
+
keys() {
|
|
284
|
+
const result = [];
|
|
285
|
+
for (const keySet of this.index.values()) {
|
|
286
|
+
for (const key of keySet) {
|
|
287
|
+
result.push(key);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 获取所有条目
|
|
294
|
+
*/
|
|
295
|
+
entries() {
|
|
296
|
+
const result = [];
|
|
297
|
+
for (const [key, keySet] of this.index) {
|
|
298
|
+
result.push({
|
|
299
|
+
value: this.keyToValue(key),
|
|
300
|
+
keys: new Set(keySet),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 清空索引
|
|
307
|
+
*/
|
|
308
|
+
clear() {
|
|
309
|
+
this.index.clear();
|
|
310
|
+
this.keyCount = 0;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* 获取索引统计信息
|
|
314
|
+
*/
|
|
315
|
+
getStats() {
|
|
316
|
+
const valueCount = this.index.size;
|
|
317
|
+
const memoryUsage = this.estimateMemoryUsage();
|
|
318
|
+
return {
|
|
319
|
+
name: this.config.name,
|
|
320
|
+
type: this.Type,
|
|
321
|
+
fieldName: this.config.fieldName,
|
|
322
|
+
keyCount: this.keyCount,
|
|
323
|
+
entryCount: valueCount,
|
|
324
|
+
unique: this.config.unique,
|
|
325
|
+
memoryUsage,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* 批量插入
|
|
330
|
+
* @param entries 条目数组
|
|
331
|
+
* @param throwOnDuplicate 如果是唯一索引且值已存在是否抛出异常
|
|
332
|
+
*/
|
|
333
|
+
insertBatch(entries, throwOnDuplicate = false) {
|
|
334
|
+
let success = 0;
|
|
335
|
+
let failed = 0;
|
|
336
|
+
const errors = [];
|
|
337
|
+
for (const { value, primaryKey } of entries) {
|
|
338
|
+
try {
|
|
339
|
+
if (this.insert(value, primaryKey, throwOnDuplicate)) {
|
|
340
|
+
success++;
|
|
341
|
+
} else {
|
|
342
|
+
failed++;
|
|
343
|
+
errors.push({ primaryKey, error: "Duplicate or existing entry" });
|
|
344
|
+
}
|
|
345
|
+
} catch (error) {
|
|
346
|
+
failed++;
|
|
347
|
+
errors.push({
|
|
348
|
+
primaryKey,
|
|
349
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return { success, failed, errors };
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* 批量删除
|
|
357
|
+
* @param entries 条目数组
|
|
358
|
+
*/
|
|
359
|
+
deleteBatch(entries) {
|
|
360
|
+
let success = 0;
|
|
361
|
+
let failed = 0;
|
|
362
|
+
for (const { value, primaryKey } of entries) {
|
|
363
|
+
if (this.delete(value, primaryKey)) {
|
|
364
|
+
success++;
|
|
365
|
+
} else {
|
|
366
|
+
failed++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return { success, failed };
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* 将索引值转换为存储键
|
|
373
|
+
*/
|
|
374
|
+
valueToKey(value) {
|
|
375
|
+
if (value === null) {
|
|
376
|
+
return "\x00null";
|
|
377
|
+
}
|
|
378
|
+
if (value === undefined) {
|
|
379
|
+
return "\x00undefined";
|
|
380
|
+
}
|
|
381
|
+
if (typeof value === "number") {
|
|
382
|
+
return `\x01num:${value}`;
|
|
383
|
+
}
|
|
384
|
+
if (typeof value === "boolean") {
|
|
385
|
+
return `\x02bool:${value}`;
|
|
386
|
+
}
|
|
387
|
+
return `\x03str:${value}`;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* 将存储键转换为索引值
|
|
391
|
+
*/
|
|
392
|
+
keyToValue(key) {
|
|
393
|
+
if (key === "\x00null") {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
if (key === "\x00undefined") {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
if (key.startsWith("\x01num:")) {
|
|
400
|
+
return parseFloat(key.slice(6));
|
|
401
|
+
}
|
|
402
|
+
if (key.startsWith("\x02bool:")) {
|
|
403
|
+
return key.slice(7) === "true";
|
|
404
|
+
}
|
|
405
|
+
if (key.startsWith("\x03str:")) {
|
|
406
|
+
return key.slice(6);
|
|
407
|
+
}
|
|
408
|
+
return key;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 比较两个索引值
|
|
412
|
+
*/
|
|
413
|
+
compareValues(a, b) {
|
|
414
|
+
if (a === null && b === null) return 0;
|
|
415
|
+
if (a === null) return -1;
|
|
416
|
+
if (b === null) return 1;
|
|
417
|
+
if (a === undefined && b === undefined) return 0;
|
|
418
|
+
if (a === undefined) return -1;
|
|
419
|
+
if (b === undefined) return 1;
|
|
420
|
+
const typeA = typeof a;
|
|
421
|
+
const typeB = typeof b;
|
|
422
|
+
if (typeA !== typeB) {
|
|
423
|
+
return typeA.localeCompare(typeB);
|
|
424
|
+
}
|
|
425
|
+
if (typeA === "number") {
|
|
426
|
+
return a - b;
|
|
427
|
+
}
|
|
428
|
+
if (typeA === "boolean") {
|
|
429
|
+
return (a ? 1 : 0) - (b ? 1 : 0);
|
|
430
|
+
}
|
|
431
|
+
const strA = String(a);
|
|
432
|
+
const strB = String(b);
|
|
433
|
+
if (strA < strB) return -1;
|
|
434
|
+
if (strA > strB) return 1;
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* 获取排序后的条目
|
|
439
|
+
*/
|
|
440
|
+
getSortedEntries() {
|
|
441
|
+
const entries = this.entries();
|
|
442
|
+
entries.sort((a, b) => this.compareValues(a.value, b.value));
|
|
443
|
+
return entries;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* 估算内存使用
|
|
447
|
+
*/
|
|
448
|
+
estimateMemoryUsage() {
|
|
449
|
+
const avgValueSize = 32;
|
|
450
|
+
const avgKeySize = 16;
|
|
451
|
+
const setOverhead = 64;
|
|
452
|
+
const mapOverhead = 64;
|
|
453
|
+
return this.index.size * (avgValueSize + mapOverhead) + this.keyCount * (avgKeySize + setOverhead);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* 验证索引一致性
|
|
457
|
+
*/
|
|
458
|
+
validate() {
|
|
459
|
+
const errors = [];
|
|
460
|
+
let countedKeys = 0;
|
|
461
|
+
for (const [valueKey, keySet] of this.index) {
|
|
462
|
+
if (keySet.size === 0) {
|
|
463
|
+
errors.push(`Empty key set for value: ${valueKey}`);
|
|
464
|
+
}
|
|
465
|
+
if (this.config.unique && keySet.size > 1) {
|
|
466
|
+
errors.push(`Unique index has multiple keys for value: ${valueKey}`);
|
|
467
|
+
}
|
|
468
|
+
countedKeys += keySet.size;
|
|
469
|
+
}
|
|
470
|
+
if (countedKeys !== this.keyCount) {
|
|
471
|
+
errors.push(`Key count mismatch: expected ${this.keyCount}, found ${countedKeys}`);
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
valid: errors.length === 0,
|
|
475
|
+
errors,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
exports.SecondaryIndex = SecondaryIndex;
|
|
480
|
+
//# sourceMappingURL=SecondaryIndex.js.map
|