@yanit/jsondb 0.1.1
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/README.md +903 -0
- package/dist/bin/cli-export.d.ts +7 -0
- package/dist/bin/cli-export.d.ts.map +1 -0
- package/dist/bin/cli-export.js +318 -0
- package/dist/bin/cli-export.js.map +1 -0
- package/dist/bin/cli-import.d.ts +7 -0
- package/dist/bin/cli-import.d.ts.map +1 -0
- package/dist/bin/cli-import.js +298 -0
- package/dist/bin/cli-import.js.map +1 -0
- package/dist/bin/server.d.ts +7 -0
- package/dist/bin/server.d.ts.map +1 -0
- package/dist/bin/server.js +92 -0
- package/dist/bin/server.js.map +1 -0
- package/dist/examples/sql-example.d.ts +7 -0
- package/dist/examples/sql-example.d.ts.map +1 -0
- package/dist/examples/sql-example.js +131 -0
- package/dist/examples/sql-example.js.map +1 -0
- package/dist/src/BulkOp.d.ts +74 -0
- package/dist/src/BulkOp.d.ts.map +1 -0
- package/dist/src/BulkOp.js +143 -0
- package/dist/src/BulkOp.js.map +1 -0
- package/dist/src/Collection.d.ts +232 -0
- package/dist/src/Collection.d.ts.map +1 -0
- package/dist/src/Collection.js +705 -0
- package/dist/src/Collection.js.map +1 -0
- package/dist/src/Cursor.d.ts +94 -0
- package/dist/src/Cursor.d.ts.map +1 -0
- package/dist/src/Cursor.js +259 -0
- package/dist/src/Cursor.js.map +1 -0
- package/dist/src/Database.d.ts +98 -0
- package/dist/src/Database.d.ts.map +1 -0
- package/dist/src/Database.js +198 -0
- package/dist/src/Database.js.map +1 -0
- package/dist/src/Operators.d.ts +73 -0
- package/dist/src/Operators.d.ts.map +1 -0
- package/dist/src/Operators.js +339 -0
- package/dist/src/Operators.js.map +1 -0
- package/dist/src/QueryCache.d.ts +87 -0
- package/dist/src/QueryCache.d.ts.map +1 -0
- package/dist/src/QueryCache.js +155 -0
- package/dist/src/QueryCache.js.map +1 -0
- package/dist/src/SQLExecutor.d.ts +60 -0
- package/dist/src/SQLExecutor.d.ts.map +1 -0
- package/dist/src/SQLExecutor.js +317 -0
- package/dist/src/SQLExecutor.js.map +1 -0
- package/dist/src/SQLParser.d.ts +181 -0
- package/dist/src/SQLParser.d.ts.map +1 -0
- package/dist/src/SQLParser.js +640 -0
- package/dist/src/SQLParser.js.map +1 -0
- package/dist/src/Schema.d.ts +92 -0
- package/dist/src/Schema.d.ts.map +1 -0
- package/dist/src/Schema.js +253 -0
- package/dist/src/Schema.js.map +1 -0
- package/dist/src/Transaction.d.ts +118 -0
- package/dist/src/Transaction.d.ts.map +1 -0
- package/dist/src/Transaction.js +233 -0
- package/dist/src/Transaction.js.map +1 -0
- package/dist/src/Utils.d.ts +68 -0
- package/dist/src/Utils.d.ts.map +1 -0
- package/dist/src/Utils.js +187 -0
- package/dist/src/Utils.js.map +1 -0
- package/dist/src/errors.d.ts +58 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +85 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +39 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +44 -0
- package/dist/src/index.js.map +1 -0
- package/dist/test/basic.test.d.ts +5 -0
- package/dist/test/basic.test.d.ts.map +1 -0
- package/dist/test/basic.test.js +283 -0
- package/dist/test/basic.test.js.map +1 -0
- package/dist/test/index.test.d.ts +5 -0
- package/dist/test/index.test.d.ts.map +1 -0
- package/dist/test/index.test.js +126 -0
- package/dist/test/index.test.js.map +1 -0
- package/dist/test/jsonb.test.d.ts +5 -0
- package/dist/test/jsonb.test.d.ts.map +1 -0
- package/dist/test/jsonb.test.js +165 -0
- package/dist/test/jsonb.test.js.map +1 -0
- package/dist/test/optimization.test.d.ts +6 -0
- package/dist/test/optimization.test.d.ts.map +1 -0
- package/dist/test/optimization.test.js +196 -0
- package/dist/test/optimization.test.js.map +1 -0
- package/dist/test/schema.test.d.ts +5 -0
- package/dist/test/schema.test.d.ts.map +1 -0
- package/dist/test/schema.test.js +197 -0
- package/dist/test/schema.test.js.map +1 -0
- package/dist/test/sql.test.d.ts +7 -0
- package/dist/test/sql.test.d.ts.map +1 -0
- package/dist/test/sql.test.js +21 -0
- package/dist/test/sql.test.js.map +1 -0
- package/package.json +73 -0
- package/src/BulkOp.js +181 -0
- package/src/BulkOp.ts +191 -0
- package/src/Collection.js +843 -0
- package/src/Collection.ts +896 -0
- package/src/Cursor.js +315 -0
- package/src/Cursor.ts +319 -0
- package/src/Database.js +244 -0
- package/src/Database.ts +268 -0
- package/src/Operators.js +382 -0
- package/src/Operators.ts +375 -0
- package/src/QueryCache.js +190 -0
- package/src/QueryCache.ts +208 -0
- package/src/SQLExecutor.ts +391 -0
- package/src/SQLParser.ts +814 -0
- package/src/Schema.js +292 -0
- package/src/Schema.ts +317 -0
- package/src/Transaction.js +291 -0
- package/src/Transaction.ts +313 -0
- package/src/Utils.js +205 -0
- package/src/Utils.ts +205 -0
- package/src/errors.js +93 -0
- package/src/errors.ts +93 -0
- package/src/index.js +90 -0
- package/src/index.ts +106 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 查询结果缓存模块
|
|
3
|
+
* 缓存相同查询的结果,提升重复查询性能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createHash } from 'crypto';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 缓存条目接口
|
|
10
|
+
*/
|
|
11
|
+
interface CacheEntry {
|
|
12
|
+
data: unknown;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
lastAccessed: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 查询缓存选项
|
|
20
|
+
*/
|
|
21
|
+
interface QueryCacheOptions {
|
|
22
|
+
maxSize?: number;
|
|
23
|
+
ttl?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 查询缓存类
|
|
28
|
+
*/
|
|
29
|
+
export class QueryCache {
|
|
30
|
+
maxSize: number;
|
|
31
|
+
ttl: number;
|
|
32
|
+
cache: Map<string, CacheEntry>;
|
|
33
|
+
hits: number;
|
|
34
|
+
misses: number;
|
|
35
|
+
|
|
36
|
+
constructor(options: QueryCacheOptions = {}) {
|
|
37
|
+
this.maxSize = options.maxSize || 1000;
|
|
38
|
+
this.ttl = options.ttl || 60000;
|
|
39
|
+
this.cache = new Map();
|
|
40
|
+
this.hits = 0;
|
|
41
|
+
this.misses = 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 生成查询缓存键
|
|
46
|
+
*/
|
|
47
|
+
generateKey(collection: string, query: Record<string, unknown>, options: Record<string, unknown> = {}): string {
|
|
48
|
+
const keyData = JSON.stringify({
|
|
49
|
+
collection,
|
|
50
|
+
query,
|
|
51
|
+
options: {
|
|
52
|
+
sort: options.sort,
|
|
53
|
+
limit: options.limit,
|
|
54
|
+
skip: options.skip,
|
|
55
|
+
projection: options.projection
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return createHash('sha256').update(keyData).digest('hex');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 获取缓存
|
|
64
|
+
*/
|
|
65
|
+
get(key: string): unknown | null {
|
|
66
|
+
const entry = this.cache.get(key);
|
|
67
|
+
|
|
68
|
+
if (!entry) {
|
|
69
|
+
this.misses++;
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 检查是否过期
|
|
74
|
+
if (Date.now() > entry.expiresAt) {
|
|
75
|
+
this.cache.delete(key);
|
|
76
|
+
this.misses++;
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 更新访问时间(LRU)
|
|
81
|
+
entry.lastAccessed = Date.now();
|
|
82
|
+
this.hits++;
|
|
83
|
+
|
|
84
|
+
return entry.data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 设置缓存
|
|
89
|
+
*/
|
|
90
|
+
set(key: string, data: unknown, ttl?: number): void {
|
|
91
|
+
// 如果缓存已满,删除最久未使用的条目
|
|
92
|
+
if (this.cache.size >= this.maxSize) {
|
|
93
|
+
this._evictOldest();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.cache.set(key, {
|
|
97
|
+
data,
|
|
98
|
+
createdAt: Date.now(),
|
|
99
|
+
expiresAt: Date.now() + (ttl || this.ttl),
|
|
100
|
+
lastAccessed: Date.now()
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 删除缓存
|
|
106
|
+
*/
|
|
107
|
+
delete(key: string): void {
|
|
108
|
+
this.cache.delete(key);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 清空缓存
|
|
113
|
+
*/
|
|
114
|
+
clear(): void {
|
|
115
|
+
this.cache.clear();
|
|
116
|
+
this.hits = 0;
|
|
117
|
+
this.misses = 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 删除与集合相关的所有缓存
|
|
122
|
+
*/
|
|
123
|
+
invalidateCollection(collection: string): void {
|
|
124
|
+
for (const [key] of this.cache.entries()) {
|
|
125
|
+
if (key.includes(collection)) {
|
|
126
|
+
this.cache.delete(key);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 删除最久未使用的条目(LRU)
|
|
133
|
+
*/
|
|
134
|
+
private _evictOldest(): void {
|
|
135
|
+
let oldestKey: string | null = null;
|
|
136
|
+
let oldestTime = Infinity;
|
|
137
|
+
|
|
138
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
139
|
+
if (entry.lastAccessed < oldestTime) {
|
|
140
|
+
oldestTime = entry.lastAccessed;
|
|
141
|
+
oldestKey = key;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (oldestKey) {
|
|
146
|
+
this.cache.delete(oldestKey);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 获取缓存统计
|
|
152
|
+
*/
|
|
153
|
+
getStats(): {
|
|
154
|
+
size: number;
|
|
155
|
+
maxSize: number;
|
|
156
|
+
hits: number;
|
|
157
|
+
misses: number;
|
|
158
|
+
hitRate: string;
|
|
159
|
+
ttl: number;
|
|
160
|
+
} {
|
|
161
|
+
const total = this.hits + this.misses;
|
|
162
|
+
return {
|
|
163
|
+
size: this.cache.size,
|
|
164
|
+
maxSize: this.maxSize,
|
|
165
|
+
hits: this.hits,
|
|
166
|
+
misses: this.misses,
|
|
167
|
+
hitRate: total > 0 ? ((this.hits / total) * 100).toFixed(2) + '%' : '0%',
|
|
168
|
+
ttl: this.ttl
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 全局查询缓存实例
|
|
175
|
+
*/
|
|
176
|
+
export const globalQueryCache = new QueryCache({
|
|
177
|
+
maxSize: 500,
|
|
178
|
+
ttl: 30000 // 30 秒
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 缓存查询装饰器
|
|
183
|
+
*/
|
|
184
|
+
export function cacheQuery(
|
|
185
|
+
queryFn: (this: unknown, collection: { name: string }, query: Record<string, unknown>, queryOptions: Record<string, unknown>) => Promise<unknown>,
|
|
186
|
+
options: { cache?: QueryCache; ttl?: number } = {}
|
|
187
|
+
) {
|
|
188
|
+
const cache = options.cache || globalQueryCache;
|
|
189
|
+
const ttl = options.ttl;
|
|
190
|
+
|
|
191
|
+
return async function(this: unknown, collection: { name: string }, query: Record<string, unknown>, queryOptions: Record<string, unknown>) {
|
|
192
|
+
const key = cache.generateKey(collection.name, query, queryOptions);
|
|
193
|
+
|
|
194
|
+
// 尝试从缓存获取
|
|
195
|
+
const cached = cache.get(key);
|
|
196
|
+
if (cached !== null) {
|
|
197
|
+
return cached;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 执行查询
|
|
201
|
+
const result = await queryFn.call(this, collection, query, queryOptions);
|
|
202
|
+
|
|
203
|
+
// 存入缓存
|
|
204
|
+
cache.set(key, result, ttl);
|
|
205
|
+
|
|
206
|
+
return result;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL 执行器模块
|
|
3
|
+
* 将解析后的 SQL 语句转换为 JSONDB Collection API 调用
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Collection } from './Collection.js';
|
|
7
|
+
import type {
|
|
8
|
+
SQLStatement,
|
|
9
|
+
SelectStatement,
|
|
10
|
+
InsertStatement,
|
|
11
|
+
UpdateStatement,
|
|
12
|
+
DeleteStatement,
|
|
13
|
+
WhereClause
|
|
14
|
+
} from './SQLParser.js';
|
|
15
|
+
import { parseSQL } from './SQLParser.js';
|
|
16
|
+
import { matchQuery } from './Operators.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* SQL 执行结果
|
|
20
|
+
*/
|
|
21
|
+
export interface SQLExecutionResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
data?: unknown[];
|
|
24
|
+
affectedRows?: number;
|
|
25
|
+
insertId?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* SQL 执行器类
|
|
31
|
+
*/
|
|
32
|
+
export class SQLExecutor {
|
|
33
|
+
private collection: Collection;
|
|
34
|
+
|
|
35
|
+
constructor(collection: Collection) {
|
|
36
|
+
this.collection = collection;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 执行 SQL 语句
|
|
41
|
+
*/
|
|
42
|
+
async execute(statement: SQLStatement): Promise<SQLExecutionResult> {
|
|
43
|
+
try {
|
|
44
|
+
switch (statement.type) {
|
|
45
|
+
case 'SELECT':
|
|
46
|
+
return await this.executeSelect(statement);
|
|
47
|
+
case 'INSERT':
|
|
48
|
+
return await this.executeInsert(statement);
|
|
49
|
+
case 'UPDATE':
|
|
50
|
+
return await this.executeUpdate(statement);
|
|
51
|
+
case 'DELETE':
|
|
52
|
+
return await this.executeDelete(statement);
|
|
53
|
+
default:
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: `不支持的 SQL 语句类型:${(statement as SQLStatement).type}`
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: `SQL 执行错误:${(error as Error).message}`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 执行 SELECT 语句
|
|
69
|
+
*/
|
|
70
|
+
private async executeSelect(statement: SelectStatement): Promise<SQLExecutionResult> {
|
|
71
|
+
// 构建查询
|
|
72
|
+
let cursor = this.collection.find({});
|
|
73
|
+
|
|
74
|
+
// 应用 WHERE 子句
|
|
75
|
+
if (statement.where) {
|
|
76
|
+
const query = this.whereToQuery(statement.where);
|
|
77
|
+
cursor = this.collection.find(query);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 应用 ORDER BY
|
|
81
|
+
if (statement.orderBy && statement.orderBy.length > 0) {
|
|
82
|
+
const sortSpec: Record<string, number> = {};
|
|
83
|
+
for (const { column, direction } of statement.orderBy) {
|
|
84
|
+
sortSpec[column] = direction === 'DESC' ? -1 : 1;
|
|
85
|
+
}
|
|
86
|
+
cursor = cursor.sort(sortSpec);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 应用 LIMIT
|
|
90
|
+
if (statement.limit !== undefined) {
|
|
91
|
+
cursor = cursor.limit(statement.limit);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 应用 OFFSET
|
|
95
|
+
if (statement.offset !== undefined) {
|
|
96
|
+
cursor = cursor.skip(statement.offset);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 执行查询
|
|
100
|
+
let results = await cursor.toArray();
|
|
101
|
+
|
|
102
|
+
// 应用投影(列选择)
|
|
103
|
+
if (statement.columns && statement.columns.length > 0 && !statement.columns.includes('*')) {
|
|
104
|
+
const projection: Record<string, number> = {};
|
|
105
|
+
for (const col of statement.columns) {
|
|
106
|
+
projection[col] = 1;
|
|
107
|
+
}
|
|
108
|
+
results = results.map(doc => {
|
|
109
|
+
const projected: Record<string, unknown> = {};
|
|
110
|
+
for (const col of statement.columns) {
|
|
111
|
+
if (doc[col] !== undefined) {
|
|
112
|
+
projected[col] = doc[col];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return projected;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 应用 DISTINCT
|
|
120
|
+
if (statement.distinct) {
|
|
121
|
+
const seen = new Set<string>();
|
|
122
|
+
results = results.filter(doc => {
|
|
123
|
+
const key = JSON.stringify(doc);
|
|
124
|
+
if (seen.has(key)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
seen.add(key);
|
|
128
|
+
return true;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 应用 GROUP BY (简化版)
|
|
133
|
+
if (statement.groupBy && statement.groupBy.length > 0) {
|
|
134
|
+
results = this.applyGroupBy(results, statement.groupBy);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
success: true,
|
|
139
|
+
data: results
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 执行 INSERT 语句
|
|
145
|
+
*/
|
|
146
|
+
private async executeInsert(statement: InsertStatement): Promise<SQLExecutionResult> {
|
|
147
|
+
const docs: Record<string, unknown>[] = [];
|
|
148
|
+
|
|
149
|
+
for (const row of statement.values) {
|
|
150
|
+
const doc: Record<string, unknown> = {};
|
|
151
|
+
for (let i = 0; i < statement.columns.length; i++) {
|
|
152
|
+
doc[statement.columns[i]] = row[i];
|
|
153
|
+
}
|
|
154
|
+
docs.push(doc);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (docs.length === 1) {
|
|
158
|
+
const inserted = await this.collection.insertOne(docs[0]);
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
data: [inserted],
|
|
162
|
+
affectedRows: 1,
|
|
163
|
+
insertId: inserted._id as string
|
|
164
|
+
};
|
|
165
|
+
} else {
|
|
166
|
+
const result = await this.collection.insertMany(docs);
|
|
167
|
+
return {
|
|
168
|
+
success: true,
|
|
169
|
+
data: docs,
|
|
170
|
+
affectedRows: result.insertedCount,
|
|
171
|
+
insertId: result.insertedIds[0]
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 执行 UPDATE 语句
|
|
178
|
+
*/
|
|
179
|
+
private async executeUpdate(statement: UpdateStatement): Promise<SQLExecutionResult> {
|
|
180
|
+
// 构建 $set 对象
|
|
181
|
+
const $set: Record<string, unknown> = {};
|
|
182
|
+
for (const { column, value } of statement.set) {
|
|
183
|
+
$set[column] = value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 构建查询
|
|
187
|
+
let query: Record<string, unknown> = {};
|
|
188
|
+
if (statement.where) {
|
|
189
|
+
query = this.whereToQuery(statement.where);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const result = await this.collection.updateMany(query, { $set });
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
success: true,
|
|
196
|
+
affectedRows: result.modifiedCount
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 执行 DELETE 语句
|
|
202
|
+
*/
|
|
203
|
+
private async executeDelete(statement: DeleteStatement): Promise<SQLExecutionResult> {
|
|
204
|
+
// 构建查询
|
|
205
|
+
let query: Record<string, unknown> = {};
|
|
206
|
+
if (statement.where) {
|
|
207
|
+
query = this.whereToQuery(statement.where);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const result = await this.collection.deleteMany(query);
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
success: true,
|
|
214
|
+
affectedRows: result.deletedCount
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 将 WHERE 子句转换为 MongoDB 风格查询
|
|
220
|
+
*/
|
|
221
|
+
private whereToQuery(where: WhereClause): Record<string, unknown> {
|
|
222
|
+
switch (where.type) {
|
|
223
|
+
case 'condition':
|
|
224
|
+
return this.conditionToQuery(where.column, where.operator, where.value);
|
|
225
|
+
|
|
226
|
+
case 'and':
|
|
227
|
+
if (where.conditions.length === 0) return {};
|
|
228
|
+
if (where.conditions.length === 1) {
|
|
229
|
+
return this.whereToQuery(where.conditions[0]);
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
$and: where.conditions.map(c => this.whereToQuery(c))
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
case 'or':
|
|
236
|
+
if (where.conditions.length === 0) return {};
|
|
237
|
+
if (where.conditions.length === 1) {
|
|
238
|
+
return this.whereToQuery(where.conditions[0]);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
$or: where.conditions.map(c => this.whereToQuery(c))
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
case 'not':
|
|
245
|
+
const condition = this.whereToQuery(where.condition);
|
|
246
|
+
return { $nor: [condition] };
|
|
247
|
+
|
|
248
|
+
default:
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 将单个条件转换为查询对象
|
|
255
|
+
*/
|
|
256
|
+
private conditionToQuery(column: string, operator: string, value: unknown): Record<string, unknown> {
|
|
257
|
+
const query: Record<string, unknown> = {};
|
|
258
|
+
|
|
259
|
+
switch (operator.toUpperCase()) {
|
|
260
|
+
case '=':
|
|
261
|
+
query[column] = value;
|
|
262
|
+
break;
|
|
263
|
+
|
|
264
|
+
case '!=':
|
|
265
|
+
case '<>':
|
|
266
|
+
query[column] = { $ne: value };
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case '<':
|
|
270
|
+
query[column] = { $lt: value };
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case '<=':
|
|
274
|
+
query[column] = { $lte: value };
|
|
275
|
+
break;
|
|
276
|
+
|
|
277
|
+
case '>':
|
|
278
|
+
query[column] = { $gt: value };
|
|
279
|
+
break;
|
|
280
|
+
|
|
281
|
+
case '>=':
|
|
282
|
+
query[column] = { $gte: value };
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case 'LIKE':
|
|
286
|
+
// 转换 SQL LIKE 到正则表达式
|
|
287
|
+
if (typeof value === 'string') {
|
|
288
|
+
let regex = value
|
|
289
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // 转义特殊字符
|
|
290
|
+
.replace(/%/g, '.*') // % 匹配任意字符
|
|
291
|
+
.replace(/_/g, '.'); // _ 匹配单个字符
|
|
292
|
+
query[column] = { $regex: new RegExp(`^${regex}$`, 'i') };
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
case 'IN':
|
|
297
|
+
if (Array.isArray(value)) {
|
|
298
|
+
query[column] = { $in: value };
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
|
|
302
|
+
case 'NOT IN':
|
|
303
|
+
if (Array.isArray(value)) {
|
|
304
|
+
query[column] = { $nin: value };
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
|
|
308
|
+
case 'IS':
|
|
309
|
+
if (value === null) {
|
|
310
|
+
query[column] = { $exists: false };
|
|
311
|
+
} else if (value === true) {
|
|
312
|
+
query[column] = { $exists: true };
|
|
313
|
+
}
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'IS NOT':
|
|
317
|
+
if (value === null) {
|
|
318
|
+
query[column] = { $exists: true };
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
|
|
322
|
+
case 'BETWEEN':
|
|
323
|
+
// 假设 value 是 [min, max] 数组
|
|
324
|
+
if (Array.isArray(value) && value.length === 2) {
|
|
325
|
+
query[column] = {
|
|
326
|
+
$gte: value[0],
|
|
327
|
+
$lte: value[1]
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
|
|
332
|
+
default:
|
|
333
|
+
query[column] = { [`$${operator.toLowerCase()}`]: value };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return query;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 应用 GROUP BY (简化实现)
|
|
341
|
+
*/
|
|
342
|
+
private applyGroupBy(
|
|
343
|
+
docs: Array<Record<string, unknown>>,
|
|
344
|
+
groupBy: string[]
|
|
345
|
+
): Array<Record<string, unknown>> {
|
|
346
|
+
const groups = new Map<string, Array<Record<string, unknown>>>();
|
|
347
|
+
|
|
348
|
+
for (const doc of docs) {
|
|
349
|
+
const key = groupBy.map(col => JSON.stringify(doc[col])).join('|');
|
|
350
|
+
if (!groups.has(key)) {
|
|
351
|
+
groups.set(key, []);
|
|
352
|
+
}
|
|
353
|
+
groups.get(key)!.push(doc);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return Array.from(groups.entries()).map(([key, groupDocs]) => {
|
|
357
|
+
const result: Record<string, unknown> = {};
|
|
358
|
+
|
|
359
|
+
// 添加分组字段
|
|
360
|
+
const keyParts = key.split('|');
|
|
361
|
+
groupBy.forEach((col, i) => {
|
|
362
|
+
result[col] = JSON.parse(keyParts[i]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// 添加聚合字段(简化:只添加计数)
|
|
366
|
+
result._count = groupDocs.length;
|
|
367
|
+
|
|
368
|
+
return result;
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 在 Collection 上执行 SQL
|
|
375
|
+
*/
|
|
376
|
+
export async function executeSQL(
|
|
377
|
+
collection: Collection,
|
|
378
|
+
sql: string
|
|
379
|
+
): Promise<SQLExecutionResult> {
|
|
380
|
+
const parseResult = parseSQL(sql);
|
|
381
|
+
|
|
382
|
+
if (!parseResult.success || !parseResult.statement) {
|
|
383
|
+
return {
|
|
384
|
+
success: false,
|
|
385
|
+
error: parseResult.error || 'SQL 解析失败'
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const executor = new SQLExecutor(collection);
|
|
390
|
+
return await executor.execute(parseResult.statement);
|
|
391
|
+
}
|