@zhin.js/database 1.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/LICENSE +21 -0
- package/README.md +93 -0
- package/lib/base/database.d.ts +71 -0
- package/lib/base/database.d.ts.map +1 -0
- package/lib/base/database.js +101 -0
- package/lib/base/database.js.map +1 -0
- package/lib/base/dialect.d.ts +34 -0
- package/lib/base/dialect.d.ts.map +1 -0
- package/lib/base/dialect.js +21 -0
- package/lib/base/dialect.js.map +1 -0
- package/lib/base/index.d.ts +6 -0
- package/lib/base/index.d.ts.map +1 -0
- package/lib/base/index.js +6 -0
- package/lib/base/index.js.map +1 -0
- package/lib/base/model.d.ts +53 -0
- package/lib/base/model.d.ts.map +1 -0
- package/lib/base/model.js +65 -0
- package/lib/base/model.js.map +1 -0
- package/lib/base/query-classes.d.ts +68 -0
- package/lib/base/query-classes.d.ts.map +1 -0
- package/lib/base/query-classes.js +178 -0
- package/lib/base/query-classes.js.map +1 -0
- package/lib/base/thenable.d.ts +15 -0
- package/lib/base/thenable.d.ts.map +1 -0
- package/lib/base/thenable.js +33 -0
- package/lib/base/thenable.js.map +1 -0
- package/lib/dialects/memory.d.ts +52 -0
- package/lib/dialects/memory.d.ts.map +1 -0
- package/lib/dialects/memory.js +772 -0
- package/lib/dialects/memory.js.map +1 -0
- package/lib/dialects/mongodb.d.ts +85 -0
- package/lib/dialects/mongodb.d.ts.map +1 -0
- package/lib/dialects/mongodb.js +461 -0
- package/lib/dialects/mongodb.js.map +1 -0
- package/lib/dialects/mysql.d.ts +33 -0
- package/lib/dialects/mysql.d.ts.map +1 -0
- package/lib/dialects/mysql.js +132 -0
- package/lib/dialects/mysql.js.map +1 -0
- package/lib/dialects/pg.d.ts +33 -0
- package/lib/dialects/pg.d.ts.map +1 -0
- package/lib/dialects/pg.js +132 -0
- package/lib/dialects/pg.js.map +1 -0
- package/lib/dialects/redis.d.ts +87 -0
- package/lib/dialects/redis.d.ts.map +1 -0
- package/lib/dialects/redis.js +500 -0
- package/lib/dialects/redis.js.map +1 -0
- package/lib/dialects/sqlite.d.ts +46 -0
- package/lib/dialects/sqlite.d.ts.map +1 -0
- package/lib/dialects/sqlite.js +201 -0
- package/lib/dialects/sqlite.js.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +18 -0
- package/lib/index.js.map +1 -0
- package/lib/registry.d.ts +37 -0
- package/lib/registry.d.ts.map +1 -0
- package/lib/registry.js +21 -0
- package/lib/registry.js.map +1 -0
- package/lib/type/document/database.d.ts +60 -0
- package/lib/type/document/database.d.ts.map +1 -0
- package/lib/type/document/database.js +242 -0
- package/lib/type/document/database.js.map +1 -0
- package/lib/type/document/model.d.ts +42 -0
- package/lib/type/document/model.d.ts.map +1 -0
- package/lib/type/document/model.js +65 -0
- package/lib/type/document/model.js.map +1 -0
- package/lib/type/keyvalue/database.d.ts +64 -0
- package/lib/type/keyvalue/database.d.ts.map +1 -0
- package/lib/type/keyvalue/database.js +214 -0
- package/lib/type/keyvalue/database.js.map +1 -0
- package/lib/type/keyvalue/model.d.ts +107 -0
- package/lib/type/keyvalue/model.d.ts.map +1 -0
- package/lib/type/keyvalue/model.js +261 -0
- package/lib/type/keyvalue/model.js.map +1 -0
- package/lib/type/related/database.d.ts +32 -0
- package/lib/type/related/database.d.ts.map +1 -0
- package/lib/type/related/database.js +334 -0
- package/lib/type/related/database.js.map +1 -0
- package/lib/type/related/model.d.ts +43 -0
- package/lib/type/related/model.d.ts.map +1 -0
- package/lib/type/related/model.js +108 -0
- package/lib/type/related/model.js.map +1 -0
- package/lib/types.d.ts +251 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +28 -0
- package/lib/types.js.map +1 -0
- package/package.json +54 -0
- package/src/base/database.ts +128 -0
- package/src/base/dialect.ts +76 -0
- package/src/base/index.ts +5 -0
- package/src/base/model.ts +89 -0
- package/src/base/query-classes.ts +217 -0
- package/src/base/thenable.ts +54 -0
- package/src/dialects/memory.ts +880 -0
- package/src/dialects/mongodb.ts +533 -0
- package/src/dialects/mysql.ts +157 -0
- package/src/dialects/pg.ts +157 -0
- package/src/dialects/redis.ts +598 -0
- package/src/dialects/sqlite.ts +233 -0
- package/src/index.ts +20 -0
- package/src/registry.ts +59 -0
- package/src/type/document/database.ts +283 -0
- package/src/type/document/model.ts +86 -0
- package/src/type/keyvalue/database.ts +261 -0
- package/src/type/keyvalue/model.ts +339 -0
- package/src/type/related/database.ts +392 -0
- package/src/type/related/model.ts +117 -0
- package/src/types.ts +403 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import {Database, Dialect} from '../base';
|
|
2
|
+
import {
|
|
3
|
+
AlterQueryParams,
|
|
4
|
+
BuildQueryResult,
|
|
5
|
+
CreateQueryParams, DatabaseDialect, DeleteQueryParams,
|
|
6
|
+
DocumentQueryResult, DropIndexQueryParams, DropTableQueryParams, InsertQueryParams,
|
|
7
|
+
QueryParams,
|
|
8
|
+
SelectQueryParams, UpdateQueryParams
|
|
9
|
+
} from "../types";
|
|
10
|
+
import {DocumentDatabase} from "../type/document/database";
|
|
11
|
+
import {Registry} from "../registry";
|
|
12
|
+
import type { MongoClientOptions } from 'mongodb';
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export interface MongoDBDialectConfig extends MongoClientOptions {
|
|
16
|
+
url: string;
|
|
17
|
+
dbName: string;
|
|
18
|
+
}
|
|
19
|
+
export class MongoDBDialect extends Dialect<MongoDBDialectConfig, DocumentQueryResult> {
|
|
20
|
+
private client: any = null;
|
|
21
|
+
private db: any = null;
|
|
22
|
+
|
|
23
|
+
constructor(config: MongoDBDialectConfig) {
|
|
24
|
+
super('mongodb', config);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 检查是否已连接
|
|
29
|
+
*/
|
|
30
|
+
isConnected(): boolean {
|
|
31
|
+
return this.client !== null && this.db !== null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 连接数据库
|
|
36
|
+
*/
|
|
37
|
+
async connect(): Promise<void> {
|
|
38
|
+
return this.start();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 断开连接
|
|
43
|
+
*/
|
|
44
|
+
async disconnect(): Promise<void> {
|
|
45
|
+
return this.stop();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 健康检查
|
|
50
|
+
*/
|
|
51
|
+
async healthCheck(): Promise<boolean> {
|
|
52
|
+
if (!this.isConnected()) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await this.db.admin().ping();
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 启动连接
|
|
65
|
+
*/
|
|
66
|
+
async start(): Promise<void> {
|
|
67
|
+
try {
|
|
68
|
+
// 动态导入 mongodb 客户端
|
|
69
|
+
const { MongoClient } = await import('mongodb');
|
|
70
|
+
|
|
71
|
+
this.client = new MongoClient(this.config.url, this.config);
|
|
72
|
+
await this.client.connect();
|
|
73
|
+
this.db = this.client.db(this.config.dbName);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('forgot install mongodb ?');
|
|
76
|
+
throw new Error(`MongoDB 连接失败: ${error}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 停止连接
|
|
82
|
+
*/
|
|
83
|
+
async stop(): Promise<void> {
|
|
84
|
+
if (this.client) {
|
|
85
|
+
await this.client.close();
|
|
86
|
+
this.client = null;
|
|
87
|
+
this.db = null;
|
|
88
|
+
console.log('MongoDB 连接已关闭');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 释放资源
|
|
94
|
+
*/
|
|
95
|
+
async dispose(): Promise<void> {
|
|
96
|
+
return this.stop();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 执行查询
|
|
101
|
+
*/
|
|
102
|
+
async query<T = any>(query: DocumentQueryResult, params: any[] = []): Promise<T> {
|
|
103
|
+
if (!this.db) {
|
|
104
|
+
throw new Error('MongoDB 未连接');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const collection = this.db.collection(query.collection);
|
|
109
|
+
|
|
110
|
+
switch (query.operation || 'find') {
|
|
111
|
+
case 'find':
|
|
112
|
+
return await this.executeFind(collection, query) as T;
|
|
113
|
+
case 'insertOne':
|
|
114
|
+
return await this.executeInsertOne(collection, query, params) as T;
|
|
115
|
+
case 'insertMany':
|
|
116
|
+
return await this.executeInsertMany(collection, query, params) as T;
|
|
117
|
+
case 'updateOne':
|
|
118
|
+
return await this.executeUpdateOne(collection, query, params) as T;
|
|
119
|
+
case 'updateMany':
|
|
120
|
+
return await this.executeUpdateMany(collection, query, params) as T;
|
|
121
|
+
case 'deleteOne':
|
|
122
|
+
return await this.executeDeleteOne(collection, query) as T;
|
|
123
|
+
case 'deleteMany':
|
|
124
|
+
return await this.executeDeleteMany(collection, query) as T;
|
|
125
|
+
case 'createIndex':
|
|
126
|
+
return await this.executeCreateIndex(collection, query, params) as T;
|
|
127
|
+
case 'dropIndex':
|
|
128
|
+
return await this.executeDropIndex(collection, query, params) as T;
|
|
129
|
+
case 'dropCollection':
|
|
130
|
+
return await this.executeDropCollection(collection) as T;
|
|
131
|
+
default:
|
|
132
|
+
throw new Error(`不支持的 MongoDB 操作: ${query.operation}`);
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new Error(`MongoDB 查询执行失败: ${error}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 构建查询
|
|
141
|
+
*/
|
|
142
|
+
buildQuery<U extends object = any>(params: QueryParams<U>): BuildQueryResult<DocumentQueryResult> {
|
|
143
|
+
switch (params.type) {
|
|
144
|
+
case 'create':
|
|
145
|
+
return this.buildCreateQuery(params as CreateQueryParams<U>);
|
|
146
|
+
case 'select':
|
|
147
|
+
return this.buildSelectQuery(params as SelectQueryParams<U>);
|
|
148
|
+
case 'insert':
|
|
149
|
+
return this.buildInsertQuery(params as InsertQueryParams<U>);
|
|
150
|
+
case 'update':
|
|
151
|
+
return this.buildUpdateQuery(params as UpdateQueryParams<U>);
|
|
152
|
+
case 'delete':
|
|
153
|
+
return this.buildDeleteQuery(params as DeleteQueryParams<U>);
|
|
154
|
+
case 'alter':
|
|
155
|
+
return this.buildAlterQuery(params as AlterQueryParams<U>);
|
|
156
|
+
case 'drop_table':
|
|
157
|
+
return this.buildDropTableQuery(params as DropTableQueryParams<U>);
|
|
158
|
+
case 'drop_index':
|
|
159
|
+
return this.buildDropIndexQuery(params as DropIndexQueryParams);
|
|
160
|
+
default:
|
|
161
|
+
throw new Error(`不支持的查询类型: ${(params as any).type}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 实现 Dialect 接口的所有必需方法
|
|
166
|
+
mapColumnType(type: string): string {
|
|
167
|
+
switch (type) {
|
|
168
|
+
case 'string':
|
|
169
|
+
return 'String';
|
|
170
|
+
case 'integer':
|
|
171
|
+
return 'Int32';
|
|
172
|
+
case 'float':
|
|
173
|
+
return 'Double';
|
|
174
|
+
case 'boolean':
|
|
175
|
+
return 'Boolean';
|
|
176
|
+
case 'date':
|
|
177
|
+
return 'Date';
|
|
178
|
+
case 'json':
|
|
179
|
+
return 'Object';
|
|
180
|
+
default:
|
|
181
|
+
return type;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
formatBoolean(value: boolean): string {
|
|
186
|
+
return value ? 'true' : 'false';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
formatDate(value: Date): string {
|
|
190
|
+
return value.toISOString();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
formatJson(value: any): string {
|
|
194
|
+
return JSON.stringify(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
escapeString(value: string): string {
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
formatDefaultValue(value: any): string {
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
formatLimit(limit: number): string {
|
|
206
|
+
return `${limit}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
formatOffset(offset: number): string {
|
|
210
|
+
return `${offset}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
formatLimitOffset(limit: number, offset: number): string {
|
|
214
|
+
return `${limit},${offset}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
formatCreateTable(tableName: string, columns: string[]): string {
|
|
218
|
+
return `CREATE TABLE ${tableName} (${columns.join(',')})`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
formatAlterTable(tableName: string, alterations: string[]): string {
|
|
222
|
+
return `ALTER TABLE ${tableName} ${alterations.join(',')}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
formatDropTable(tableName: string, ifExists?: boolean): string {
|
|
226
|
+
return `DROP TABLE ${tableName} ${ifExists ? 'IF EXISTS' : ''}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
formatDropIndex(indexName: string, tableName: string, ifExists?: boolean): string {
|
|
230
|
+
return `DROP INDEX ${indexName} ON ${tableName} ${ifExists ? 'IF EXISTS' : ''}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
quoteIdentifier(identifier: string): string {
|
|
234
|
+
return identifier;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
getParameterPlaceholder(index: number): string {
|
|
238
|
+
return `$${index}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getStatementTerminator(): string {
|
|
242
|
+
return ';';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// MongoDB 特定的查询构建方法
|
|
246
|
+
private buildCreateQuery<T extends object>(params: CreateQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
247
|
+
return {
|
|
248
|
+
query: {
|
|
249
|
+
collection: params.tableName,
|
|
250
|
+
operation: 'createCollection',
|
|
251
|
+
filter: {},
|
|
252
|
+
projection: {}
|
|
253
|
+
},
|
|
254
|
+
params: []
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private buildSelectQuery<T extends object>(params: SelectQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
259
|
+
const filter: Record<string, any> = {};
|
|
260
|
+
|
|
261
|
+
// 转换条件为 MongoDB 查询格式
|
|
262
|
+
if (params.conditions) {
|
|
263
|
+
this.convertConditionToMongoFilter(params.conditions, filter);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const query: DocumentQueryResult = {
|
|
267
|
+
collection: params.tableName,
|
|
268
|
+
operation: 'find',
|
|
269
|
+
filter
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
if (params.orderings) {
|
|
273
|
+
query.sort = {};
|
|
274
|
+
for (const order of params.orderings) {
|
|
275
|
+
query.sort[order.field as string] = order.direction === 'ASC' ? 1 : -1;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (params.limitCount) {
|
|
280
|
+
query.limit = params.limitCount;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (params.offsetCount) {
|
|
284
|
+
query.skip = params.offsetCount;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (params.fields && params.fields.length > 0) {
|
|
288
|
+
query.projection = {};
|
|
289
|
+
for (const field of params.fields) {
|
|
290
|
+
query.projection[field as string] = 1;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
query,
|
|
296
|
+
params: []
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private buildInsertQuery<T extends object>(params: InsertQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
301
|
+
return {
|
|
302
|
+
query: {
|
|
303
|
+
collection: params.tableName,
|
|
304
|
+
operation: 'insertOne',
|
|
305
|
+
filter: {},
|
|
306
|
+
projection: {}
|
|
307
|
+
},
|
|
308
|
+
params: [params.data]
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private buildUpdateQuery<T extends object>(params: UpdateQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
313
|
+
const filter: Record<string, any> = {};
|
|
314
|
+
|
|
315
|
+
if (params.conditions) {
|
|
316
|
+
this.convertConditionToMongoFilter(params.conditions, filter);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
query: {
|
|
321
|
+
collection: params.tableName,
|
|
322
|
+
operation: 'updateMany',
|
|
323
|
+
filter,
|
|
324
|
+
projection: {}
|
|
325
|
+
},
|
|
326
|
+
params: [params.update]
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private buildDeleteQuery<T extends object>(params: DeleteQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
331
|
+
const filter: Record<string, any> = {};
|
|
332
|
+
|
|
333
|
+
if (params.conditions) {
|
|
334
|
+
this.convertConditionToMongoFilter(params.conditions, filter);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
query: {
|
|
339
|
+
collection: params.tableName,
|
|
340
|
+
operation: 'deleteMany',
|
|
341
|
+
filter,
|
|
342
|
+
projection: {}
|
|
343
|
+
},
|
|
344
|
+
params: []
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private buildAlterQuery<T extends object>(params: AlterQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
349
|
+
return {
|
|
350
|
+
query: {
|
|
351
|
+
collection: params.tableName,
|
|
352
|
+
operation: 'createIndex',
|
|
353
|
+
filter: {},
|
|
354
|
+
projection: {}
|
|
355
|
+
},
|
|
356
|
+
params: [params.alterations]
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private buildDropTableQuery<T extends object>(params: DropTableQueryParams<T>): BuildQueryResult<DocumentQueryResult> {
|
|
361
|
+
return {
|
|
362
|
+
query: {
|
|
363
|
+
collection: params.tableName,
|
|
364
|
+
operation: 'dropCollection',
|
|
365
|
+
filter: {},
|
|
366
|
+
projection: {}
|
|
367
|
+
},
|
|
368
|
+
params: []
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private buildDropIndexQuery(params: DropIndexQueryParams): BuildQueryResult<DocumentQueryResult> {
|
|
373
|
+
return {
|
|
374
|
+
query: {
|
|
375
|
+
collection: params.tableName,
|
|
376
|
+
operation: 'dropIndex',
|
|
377
|
+
filter: {},
|
|
378
|
+
projection: {}
|
|
379
|
+
},
|
|
380
|
+
params: [params.indexName]
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private convertConditionToMongoFilter(condition: any, filter: Record<string, any>): void {
|
|
385
|
+
if (typeof condition !== 'object' || condition === null) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
for (const [key, value] of Object.entries(condition)) {
|
|
390
|
+
if (key.startsWith('$')) {
|
|
391
|
+
// 逻辑操作符
|
|
392
|
+
if (key === '$and' && Array.isArray(value)) {
|
|
393
|
+
filter.$and = value.map(cond => {
|
|
394
|
+
const subFilter: Record<string, any> = {};
|
|
395
|
+
this.convertConditionToMongoFilter(cond, subFilter);
|
|
396
|
+
return subFilter;
|
|
397
|
+
});
|
|
398
|
+
} else if (key === '$or' && Array.isArray(value)) {
|
|
399
|
+
filter.$or = value.map(cond => {
|
|
400
|
+
const subFilter: Record<string, any> = {};
|
|
401
|
+
this.convertConditionToMongoFilter(cond, subFilter);
|
|
402
|
+
return subFilter;
|
|
403
|
+
});
|
|
404
|
+
} else if (key === '$not') {
|
|
405
|
+
const subFilter: Record<string, any> = {};
|
|
406
|
+
this.convertConditionToMongoFilter(value, subFilter);
|
|
407
|
+
filter.$not = subFilter;
|
|
408
|
+
} else {
|
|
409
|
+
filter[key] = value;
|
|
410
|
+
}
|
|
411
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
412
|
+
// 比较操作符
|
|
413
|
+
const fieldFilter: Record<string, any> = {};
|
|
414
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
415
|
+
if (op.startsWith('$')) {
|
|
416
|
+
fieldFilter[op] = opValue;
|
|
417
|
+
} else {
|
|
418
|
+
fieldFilter.$eq = value;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
filter[key] = Object.keys(fieldFilter).length > 0 ? fieldFilter : value;
|
|
422
|
+
} else {
|
|
423
|
+
// 简单相等
|
|
424
|
+
filter[key] = value;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// MongoDB 执行方法
|
|
430
|
+
private async executeFind(collection: any, query: DocumentQueryResult): Promise<any[]> {
|
|
431
|
+
let cursor = collection.find(query.filter);
|
|
432
|
+
|
|
433
|
+
if (query.sort) {
|
|
434
|
+
cursor = cursor.sort(query.sort);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (query.skip) {
|
|
438
|
+
cursor = cursor.skip(query.skip);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (query.limit) {
|
|
442
|
+
cursor = cursor.limit(query.limit);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (query.projection) {
|
|
446
|
+
cursor = cursor.projection(query.projection);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return await cursor.toArray();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private async executeInsertOne(collection: any, query: DocumentQueryResult, params: any[]): Promise<any[]> {
|
|
453
|
+
const result = await collection.insertOne(params[0]);
|
|
454
|
+
return [{ insertedId: result.insertedId, ...params[0] }];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private async executeInsertMany(collection: any, query: DocumentQueryResult, params: any[]): Promise<any[]> {
|
|
458
|
+
const result = await collection.insertMany(params);
|
|
459
|
+
return Object.values(result.insertedIds).map((id, index) => ({
|
|
460
|
+
insertedId: id,
|
|
461
|
+
...params[index]
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private async executeUpdateOne(collection: any, query: DocumentQueryResult, params: any[]): Promise<any[]> {
|
|
466
|
+
const result = await collection.updateOne(query.filter, { $set: params[0] });
|
|
467
|
+
return [{ modifiedCount: result.modifiedCount, matchedCount: result.matchedCount }];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private async executeUpdateMany(collection: any, query: DocumentQueryResult, params: any[]): Promise<any[]> {
|
|
471
|
+
const result = await collection.updateMany(query.filter, { $set: params[0] });
|
|
472
|
+
return [{ modifiedCount: result.modifiedCount, matchedCount: result.matchedCount }];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private async executeDeleteOne(collection: any, query: DocumentQueryResult): Promise<any[]> {
|
|
476
|
+
const result = await collection.deleteOne(query.filter);
|
|
477
|
+
return [{ deletedCount: result.deletedCount }];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private async executeDeleteMany(collection: any, query: DocumentQueryResult): Promise<any[]> {
|
|
481
|
+
const result = await collection.deleteMany(query.filter);
|
|
482
|
+
return [{ deletedCount: result.deletedCount }];
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private async executeCreateIndex(collection: any, query: DocumentQueryResult, params: any[]): Promise<any[]> {
|
|
486
|
+
const result = await collection.createIndex(params[0]);
|
|
487
|
+
return [{ indexName: result }];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private async executeDropIndex(collection: any, query: DocumentQueryResult, params: any[]): Promise<any[]> {
|
|
491
|
+
const result = await collection.dropIndex(params[0]);
|
|
492
|
+
return [{ result }];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private async executeDropCollection(collection: any): Promise<any[]> {
|
|
496
|
+
const result = await collection.drop();
|
|
497
|
+
return [{ result }];
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
get dialectInfo(): DatabaseDialect {
|
|
501
|
+
return {
|
|
502
|
+
name: this.name,
|
|
503
|
+
version: '1.0.0',
|
|
504
|
+
features: [
|
|
505
|
+
'document_storage',
|
|
506
|
+
'indexing',
|
|
507
|
+
'aggregation',
|
|
508
|
+
'transactions',
|
|
509
|
+
'replica_sets',
|
|
510
|
+
'sharding'
|
|
511
|
+
],
|
|
512
|
+
dataTypes: {
|
|
513
|
+
'string': 'String',
|
|
514
|
+
'integer': 'Int32',
|
|
515
|
+
'float': 'Double',
|
|
516
|
+
'boolean': 'Boolean',
|
|
517
|
+
'date': 'Date',
|
|
518
|
+
'json': 'Object'
|
|
519
|
+
},
|
|
520
|
+
identifierQuote: '',
|
|
521
|
+
parameterPlaceholder: '?',
|
|
522
|
+
supportsTransactions: true,
|
|
523
|
+
supportsIndexes: true,
|
|
524
|
+
supportsForeignKeys: false,
|
|
525
|
+
supportsViews: false,
|
|
526
|
+
supportsStoredProcedures: false
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
Registry.register('mongodb', (config: MongoDBDialectConfig, schemas?: Database.Schemas<Record<string, object>>) => {
|
|
532
|
+
return new DocumentDatabase(new MongoDBDialect(config), schemas);
|
|
533
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {Dialect} from '../base';
|
|
2
|
+
import {RelatedDatabase} from "../type/related/database";
|
|
3
|
+
import {Registry} from "../registry";
|
|
4
|
+
import type { ConnectionOptions } from 'mysql2/promise';
|
|
5
|
+
import {Database} from "../base";
|
|
6
|
+
import {Column} from "../types";
|
|
7
|
+
|
|
8
|
+
export interface MySQLDialectConfig extends ConnectionOptions {}
|
|
9
|
+
|
|
10
|
+
export class MySQLDialect extends Dialect<MySQLDialectConfig, string> {
|
|
11
|
+
private connection: any = null;
|
|
12
|
+
|
|
13
|
+
constructor(config: MySQLDialectConfig) {
|
|
14
|
+
super('mysql', config);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Connection management
|
|
18
|
+
isConnected(): boolean {
|
|
19
|
+
return this.connection !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async connect(): Promise<void> {
|
|
23
|
+
try {
|
|
24
|
+
const { createConnection } = await import('mysql2/promise');
|
|
25
|
+
this.connection = await createConnection(this.config);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('forgot install mysql2 ?');
|
|
28
|
+
throw new Error(`MySQL 连接失败: ${error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async disconnect(): Promise<void> {
|
|
33
|
+
this.connection = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async healthCheck(): Promise<boolean> {
|
|
37
|
+
return this.isConnected();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async query<U = any>(sql: string, params?: any[]): Promise<U> {
|
|
41
|
+
const [rows] = await this.connection.execute(sql, params);
|
|
42
|
+
return rows as U;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async dispose(): Promise<void> {
|
|
46
|
+
if (this.connection) {
|
|
47
|
+
await this.connection.end();
|
|
48
|
+
this.connection = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// SQL generation methods
|
|
53
|
+
mapColumnType(type: string): string {
|
|
54
|
+
const typeMap: Record<string, string> = {
|
|
55
|
+
'text': 'TEXT',
|
|
56
|
+
'integer': 'INT',
|
|
57
|
+
'float': 'FLOAT',
|
|
58
|
+
'boolean': 'BOOLEAN',
|
|
59
|
+
'date': 'DATETIME',
|
|
60
|
+
'json': 'JSON'
|
|
61
|
+
};
|
|
62
|
+
return typeMap[type.toLowerCase()] || 'TEXT';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
quoteIdentifier(identifier: string): string {
|
|
66
|
+
return `\`${identifier}\``;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getParameterPlaceholder(index: number): string {
|
|
70
|
+
return '?';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getStatementTerminator(): string {
|
|
74
|
+
return ';';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
formatBoolean(value: boolean): string {
|
|
78
|
+
return value ? 'TRUE' : 'FALSE';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
formatDate(value: Date): string {
|
|
82
|
+
return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
formatJson(value: any): string {
|
|
86
|
+
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
escapeString(value: string): string {
|
|
90
|
+
return value.replace(/'/g, "''");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
formatDefaultValue(value: any): string {
|
|
94
|
+
if (typeof value === 'string') {
|
|
95
|
+
return `'${this.escapeString(value)}'`;
|
|
96
|
+
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
97
|
+
return value.toString();
|
|
98
|
+
} else if (value instanceof Date) {
|
|
99
|
+
return this.formatDate(value);
|
|
100
|
+
} else if (value === null) {
|
|
101
|
+
return 'NULL';
|
|
102
|
+
} else if (typeof value === 'object') {
|
|
103
|
+
return this.formatJson(value);
|
|
104
|
+
} else {
|
|
105
|
+
throw new Error(`Unsupported default value type: ${typeof value}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
formatLimit(limit: number): string {
|
|
110
|
+
return `LIMIT ${limit}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
formatOffset(offset: number): string {
|
|
114
|
+
return `OFFSET ${offset}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
formatLimitOffset(limit: number, offset: number): string {
|
|
118
|
+
return `LIMIT ${offset}, ${limit}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
formatCreateTable(tableName: string, columns: string[]): string {
|
|
122
|
+
return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(tableName)} (${columns.join(', ')}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
formatColumnDefinition(field: string, column: Column<any>): string {
|
|
126
|
+
const name = this.quoteIdentifier(String(field));
|
|
127
|
+
const type = this.mapColumnType(column.type);
|
|
128
|
+
const length = column.length ? `(${column.length})` : '';
|
|
129
|
+
const nullable = column.nullable === false ? ' NOT NULL' : '';
|
|
130
|
+
const primary = column.primary ? ' PRIMARY KEY' : '';
|
|
131
|
+
const unique = column.unique ? ' UNIQUE' : '';
|
|
132
|
+
const autoIncrement = column.autoIncrement ? ' AUTO_INCREMENT' : '';
|
|
133
|
+
const defaultVal = column.default !== undefined
|
|
134
|
+
? ` DEFAULT ${this.formatDefaultValue(column.default)}`
|
|
135
|
+
: '';
|
|
136
|
+
|
|
137
|
+
return `${name} ${type}${length}${primary}${unique}${autoIncrement}${nullable}${defaultVal}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
formatAlterTable(tableName: string, alterations: string[]): string {
|
|
141
|
+
return `ALTER TABLE ${this.quoteIdentifier(tableName)} ${alterations.join(', ')}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
formatDropTable(tableName: string, ifExists?: boolean): string {
|
|
145
|
+
const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
|
|
146
|
+
return `DROP TABLE ${ifExistsClause}${this.quoteIdentifier(tableName)}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
formatDropIndex(indexName: string, tableName: string, ifExists?: boolean): string {
|
|
150
|
+
const ifExistsClause = ifExists ? 'IF EXISTS ' : '';
|
|
151
|
+
return `DROP INDEX ${ifExistsClause}${this.quoteIdentifier(indexName)} ON ${this.quoteIdentifier(tableName)}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Registry.register('mysql', (config: MySQLDialectConfig, schemas?: Database.Schemas<Record<string, object>>) => {
|
|
156
|
+
return new RelatedDatabase(new MySQLDialect(config), schemas);
|
|
157
|
+
});
|