@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
package/src/SQLParser.ts
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL 解析器模块
|
|
3
|
+
* 支持标准 SQL 语法子集,转换为 JSONDB 查询操作
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SQL 语句类型
|
|
8
|
+
*/
|
|
9
|
+
export type SQLStatementType = 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SQL 解析后的 SELECT 语句结构
|
|
13
|
+
*/
|
|
14
|
+
export interface SelectStatement {
|
|
15
|
+
type: 'SELECT';
|
|
16
|
+
columns: string[];
|
|
17
|
+
table: string;
|
|
18
|
+
where?: WhereClause;
|
|
19
|
+
orderBy?: OrderByClause[];
|
|
20
|
+
limit?: number;
|
|
21
|
+
offset?: number;
|
|
22
|
+
groupBy?: string[];
|
|
23
|
+
having?: WhereClause;
|
|
24
|
+
distinct?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* SQL 解析后的 INSERT 语句结构
|
|
29
|
+
*/
|
|
30
|
+
export interface InsertStatement {
|
|
31
|
+
type: 'INSERT';
|
|
32
|
+
table: string;
|
|
33
|
+
columns: string[];
|
|
34
|
+
values: unknown[][];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* SQL 解析后的 UPDATE 语句结构
|
|
39
|
+
*/
|
|
40
|
+
export interface UpdateStatement {
|
|
41
|
+
type: 'UPDATE';
|
|
42
|
+
table: string;
|
|
43
|
+
set: { column: string; value: unknown }[];
|
|
44
|
+
where?: WhereClause;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* SQL 解析后的 DELETE 语句结构
|
|
49
|
+
*/
|
|
50
|
+
export interface DeleteStatement {
|
|
51
|
+
type: 'DELETE';
|
|
52
|
+
table: string;
|
|
53
|
+
where?: WhereClause;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* WHERE 子句结构
|
|
58
|
+
*/
|
|
59
|
+
export type WhereClause =
|
|
60
|
+
| { type: 'condition'; column: string; operator: string; value: unknown }
|
|
61
|
+
| { type: 'and'; conditions: WhereClause[] }
|
|
62
|
+
| { type: 'or'; conditions: WhereClause[] }
|
|
63
|
+
| { type: 'not'; condition: WhereClause };
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* ORDER BY 子句结构
|
|
67
|
+
*/
|
|
68
|
+
export interface OrderByClause {
|
|
69
|
+
column: string;
|
|
70
|
+
direction: 'ASC' | 'DESC';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* SQL 语句联合类型
|
|
75
|
+
*/
|
|
76
|
+
export type SQLStatement = SelectStatement | InsertStatement | UpdateStatement | DeleteStatement;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* SQL 解析结果
|
|
80
|
+
*/
|
|
81
|
+
export interface SQLParseResult {
|
|
82
|
+
success: boolean;
|
|
83
|
+
statement?: SQLStatement;
|
|
84
|
+
error?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* SQL 解析器类
|
|
89
|
+
*/
|
|
90
|
+
export class SQLParser {
|
|
91
|
+
private sql: string = '';
|
|
92
|
+
private pos: number = 0;
|
|
93
|
+
private currentChar: string = '';
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 解析 SQL 语句
|
|
97
|
+
*/
|
|
98
|
+
parse(sql: string): SQLParseResult {
|
|
99
|
+
try {
|
|
100
|
+
this.sql = sql.trim();
|
|
101
|
+
this.pos = 0;
|
|
102
|
+
this.currentChar = this.sql[0] || '';
|
|
103
|
+
|
|
104
|
+
// 跳过空白字符
|
|
105
|
+
this.skipWhitespace();
|
|
106
|
+
|
|
107
|
+
// 获取第一个词判断语句类型
|
|
108
|
+
const firstWord = this.readWord().toUpperCase();
|
|
109
|
+
|
|
110
|
+
this.skipWhitespace();
|
|
111
|
+
|
|
112
|
+
switch (firstWord) {
|
|
113
|
+
case 'SELECT':
|
|
114
|
+
return { success: true, statement: this.parseSelect() };
|
|
115
|
+
case 'INSERT':
|
|
116
|
+
return { success: true, statement: this.parseInsert() };
|
|
117
|
+
case 'UPDATE':
|
|
118
|
+
return { success: true, statement: this.parseUpdate() };
|
|
119
|
+
case 'DELETE':
|
|
120
|
+
return { success: true, statement: this.parseDelete() };
|
|
121
|
+
default:
|
|
122
|
+
return { success: false, error: `不支持的 SQL 语句类型:${firstWord}` };
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
error: `SQL 解析错误:${(error as Error).message}`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 解析 SELECT 语句
|
|
134
|
+
*/
|
|
135
|
+
private parseSelect(): SelectStatement {
|
|
136
|
+
const statement: SelectStatement = {
|
|
137
|
+
type: 'SELECT',
|
|
138
|
+
columns: [],
|
|
139
|
+
table: '',
|
|
140
|
+
distinct: false
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// 检查 DISTINCT
|
|
144
|
+
if (this.peekWord().toUpperCase() === 'DISTINCT') {
|
|
145
|
+
this.readWord();
|
|
146
|
+
statement.distinct = true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 解析列
|
|
150
|
+
statement.columns = this.parseColumnList();
|
|
151
|
+
|
|
152
|
+
// 解析 FROM
|
|
153
|
+
this.skipWhitespace();
|
|
154
|
+
const fromWord = this.readWord().toUpperCase();
|
|
155
|
+
if (fromWord !== 'FROM') {
|
|
156
|
+
throw new Error(`期望 FROM 关键字,得到:${fromWord}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 解析表名
|
|
160
|
+
this.skipWhitespace();
|
|
161
|
+
statement.table = this.readIdentifier();
|
|
162
|
+
|
|
163
|
+
// 解析可选子句
|
|
164
|
+
while (this.pos < this.sql.length) {
|
|
165
|
+
this.skipWhitespace();
|
|
166
|
+
if (this.pos >= this.sql.length) break;
|
|
167
|
+
|
|
168
|
+
const keyword = this.peekWord().toUpperCase();
|
|
169
|
+
|
|
170
|
+
switch (keyword) {
|
|
171
|
+
case 'WHERE':
|
|
172
|
+
this.readWord();
|
|
173
|
+
this.skipWhitespace();
|
|
174
|
+
statement.where = this.parseWhereClause();
|
|
175
|
+
break;
|
|
176
|
+
case 'GROUP':
|
|
177
|
+
this.readWord();
|
|
178
|
+
this.skipWhitespace();
|
|
179
|
+
const byWord = this.readWord().toUpperCase();
|
|
180
|
+
if (byWord !== 'BY') {
|
|
181
|
+
throw new Error('期望 GROUP BY');
|
|
182
|
+
}
|
|
183
|
+
this.skipWhitespace();
|
|
184
|
+
statement.groupBy = this.parseColumnList();
|
|
185
|
+
break;
|
|
186
|
+
case 'HAVING':
|
|
187
|
+
this.readWord();
|
|
188
|
+
this.skipWhitespace();
|
|
189
|
+
statement.having = this.parseWhereClause();
|
|
190
|
+
break;
|
|
191
|
+
case 'ORDER':
|
|
192
|
+
this.readWord();
|
|
193
|
+
this.skipWhitespace();
|
|
194
|
+
const orderByWord = this.readWord().toUpperCase();
|
|
195
|
+
if (orderByWord !== 'BY') {
|
|
196
|
+
throw new Error('期望 ORDER BY');
|
|
197
|
+
}
|
|
198
|
+
this.skipWhitespace();
|
|
199
|
+
statement.orderBy = this.parseOrderByList();
|
|
200
|
+
break;
|
|
201
|
+
case 'LIMIT':
|
|
202
|
+
this.readWord();
|
|
203
|
+
this.skipWhitespace();
|
|
204
|
+
statement.limit = this.readNumber();
|
|
205
|
+
break;
|
|
206
|
+
case 'OFFSET':
|
|
207
|
+
this.readWord();
|
|
208
|
+
this.skipWhitespace();
|
|
209
|
+
statement.offset = this.readNumber();
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
// 未知关键字,跳过
|
|
213
|
+
this.readWord();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return statement;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 解析 INSERT 语句
|
|
222
|
+
*/
|
|
223
|
+
private parseInsert(): InsertStatement {
|
|
224
|
+
const statement: InsertStatement = {
|
|
225
|
+
type: 'INSERT',
|
|
226
|
+
table: '',
|
|
227
|
+
columns: [],
|
|
228
|
+
values: []
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// 解析 INTO (可选)
|
|
232
|
+
const intoWord = this.peekWord().toUpperCase();
|
|
233
|
+
if (intoWord === 'INTO') {
|
|
234
|
+
this.readWord();
|
|
235
|
+
this.skipWhitespace();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 解析表名
|
|
239
|
+
statement.table = this.readIdentifier();
|
|
240
|
+
|
|
241
|
+
// 解析列名
|
|
242
|
+
this.skipWhitespace();
|
|
243
|
+
if (this.currentChar === '(') {
|
|
244
|
+
this.readChar();
|
|
245
|
+
this.skipWhitespace();
|
|
246
|
+
statement.columns = this.parseColumnList();
|
|
247
|
+
this.skipWhitespace();
|
|
248
|
+
// 读取闭合括号
|
|
249
|
+
// @ts-ignore - 类型推断问题
|
|
250
|
+
if (this.sql[this.pos] === ')') {
|
|
251
|
+
this.readChar();
|
|
252
|
+
} else {
|
|
253
|
+
throw new Error('期望闭合括号 )');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 解析 VALUES
|
|
258
|
+
this.skipWhitespace();
|
|
259
|
+
const valuesWord = this.readWord().toUpperCase();
|
|
260
|
+
if (valuesWord !== 'VALUES') {
|
|
261
|
+
throw new Error(`期望 VALUES 关键字,得到:${valuesWord}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 解析值列表
|
|
265
|
+
this.skipWhitespace();
|
|
266
|
+
statement.values = this.parseValuesList();
|
|
267
|
+
|
|
268
|
+
return statement;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 解析 UPDATE 语句
|
|
273
|
+
*/
|
|
274
|
+
private parseUpdate(): UpdateStatement {
|
|
275
|
+
const statement: UpdateStatement = {
|
|
276
|
+
type: 'UPDATE',
|
|
277
|
+
table: '',
|
|
278
|
+
set: []
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// 解析表名
|
|
282
|
+
statement.table = this.readIdentifier();
|
|
283
|
+
|
|
284
|
+
// 解析 SET
|
|
285
|
+
this.skipWhitespace();
|
|
286
|
+
const setWord = this.readWord().toUpperCase();
|
|
287
|
+
if (setWord !== 'SET') {
|
|
288
|
+
throw new Error(`期望 SET 关键字,得到:${setWord}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 解析 SET 子句
|
|
292
|
+
this.skipWhitespace();
|
|
293
|
+
statement.set = this.parseSetClause();
|
|
294
|
+
|
|
295
|
+
// 解析可选 WHERE
|
|
296
|
+
this.skipWhitespace();
|
|
297
|
+
if (this.peekWord().toUpperCase() === 'WHERE') {
|
|
298
|
+
this.readWord();
|
|
299
|
+
this.skipWhitespace();
|
|
300
|
+
statement.where = this.parseWhereClause();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return statement;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 解析 DELETE 语句
|
|
308
|
+
*/
|
|
309
|
+
private parseDelete(): DeleteStatement {
|
|
310
|
+
const statement: DeleteStatement = {
|
|
311
|
+
type: 'DELETE',
|
|
312
|
+
table: ''
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// 解析 FROM
|
|
316
|
+
const fromWord = this.peekWord().toUpperCase();
|
|
317
|
+
if (fromWord === 'FROM') {
|
|
318
|
+
this.readWord();
|
|
319
|
+
this.skipWhitespace();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 解析表名
|
|
323
|
+
statement.table = this.readIdentifier();
|
|
324
|
+
|
|
325
|
+
// 解析可选 WHERE
|
|
326
|
+
this.skipWhitespace();
|
|
327
|
+
if (this.peekWord().toUpperCase() === 'WHERE') {
|
|
328
|
+
this.readWord();
|
|
329
|
+
this.skipWhitespace();
|
|
330
|
+
statement.where = this.parseWhereClause();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return statement;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 解析列名列表
|
|
338
|
+
*/
|
|
339
|
+
private parseColumnList(): string[] {
|
|
340
|
+
const columns: string[] = [];
|
|
341
|
+
|
|
342
|
+
// 处理 * 情况
|
|
343
|
+
if (this.currentChar === '*') {
|
|
344
|
+
this.readChar();
|
|
345
|
+
return ['*'];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
columns.push(this.readIdentifier());
|
|
349
|
+
|
|
350
|
+
while (true) {
|
|
351
|
+
this.skipWhitespace();
|
|
352
|
+
if (this.currentChar === ',') {
|
|
353
|
+
this.readChar();
|
|
354
|
+
this.skipWhitespace();
|
|
355
|
+
columns.push(this.readIdentifier());
|
|
356
|
+
} else {
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return columns;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 解析 WHERE 子句
|
|
366
|
+
*/
|
|
367
|
+
private parseWhereClause(): WhereClause {
|
|
368
|
+
const conditions = this.parseCondition();
|
|
369
|
+
return conditions;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* 解析条件(支持 AND/OR)
|
|
374
|
+
*/
|
|
375
|
+
private parseCondition(): WhereClause {
|
|
376
|
+
const left = this.parseSimpleCondition();
|
|
377
|
+
|
|
378
|
+
this.skipWhitespace();
|
|
379
|
+
const keyword = this.peekWord().toUpperCase();
|
|
380
|
+
|
|
381
|
+
if (keyword === 'AND') {
|
|
382
|
+
this.readWord();
|
|
383
|
+
this.skipWhitespace();
|
|
384
|
+
const right = this.parseCondition();
|
|
385
|
+
return {
|
|
386
|
+
type: 'and',
|
|
387
|
+
conditions: [left, right]
|
|
388
|
+
};
|
|
389
|
+
} else if (keyword === 'OR') {
|
|
390
|
+
this.readWord();
|
|
391
|
+
this.skipWhitespace();
|
|
392
|
+
const right = this.parseCondition();
|
|
393
|
+
return {
|
|
394
|
+
type: 'or',
|
|
395
|
+
conditions: [left, right]
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return left;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* 解析简单条件
|
|
404
|
+
*/
|
|
405
|
+
private parseSimpleCondition(): WhereClause {
|
|
406
|
+
// 处理括号
|
|
407
|
+
this.skipWhitespace();
|
|
408
|
+
if (this.currentChar === '(') {
|
|
409
|
+
this.readChar();
|
|
410
|
+
const condition = this.parseCondition();
|
|
411
|
+
this.skipWhitespace();
|
|
412
|
+
// @ts-ignore - 类型推断问题
|
|
413
|
+
if (this.sql[this.pos] === ')') {
|
|
414
|
+
this.readChar();
|
|
415
|
+
}
|
|
416
|
+
return condition;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// 处理 NOT
|
|
420
|
+
if (this.peekWord().toUpperCase() === 'NOT') {
|
|
421
|
+
this.readWord();
|
|
422
|
+
this.skipWhitespace();
|
|
423
|
+
const condition = this.parseSimpleCondition();
|
|
424
|
+
return { type: 'not', condition };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const column = this.readIdentifier();
|
|
428
|
+
this.skipWhitespace();
|
|
429
|
+
|
|
430
|
+
// 解析操作符
|
|
431
|
+
let operator = this.readOperator();
|
|
432
|
+
this.skipWhitespace();
|
|
433
|
+
|
|
434
|
+
// 解析值
|
|
435
|
+
const value = this.readValue();
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
type: 'condition',
|
|
439
|
+
column,
|
|
440
|
+
operator,
|
|
441
|
+
value
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* 解析操作符
|
|
447
|
+
*/
|
|
448
|
+
private readOperator(): string {
|
|
449
|
+
this.skipWhitespace();
|
|
450
|
+
let op = '';
|
|
451
|
+
|
|
452
|
+
// 双字符操作符
|
|
453
|
+
if (this.currentChar === '=' && this.peekChar(1) !== '=') {
|
|
454
|
+
this.readChar();
|
|
455
|
+
return '=';
|
|
456
|
+
}
|
|
457
|
+
if (this.currentChar === '<' && this.peekChar(1) === '=') {
|
|
458
|
+
this.readChar();
|
|
459
|
+
this.readChar();
|
|
460
|
+
return '<=';
|
|
461
|
+
}
|
|
462
|
+
if (this.currentChar === '>' && this.peekChar(1) === '=') {
|
|
463
|
+
this.readChar();
|
|
464
|
+
this.readChar();
|
|
465
|
+
return '>=';
|
|
466
|
+
}
|
|
467
|
+
if (this.currentChar === '!' && this.peekChar(1) === '=') {
|
|
468
|
+
this.readChar();
|
|
469
|
+
this.readChar();
|
|
470
|
+
return '!=';
|
|
471
|
+
}
|
|
472
|
+
if (this.currentChar === '<' && this.peekChar(1) === '>') {
|
|
473
|
+
this.readChar();
|
|
474
|
+
this.readChar();
|
|
475
|
+
return '<>';
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// 单字符操作符
|
|
479
|
+
if (['=', '<', '>', '!'].includes(this.currentChar)) {
|
|
480
|
+
op = this.currentChar;
|
|
481
|
+
this.readChar();
|
|
482
|
+
return op;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 单词操作符 (LIKE, IN, IS, BETWEEN)
|
|
486
|
+
// 先检查是否是字母开头,避免遇到引号
|
|
487
|
+
if (/[a-zA-Z]/.test(this.currentChar)) {
|
|
488
|
+
const word = this.readWord().toUpperCase();
|
|
489
|
+
if (['LIKE', 'IN', 'IS', 'BETWEEN'].includes(word)) {
|
|
490
|
+
return word;
|
|
491
|
+
}
|
|
492
|
+
// 如果不是已知操作符,可能是值的一部分,回退
|
|
493
|
+
throw new Error(`未知操作符:${word}`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
throw new Error(`未知操作符:${this.currentChar}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 解析值
|
|
501
|
+
*/
|
|
502
|
+
private readValue(): unknown {
|
|
503
|
+
this.skipWhitespace();
|
|
504
|
+
|
|
505
|
+
// NULL
|
|
506
|
+
if (this.peekWord().toUpperCase() === 'NULL') {
|
|
507
|
+
this.readWord();
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// TRUE
|
|
512
|
+
if (this.peekWord().toUpperCase() === 'TRUE') {
|
|
513
|
+
this.readWord();
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// FALSE
|
|
518
|
+
if (this.peekWord().toUpperCase() === 'FALSE') {
|
|
519
|
+
this.readWord();
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// 数组 (value1, value2, ...)
|
|
524
|
+
if (this.currentChar === '(') {
|
|
525
|
+
this.readChar();
|
|
526
|
+
this.skipWhitespace();
|
|
527
|
+
const values: unknown[] = [];
|
|
528
|
+
|
|
529
|
+
// @ts-ignore - 类型推断问题
|
|
530
|
+
while (this.sql[this.pos] !== ')') {
|
|
531
|
+
values.push(this.readValue());
|
|
532
|
+
this.skipWhitespace();
|
|
533
|
+
// @ts-ignore - 类型推断问题
|
|
534
|
+
if (this.sql[this.pos] === ',') {
|
|
535
|
+
this.readChar();
|
|
536
|
+
this.skipWhitespace();
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// @ts-ignore - 类型推断问题
|
|
541
|
+
if (this.sql[this.pos] === ')') {
|
|
542
|
+
this.readChar();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return values;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 字符串
|
|
549
|
+
if (this.currentChar === "'" || this.currentChar === '"') {
|
|
550
|
+
return this.readString();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// 数字
|
|
554
|
+
if (this.isDigit(this.currentChar) || (this.currentChar === '-' && this.isDigit(this.peekChar(1)))) {
|
|
555
|
+
return this.readNumber();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 标识符
|
|
559
|
+
return this.readIdentifier();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 解析 SET 子句
|
|
564
|
+
*/
|
|
565
|
+
private parseSetClause(): { column: string; value: unknown }[] {
|
|
566
|
+
const set: { column: string; value: unknown }[] = [];
|
|
567
|
+
|
|
568
|
+
set.push({
|
|
569
|
+
column: this.readIdentifier(),
|
|
570
|
+
value: this.parseSetValue()
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
while (true) {
|
|
574
|
+
this.skipWhitespace();
|
|
575
|
+
if (this.currentChar === ',') {
|
|
576
|
+
this.readChar();
|
|
577
|
+
this.skipWhitespace();
|
|
578
|
+
set.push({
|
|
579
|
+
column: this.readIdentifier(),
|
|
580
|
+
value: this.parseSetValue()
|
|
581
|
+
});
|
|
582
|
+
} else {
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return set;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* 解析 SET 值
|
|
592
|
+
*/
|
|
593
|
+
private parseSetValue(): unknown {
|
|
594
|
+
this.skipWhitespace();
|
|
595
|
+
if (this.currentChar === '=') {
|
|
596
|
+
this.readChar();
|
|
597
|
+
}
|
|
598
|
+
this.skipWhitespace();
|
|
599
|
+
return this.readValue();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* 解析值列表
|
|
604
|
+
*/
|
|
605
|
+
private parseValuesList(): unknown[][] {
|
|
606
|
+
const values: unknown[][] = [];
|
|
607
|
+
|
|
608
|
+
values.push(this.parseValueRow());
|
|
609
|
+
|
|
610
|
+
while (true) {
|
|
611
|
+
this.skipWhitespace();
|
|
612
|
+
if (this.currentChar === ',') {
|
|
613
|
+
this.readChar();
|
|
614
|
+
this.skipWhitespace();
|
|
615
|
+
values.push(this.parseValueRow());
|
|
616
|
+
} else {
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return values;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* 解析单行值
|
|
626
|
+
*/
|
|
627
|
+
private parseValueRow(): unknown[] {
|
|
628
|
+
const row: unknown[] = [];
|
|
629
|
+
|
|
630
|
+
this.skipWhitespace();
|
|
631
|
+
if (this.currentChar === '(') {
|
|
632
|
+
this.readChar();
|
|
633
|
+
this.skipWhitespace();
|
|
634
|
+
row.push(this.readValue());
|
|
635
|
+
|
|
636
|
+
while (true) {
|
|
637
|
+
this.skipWhitespace();
|
|
638
|
+
// @ts-ignore - 类型推断问题
|
|
639
|
+
if (this.sql[this.pos] === ',') {
|
|
640
|
+
this.readChar();
|
|
641
|
+
this.skipWhitespace();
|
|
642
|
+
row.push(this.readValue());
|
|
643
|
+
} else {
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
this.skipWhitespace();
|
|
649
|
+
// @ts-ignore - 类型推断问题
|
|
650
|
+
if (this.sql[this.pos] === ')') {
|
|
651
|
+
this.readChar();
|
|
652
|
+
} else {
|
|
653
|
+
throw new Error('期望闭合括号 )');
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return row;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* 解析 ORDER BY 列表
|
|
662
|
+
*/
|
|
663
|
+
private parseOrderByList(): OrderByClause[] {
|
|
664
|
+
const orderBy: OrderByClause[] = [];
|
|
665
|
+
|
|
666
|
+
orderBy.push(this.parseOrderByItem());
|
|
667
|
+
|
|
668
|
+
while (true) {
|
|
669
|
+
this.skipWhitespace();
|
|
670
|
+
if (this.currentChar === ',') {
|
|
671
|
+
this.readChar();
|
|
672
|
+
this.skipWhitespace();
|
|
673
|
+
orderBy.push(this.parseOrderByItem());
|
|
674
|
+
} else {
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return orderBy;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* 解析单个 ORDER BY
|
|
684
|
+
*/
|
|
685
|
+
private parseOrderByItem(): OrderByClause {
|
|
686
|
+
const column = this.readIdentifier();
|
|
687
|
+
this.skipWhitespace();
|
|
688
|
+
|
|
689
|
+
let direction: 'ASC' | 'DESC' = 'ASC';
|
|
690
|
+
const dirWord = this.peekWord().toUpperCase();
|
|
691
|
+
if (dirWord === 'ASC' || dirWord === 'DESC') {
|
|
692
|
+
this.readWord();
|
|
693
|
+
direction = dirWord as 'ASC' | 'DESC';
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return { column, direction };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// ========== 词法分析辅助方法 ==========
|
|
700
|
+
|
|
701
|
+
private skipWhitespace(): void {
|
|
702
|
+
while (this.isWhitespace(this.currentChar)) {
|
|
703
|
+
this.pos++;
|
|
704
|
+
this.currentChar = this.sql[this.pos] || '';
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
private readChar(): string {
|
|
709
|
+
const char = this.currentChar;
|
|
710
|
+
this.pos++;
|
|
711
|
+
this.currentChar = this.sql[this.pos] || '';
|
|
712
|
+
return char;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
private peekChar(offset: number = 0): string {
|
|
716
|
+
return this.sql[this.pos + offset] || '';
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private readWord(): string {
|
|
720
|
+
let word = '';
|
|
721
|
+
while (this.isWordChar(this.currentChar)) {
|
|
722
|
+
word += this.readChar();
|
|
723
|
+
}
|
|
724
|
+
return word;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private peekWord(): string {
|
|
728
|
+
const savedPos = this.pos;
|
|
729
|
+
const word = this.readWord();
|
|
730
|
+
this.pos = savedPos;
|
|
731
|
+
this.currentChar = this.sql[this.pos] || '';
|
|
732
|
+
return word;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private readIdentifier(): string {
|
|
736
|
+
this.skipWhitespace();
|
|
737
|
+
|
|
738
|
+
// 处理引号标识符
|
|
739
|
+
if (this.currentChar === '`' || this.currentChar === '"') {
|
|
740
|
+
const quote = this.readChar();
|
|
741
|
+
let identifier = '';
|
|
742
|
+
while (this.currentChar !== quote && this.pos < this.sql.length) {
|
|
743
|
+
identifier += this.readChar();
|
|
744
|
+
}
|
|
745
|
+
if (this.currentChar === quote) {
|
|
746
|
+
this.readChar();
|
|
747
|
+
}
|
|
748
|
+
return identifier;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return this.readWord();
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private readString(): string {
|
|
755
|
+
const quote = this.readChar();
|
|
756
|
+
let str = '';
|
|
757
|
+
while (this.currentChar !== quote && this.pos < this.sql.length) {
|
|
758
|
+
if (this.currentChar === '\\' && this.peekChar(1) === quote) {
|
|
759
|
+
this.readChar();
|
|
760
|
+
str += this.readChar();
|
|
761
|
+
} else {
|
|
762
|
+
str += this.readChar();
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (this.currentChar === quote) {
|
|
766
|
+
this.readChar();
|
|
767
|
+
}
|
|
768
|
+
return str;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
private readNumber(): number {
|
|
772
|
+
let numStr = '';
|
|
773
|
+
|
|
774
|
+
// 处理负数
|
|
775
|
+
if (this.currentChar === '-') {
|
|
776
|
+
numStr += this.readChar();
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// 整数部分
|
|
780
|
+
while (this.isDigit(this.currentChar)) {
|
|
781
|
+
numStr += this.readChar();
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// 小数部分
|
|
785
|
+
if (this.currentChar === '.' && this.isDigit(this.peekChar(1))) {
|
|
786
|
+
numStr += this.readChar();
|
|
787
|
+
while (this.isDigit(this.currentChar)) {
|
|
788
|
+
numStr += this.readChar();
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return parseFloat(numStr);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
private isWhitespace(char: string): boolean {
|
|
796
|
+
return char === ' ' || char === '\t' || char === '\n' || char === '\r';
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
private isWordChar(char: string): boolean {
|
|
800
|
+
return /[a-zA-Z0-9_]/.test(char);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
private isDigit(char: string): boolean {
|
|
804
|
+
return /[0-9]/.test(char);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* 解析 SQL 语句
|
|
810
|
+
*/
|
|
811
|
+
export function parseSQL(sql: string): SQLParseResult {
|
|
812
|
+
const parser = new SQLParser();
|
|
813
|
+
return parser.parse(sql);
|
|
814
|
+
}
|