@zhin.js/database 1.0.45 → 1.0.47
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/package.json +7 -4
- package/CHANGELOG.md +0 -293
- package/src/base/database.ts +0 -272
- package/src/base/dialect.ts +0 -89
- package/src/base/index.ts +0 -6
- package/src/base/model.ts +0 -329
- package/src/base/query-classes.ts +0 -625
- package/src/base/thenable.ts +0 -55
- package/src/base/transaction.ts +0 -213
- package/src/dialects/memory.ts +0 -882
- package/src/dialects/mongodb.ts +0 -535
- package/src/dialects/mysql.ts +0 -286
- package/src/dialects/pg.ts +0 -284
- package/src/dialects/redis.ts +0 -600
- package/src/dialects/sqlite.ts +0 -317
- package/src/index.ts +0 -19
- package/src/migration.ts +0 -547
- package/src/registry.ts +0 -59
- package/src/type/document/database.ts +0 -284
- package/src/type/document/model.ts +0 -87
- package/src/type/keyvalue/database.ts +0 -262
- package/src/type/keyvalue/model.ts +0 -339
- package/src/type/related/database.ts +0 -674
- package/src/type/related/model.ts +0 -884
- package/src/types.ts +0 -918
- package/tests/database.test.ts +0 -1750
- package/tests/dialects.test.ts +0 -147
- package/tests/migration.test.ts +0 -73
- package/tsconfig.json +0 -24
|
@@ -1,674 +0,0 @@
|
|
|
1
|
-
import { Database,Dialect,Model } from '../../base/index.js';
|
|
2
|
-
import { RelatedModel } from './model.js';
|
|
3
|
-
import {
|
|
4
|
-
QueryParams,
|
|
5
|
-
BuildQueryResult,
|
|
6
|
-
CreateQueryParams,
|
|
7
|
-
SelectQueryParams,
|
|
8
|
-
InsertQueryParams,
|
|
9
|
-
InsertManyQueryParams,
|
|
10
|
-
UpdateQueryParams,
|
|
11
|
-
DeleteQueryParams,
|
|
12
|
-
AlterQueryParams,
|
|
13
|
-
DropTableQueryParams,
|
|
14
|
-
DropIndexQueryParams,
|
|
15
|
-
AggregateQueryParams,
|
|
16
|
-
Condition,
|
|
17
|
-
Column,
|
|
18
|
-
AddDefinition,
|
|
19
|
-
ModifyDefinition,
|
|
20
|
-
DropDefinition,
|
|
21
|
-
Subquery,
|
|
22
|
-
JoinClause,
|
|
23
|
-
RelationsConfig,
|
|
24
|
-
isCreateQuery,
|
|
25
|
-
isSelectQuery,
|
|
26
|
-
isInsertQuery,
|
|
27
|
-
isInsertManyQuery,
|
|
28
|
-
isUpdateQuery,
|
|
29
|
-
isDeleteQuery,
|
|
30
|
-
isAlterQuery,
|
|
31
|
-
isDropTableQuery,
|
|
32
|
-
isDropIndexQuery,
|
|
33
|
-
isAggregateQuery,
|
|
34
|
-
} from '../../types.js';
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 判断是否为子查询对象
|
|
38
|
-
*/
|
|
39
|
-
function isSubquery(value: any): value is Subquery {
|
|
40
|
-
return value && typeof value === 'object' && value.__isSubquery === true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 关系型数据库类
|
|
45
|
-
* 支持表、行、列的关系型数据模型
|
|
46
|
-
*/
|
|
47
|
-
export class RelatedDatabase<
|
|
48
|
-
D=any,
|
|
49
|
-
S extends Record<string, object> = Record<string, object>
|
|
50
|
-
> extends Database<D,S,string> {
|
|
51
|
-
|
|
52
|
-
/** 关系配置 */
|
|
53
|
-
protected relationsConfig?: RelationsConfig<S>;
|
|
54
|
-
|
|
55
|
-
constructor(
|
|
56
|
-
dialect: Dialect<D,S,string>,
|
|
57
|
-
definitions?: Database.DefinitionObj<S>,
|
|
58
|
-
relations?: RelationsConfig<S>,
|
|
59
|
-
) {
|
|
60
|
-
super(dialect,definitions);
|
|
61
|
-
this.relationsConfig = relations;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 设置关系配置
|
|
66
|
-
* @example
|
|
67
|
-
* ```ts
|
|
68
|
-
* db.defineRelations({
|
|
69
|
-
* users: {
|
|
70
|
-
* hasMany: { orders: 'userId' },
|
|
71
|
-
* hasOne: { profile: 'userId' }
|
|
72
|
-
* },
|
|
73
|
-
* orders: {
|
|
74
|
-
* belongsTo: { users: 'userId' }
|
|
75
|
-
* }
|
|
76
|
-
* });
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
defineRelations(config: RelationsConfig<S>): this {
|
|
80
|
-
this.relationsConfig = config;
|
|
81
|
-
// 清除模型缓存,以便重新应用关系
|
|
82
|
-
this.models.clear();
|
|
83
|
-
return this;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
protected async initialize(): Promise<void> {
|
|
87
|
-
// 并行创建所有表以提高性能
|
|
88
|
-
const tableEntries = Array.from(this.definitions.entries());
|
|
89
|
-
await Promise.all(
|
|
90
|
-
tableEntries.map(async ([tableName, definition]) => {
|
|
91
|
-
await this.create(tableName, definition);
|
|
92
|
-
})
|
|
93
|
-
);
|
|
94
|
-
// 创建完成后,统一设置 models(避免并发竞争)
|
|
95
|
-
for (const [tableName, definition] of tableEntries) {
|
|
96
|
-
this.models.set(tableName, new RelatedModel(this, tableName, definition));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// SQL generation method
|
|
101
|
-
buildQuery<T extends keyof S>(params: QueryParams<S,T>): BuildQueryResult<string> {
|
|
102
|
-
if (isCreateQuery(params)) {
|
|
103
|
-
return this.buildCreateQuery(params);
|
|
104
|
-
} else if (isSelectQuery(params)) {
|
|
105
|
-
return this.buildSelectQuery(params);
|
|
106
|
-
} else if (isInsertQuery(params)) {
|
|
107
|
-
return this.buildInsertQuery(params);
|
|
108
|
-
} else if (isInsertManyQuery(params)) {
|
|
109
|
-
return this.buildInsertManyQuery(params);
|
|
110
|
-
} else if (isUpdateQuery(params)) {
|
|
111
|
-
return this.buildUpdateQuery(params);
|
|
112
|
-
} else if (isDeleteQuery(params)) {
|
|
113
|
-
return this.buildDeleteQuery(params);
|
|
114
|
-
} else if (isAlterQuery(params)) {
|
|
115
|
-
return this.buildAlterQuery(params);
|
|
116
|
-
} else if (isDropTableQuery(params)) {
|
|
117
|
-
return this.buildDropTableQuery(params);
|
|
118
|
-
} else if (isDropIndexQuery(params)) {
|
|
119
|
-
return this.buildDropIndexQuery(params);
|
|
120
|
-
} else if (isAggregateQuery(params)) {
|
|
121
|
-
return this.buildAggregateQuery(params);
|
|
122
|
-
} else {
|
|
123
|
-
throw new Error(`Unsupported query type: ${(params as any).type}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ========================================================================
|
|
128
|
-
// CREATE TABLE Query
|
|
129
|
-
// ========================================================================
|
|
130
|
-
|
|
131
|
-
protected buildCreateQuery<T extends keyof S>(params: CreateQueryParams<S,T>): BuildQueryResult<string> {
|
|
132
|
-
const columnDefs = Object.entries(params.definition).map(([field, column]) => this.formatColumnDefinition(field,column as Column));
|
|
133
|
-
const query = this.dialect.formatCreateTable(params.tableName, columnDefs);
|
|
134
|
-
return { query, params: [] };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ========================================================================
|
|
138
|
-
// SELECT Query
|
|
139
|
-
// ========================================================================
|
|
140
|
-
|
|
141
|
-
protected buildSelectQuery<T extends keyof S>(params: SelectQueryParams<S,T>): BuildQueryResult<string> {
|
|
142
|
-
const tableName = String(params.tableName);
|
|
143
|
-
const hasJoins = params.joins && params.joins.length > 0;
|
|
144
|
-
|
|
145
|
-
// 构建字段列表(有 JOIN 时需要加表名前缀)
|
|
146
|
-
const fields = params.fields && params.fields.length
|
|
147
|
-
? params.fields.map(f => {
|
|
148
|
-
const fieldName = this.dialect.quoteIdentifier(String(f));
|
|
149
|
-
return hasJoins
|
|
150
|
-
? `${this.dialect.quoteIdentifier(tableName)}.${fieldName}`
|
|
151
|
-
: fieldName;
|
|
152
|
-
}).join(', ')
|
|
153
|
-
: hasJoins ? `${this.dialect.quoteIdentifier(tableName)}.*` : '*';
|
|
154
|
-
|
|
155
|
-
let query = `SELECT ${fields} FROM ${this.dialect.quoteIdentifier(tableName)}`;
|
|
156
|
-
const queryParams: any[] = [];
|
|
157
|
-
|
|
158
|
-
// JOIN clauses
|
|
159
|
-
if (hasJoins) {
|
|
160
|
-
for (const join of params.joins!) {
|
|
161
|
-
query += ` ${this.formatJoinClause(tableName, join)}`;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// WHERE clause
|
|
166
|
-
if (params.conditions) {
|
|
167
|
-
const [condition, conditionParams] = this.parseCondition(params.conditions, hasJoins ? tableName : undefined);
|
|
168
|
-
if (condition) {
|
|
169
|
-
query += ` WHERE ${condition}`;
|
|
170
|
-
queryParams.push(...conditionParams);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// GROUP BY clause
|
|
175
|
-
if (params.groupings && params.groupings.length) {
|
|
176
|
-
const groupings = params.groupings.map(f => {
|
|
177
|
-
const fieldName = this.dialect.quoteIdentifier(String(f));
|
|
178
|
-
return hasJoins
|
|
179
|
-
? `${this.dialect.quoteIdentifier(tableName)}.${fieldName}`
|
|
180
|
-
: fieldName;
|
|
181
|
-
}).join(', ');
|
|
182
|
-
query += ` GROUP BY ${groupings}`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ORDER BY clause
|
|
186
|
-
if (params.orderings && params.orderings.length) {
|
|
187
|
-
const orderings = params.orderings
|
|
188
|
-
.map(o => {
|
|
189
|
-
const fieldName = this.dialect.quoteIdentifier(String(o.field));
|
|
190
|
-
const fullField = hasJoins
|
|
191
|
-
? `${this.dialect.quoteIdentifier(tableName)}.${fieldName}`
|
|
192
|
-
: fieldName;
|
|
193
|
-
return `${fullField} ${o.direction}`;
|
|
194
|
-
})
|
|
195
|
-
.join(', ');
|
|
196
|
-
query += ` ORDER BY ${orderings}`;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// LIMIT and OFFSET
|
|
200
|
-
if (params.limitCount !== undefined && params.offsetCount !== undefined) {
|
|
201
|
-
query += ` ${this.dialect.formatLimitOffset(params.limitCount, params.offsetCount)}`;
|
|
202
|
-
} else if (params.limitCount !== undefined) {
|
|
203
|
-
query += ` ${this.dialect.formatLimit(params.limitCount)}`;
|
|
204
|
-
} else if (params.offsetCount !== undefined) {
|
|
205
|
-
query += ` ${this.dialect.formatOffset(params.offsetCount)}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return { query, params: queryParams };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* 格式化 JOIN 子句
|
|
213
|
-
*/
|
|
214
|
-
protected formatJoinClause<T extends keyof S>(
|
|
215
|
-
mainTable: string,
|
|
216
|
-
join: JoinClause<S, T, keyof S>
|
|
217
|
-
): string {
|
|
218
|
-
const joinTable = this.dialect.quoteIdentifier(String(join.table));
|
|
219
|
-
const leftField = `${this.dialect.quoteIdentifier(mainTable)}.${this.dialect.quoteIdentifier(String(join.leftField))}`;
|
|
220
|
-
const rightField = `${joinTable}.${this.dialect.quoteIdentifier(String(join.rightField))}`;
|
|
221
|
-
|
|
222
|
-
return `${join.type} JOIN ${joinTable} ON ${leftField} = ${rightField}`;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ========================================================================
|
|
226
|
-
// INSERT Query
|
|
227
|
-
// ========================================================================
|
|
228
|
-
|
|
229
|
-
protected buildInsertQuery<T extends keyof S>(params: InsertQueryParams<S,T>): BuildQueryResult<string> {
|
|
230
|
-
const keys = Object.keys(params.data);
|
|
231
|
-
const columns = keys.map(k => this.dialect.quoteIdentifier(k)).join(', ');
|
|
232
|
-
const placeholders = keys.map((_, index) => this.dialect.getParameterPlaceholder(index)).join(', ');
|
|
233
|
-
|
|
234
|
-
const query = `INSERT INTO ${this.dialect.quoteIdentifier(params.tableName)} (${columns}) VALUES (${placeholders})`;
|
|
235
|
-
// 直接传值,不要格式化(参数化查询由驱动处理)
|
|
236
|
-
const values = Object.values(params.data);
|
|
237
|
-
|
|
238
|
-
return { query, params: values };
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ========================================================================
|
|
242
|
-
// INSERT MANY Query (Batch Insert)
|
|
243
|
-
// ========================================================================
|
|
244
|
-
|
|
245
|
-
protected buildInsertManyQuery<T extends keyof S>(params: InsertManyQueryParams<S,T>): BuildQueryResult<string> {
|
|
246
|
-
if (!params.data.length) {
|
|
247
|
-
throw new Error('Cannot insert empty array');
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const keys = Object.keys(params.data[0]);
|
|
251
|
-
const columns = keys.map(k => this.dialect.quoteIdentifier(k)).join(', ');
|
|
252
|
-
|
|
253
|
-
const allValues: any[] = [];
|
|
254
|
-
const valueRows: string[] = [];
|
|
255
|
-
|
|
256
|
-
params.data.forEach((row, rowIndex) => {
|
|
257
|
-
const placeholders = keys.map((_, colIndex) =>
|
|
258
|
-
this.dialect.getParameterPlaceholder(rowIndex * keys.length + colIndex)
|
|
259
|
-
).join(', ');
|
|
260
|
-
valueRows.push(`(${placeholders})`);
|
|
261
|
-
|
|
262
|
-
keys.forEach(key => {
|
|
263
|
-
// 直接传值,不要格式化(参数化查询由驱动处理)
|
|
264
|
-
allValues.push((row as any)[key]);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
const query = `INSERT INTO ${this.dialect.quoteIdentifier(params.tableName)} (${columns}) VALUES ${valueRows.join(', ')}`;
|
|
269
|
-
|
|
270
|
-
return { query, params: allValues };
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// ========================================================================
|
|
274
|
-
// UPDATE Query
|
|
275
|
-
// ========================================================================
|
|
276
|
-
|
|
277
|
-
protected buildUpdateQuery<T extends keyof S>(params: UpdateQueryParams<S,T>): BuildQueryResult<string> {
|
|
278
|
-
const updateKeys = Object.keys(params.update);
|
|
279
|
-
const setClause = updateKeys
|
|
280
|
-
.map((k, index) => `${this.dialect.quoteIdentifier(k)} = ${this.dialect.getParameterPlaceholder(index)}`)
|
|
281
|
-
.join(', ');
|
|
282
|
-
|
|
283
|
-
let query = `UPDATE ${this.dialect.quoteIdentifier(params.tableName)} SET ${setClause}`;
|
|
284
|
-
const queryParams: any[] = [...Object.values(params.update)];
|
|
285
|
-
|
|
286
|
-
// WHERE clause
|
|
287
|
-
if (params.conditions) {
|
|
288
|
-
const [condition, conditionParams] = this.parseCondition(params.conditions);
|
|
289
|
-
if (condition) {
|
|
290
|
-
query += ` WHERE ${condition}`;
|
|
291
|
-
queryParams.push(...conditionParams);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return { query, params: queryParams };
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ========================================================================
|
|
299
|
-
// DELETE Query
|
|
300
|
-
// ========================================================================
|
|
301
|
-
|
|
302
|
-
protected buildDeleteQuery<T extends keyof S>(params: DeleteQueryParams<S,T>): BuildQueryResult<string> {
|
|
303
|
-
let query = `DELETE FROM ${this.dialect.quoteIdentifier(params.tableName)}`;
|
|
304
|
-
const queryParams: any[] = [];
|
|
305
|
-
|
|
306
|
-
// WHERE clause
|
|
307
|
-
if (params.conditions) {
|
|
308
|
-
const [condition, conditionParams] = this.parseCondition(params.conditions);
|
|
309
|
-
if (condition) {
|
|
310
|
-
query += ` WHERE ${condition}`;
|
|
311
|
-
queryParams.push(...conditionParams);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return { query, params: queryParams };
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// ========================================================================
|
|
319
|
-
// ALTER TABLE Query
|
|
320
|
-
// ========================================================================
|
|
321
|
-
|
|
322
|
-
protected buildAlterQuery<T extends keyof S>(params: AlterQueryParams<S,T>): BuildQueryResult<string> {
|
|
323
|
-
const alterations = Object.entries(params.alterations).map(([field,alteration]) => this.formatAlteration(field, alteration as AddDefinition<T> | ModifyDefinition<T> | DropDefinition));
|
|
324
|
-
const query = this.dialect.formatAlterTable(params.tableName, alterations);
|
|
325
|
-
return { query, params: [] };
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// ========================================================================
|
|
329
|
-
// DROP TABLE Query
|
|
330
|
-
// ========================================================================
|
|
331
|
-
|
|
332
|
-
protected buildDropTableQuery<T extends keyof S>(params: DropTableQueryParams<S,T>): BuildQueryResult<string> {
|
|
333
|
-
const query = this.dialect.formatDropTable(params.tableName, true);
|
|
334
|
-
return { query, params: [] };
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// ========================================================================
|
|
338
|
-
// DROP INDEX Query
|
|
339
|
-
// ========================================================================
|
|
340
|
-
|
|
341
|
-
protected buildDropIndexQuery<T extends keyof S>(params: DropIndexQueryParams<S,T>): BuildQueryResult<string> {
|
|
342
|
-
const query = this.dialect.formatDropIndex(params.indexName, params.tableName, true);
|
|
343
|
-
return { query, params: [] };
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// ========================================================================
|
|
347
|
-
// AGGREGATE Query
|
|
348
|
-
// ========================================================================
|
|
349
|
-
|
|
350
|
-
protected buildAggregateQuery<T extends keyof S>(params: AggregateQueryParams<S,T>): BuildQueryResult<string> {
|
|
351
|
-
if (!params.aggregates.length) {
|
|
352
|
-
throw new Error('At least one aggregate function is required');
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Build SELECT fields with aggregate functions
|
|
356
|
-
const selectFields: string[] = [];
|
|
357
|
-
|
|
358
|
-
// Add grouping fields to select
|
|
359
|
-
if (params.groupings && params.groupings.length) {
|
|
360
|
-
params.groupings.forEach(field => {
|
|
361
|
-
selectFields.push(this.dialect.quoteIdentifier(String(field)));
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Add aggregate functions
|
|
366
|
-
params.aggregates.forEach(agg => {
|
|
367
|
-
selectFields.push(this.dialect.formatAggregate(agg.fn, String(agg.field), agg.alias));
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
let query = `SELECT ${selectFields.join(', ')} FROM ${this.dialect.quoteIdentifier(params.tableName)}`;
|
|
371
|
-
const queryParams: any[] = [];
|
|
372
|
-
|
|
373
|
-
// WHERE clause
|
|
374
|
-
if (params.conditions) {
|
|
375
|
-
const [condition, conditionParams] = this.parseCondition(params.conditions);
|
|
376
|
-
if (condition) {
|
|
377
|
-
query += ` WHERE ${condition}`;
|
|
378
|
-
queryParams.push(...conditionParams);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// GROUP BY clause
|
|
383
|
-
if (params.groupings && params.groupings.length) {
|
|
384
|
-
const groupings = params.groupings.map(f => this.dialect.quoteIdentifier(String(f))).join(', ');
|
|
385
|
-
query += ` GROUP BY ${groupings}`;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// HAVING clause
|
|
389
|
-
if (params.havingConditions && Object.keys(params.havingConditions).length) {
|
|
390
|
-
const [havingCondition, havingParams] = this.parseCondition(params.havingConditions);
|
|
391
|
-
if (havingCondition) {
|
|
392
|
-
query += ` HAVING ${havingCondition}`;
|
|
393
|
-
queryParams.push(...havingParams);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return { query, params: queryParams };
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// ========================================================================
|
|
401
|
-
// Helper Methods
|
|
402
|
-
// ========================================================================
|
|
403
|
-
|
|
404
|
-
protected formatColumnDefinition<T =any>(field: string, column: Column<T>): string {
|
|
405
|
-
const name = this.dialect.quoteIdentifier(String(field));
|
|
406
|
-
const type = this.dialect.mapColumnType(column.type);
|
|
407
|
-
const length = column.length ? `(${column.length})` : '';
|
|
408
|
-
const nullable = column.nullable === false ? ' NOT NULL' : '';
|
|
409
|
-
const primary = column.primary ? ' PRIMARY KEY' : '';
|
|
410
|
-
const unique = column.unique ? ' UNIQUE' : '';
|
|
411
|
-
const defaultVal = column.default !== undefined
|
|
412
|
-
? ` DEFAULT ${this.dialect.formatDefaultValue(column.default)}`
|
|
413
|
-
: '';
|
|
414
|
-
|
|
415
|
-
return `${name} ${type}${length}${primary}${unique}${nullable}${defaultVal}`;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
protected formatAlteration<T=any>(field:string,alteration: AddDefinition<T> | ModifyDefinition<T> | DropDefinition): string {
|
|
419
|
-
const name = this.dialect.quoteIdentifier(field);
|
|
420
|
-
|
|
421
|
-
switch (alteration.action) {
|
|
422
|
-
case 'add':
|
|
423
|
-
// 将 alteration 转换为 Column 格式
|
|
424
|
-
const addColumn: Column<T> = {
|
|
425
|
-
type: alteration.type,
|
|
426
|
-
nullable: alteration.nullable,
|
|
427
|
-
default: alteration.default,
|
|
428
|
-
primary: alteration.primary,
|
|
429
|
-
length: alteration.length
|
|
430
|
-
};
|
|
431
|
-
return `ADD COLUMN ${this.formatColumnDefinition(field, addColumn)}`;
|
|
432
|
-
case 'modify':
|
|
433
|
-
const type = alteration.type ? this.dialect.mapColumnType(alteration.type) : '';
|
|
434
|
-
const length = alteration.length ? `(${alteration.length})` : '';
|
|
435
|
-
const nullable = alteration.nullable !== undefined
|
|
436
|
-
? (alteration.nullable ? ' NULL' : ' NOT NULL')
|
|
437
|
-
: '';
|
|
438
|
-
const defaultVal = alteration.default !== undefined
|
|
439
|
-
? ` DEFAULT ${this.dialect.formatDefaultValue(alteration.default)}`
|
|
440
|
-
: '';
|
|
441
|
-
return `MODIFY COLUMN ${name} ${type}${length}${nullable}${defaultVal}`;
|
|
442
|
-
case 'drop':
|
|
443
|
-
return `DROP COLUMN ${name}`;
|
|
444
|
-
default:
|
|
445
|
-
throw new Error(`Unsupported alteration action`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* 解析条件对象为 SQL WHERE 子句
|
|
451
|
-
* @param condition 条件对象
|
|
452
|
-
* @param tablePrefix 表名前缀(用于 JOIN 查询)
|
|
453
|
-
*/
|
|
454
|
-
protected parseCondition<T extends object>(condition: Condition<T>, tablePrefix?: string): [string, any[]] {
|
|
455
|
-
const clauses: string[] = [];
|
|
456
|
-
const params: any[] = [];
|
|
457
|
-
|
|
458
|
-
// 辅助函数:生成带前缀的字段名
|
|
459
|
-
const formatField = (field: string): string => {
|
|
460
|
-
const quotedField = this.dialect.quoteIdentifier(field);
|
|
461
|
-
return tablePrefix
|
|
462
|
-
? `${this.dialect.quoteIdentifier(tablePrefix)}.${quotedField}`
|
|
463
|
-
: quotedField;
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
for (const key in condition) {
|
|
467
|
-
if (key === '$and' && Array.isArray((condition as any).$and)) {
|
|
468
|
-
const subClauses: string[] = [];
|
|
469
|
-
for (const subCondition of (condition as any).$and) {
|
|
470
|
-
const [subClause, subParams] = this.parseCondition(subCondition, tablePrefix);
|
|
471
|
-
if (subClause) {
|
|
472
|
-
subClauses.push(`(${subClause})`);
|
|
473
|
-
params.push(...subParams);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (subClauses.length) {
|
|
477
|
-
clauses.push(subClauses.join(' AND '));
|
|
478
|
-
}
|
|
479
|
-
} else if (key === '$or' && Array.isArray((condition as any).$or)) {
|
|
480
|
-
const subClauses: string[] = [];
|
|
481
|
-
for (const subCondition of (condition as any).$or) {
|
|
482
|
-
const [subClause, subParams] = this.parseCondition(subCondition, tablePrefix);
|
|
483
|
-
if (subClause) {
|
|
484
|
-
subClauses.push(`(${subClause})`);
|
|
485
|
-
params.push(...subParams);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
if (subClauses.length) {
|
|
489
|
-
clauses.push(subClauses.join(' OR '));
|
|
490
|
-
}
|
|
491
|
-
} else if (key === '$not' && (condition as any).$not) {
|
|
492
|
-
const [subClause, subParams] = this.parseCondition((condition as any).$not, tablePrefix);
|
|
493
|
-
if (subClause) {
|
|
494
|
-
clauses.push(`NOT (${subClause})`);
|
|
495
|
-
params.push(...subParams);
|
|
496
|
-
}
|
|
497
|
-
} else {
|
|
498
|
-
const value = (condition as any)[key];
|
|
499
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
500
|
-
for (const op in value) {
|
|
501
|
-
const quotedKey = formatField(key);
|
|
502
|
-
const placeholder = this.dialect.getParameterPlaceholder(params.length);
|
|
503
|
-
|
|
504
|
-
switch (op) {
|
|
505
|
-
case '$eq':
|
|
506
|
-
if (value[op] === null) {
|
|
507
|
-
clauses.push(`${quotedKey} IS NULL`);
|
|
508
|
-
} else {
|
|
509
|
-
clauses.push(`${quotedKey} = ${placeholder}`);
|
|
510
|
-
params.push(value[op]);
|
|
511
|
-
}
|
|
512
|
-
break;
|
|
513
|
-
case '$ne':
|
|
514
|
-
if (value[op] === null) {
|
|
515
|
-
clauses.push(`${quotedKey} IS NOT NULL`);
|
|
516
|
-
} else {
|
|
517
|
-
clauses.push(`${quotedKey} <> ${placeholder}`);
|
|
518
|
-
params.push(value[op]);
|
|
519
|
-
}
|
|
520
|
-
break;
|
|
521
|
-
case '$gt':
|
|
522
|
-
clauses.push(`${quotedKey} > ${placeholder}`);
|
|
523
|
-
params.push(value[op]);
|
|
524
|
-
break;
|
|
525
|
-
case '$gte':
|
|
526
|
-
clauses.push(`${quotedKey} >= ${placeholder}`);
|
|
527
|
-
params.push(value[op]);
|
|
528
|
-
break;
|
|
529
|
-
case '$lt':
|
|
530
|
-
clauses.push(`${quotedKey} < ${placeholder}`);
|
|
531
|
-
params.push(value[op]);
|
|
532
|
-
break;
|
|
533
|
-
case '$lte':
|
|
534
|
-
clauses.push(`${quotedKey} <= ${placeholder}`);
|
|
535
|
-
params.push(value[op]);
|
|
536
|
-
break;
|
|
537
|
-
case '$in':
|
|
538
|
-
if (isSubquery(value[op])) {
|
|
539
|
-
// 子查询
|
|
540
|
-
const subquery = value[op].toSQL();
|
|
541
|
-
clauses.push(`${quotedKey} IN (${subquery.sql})`);
|
|
542
|
-
params.push(...subquery.params);
|
|
543
|
-
} else if (Array.isArray(value[op]) && value[op].length) {
|
|
544
|
-
const placeholders = value[op].map((_: any, i: number) => this.dialect.getParameterPlaceholder(params.length + i));
|
|
545
|
-
clauses.push(`${quotedKey} IN (${placeholders.join(', ')})`);
|
|
546
|
-
params.push(...value[op]);
|
|
547
|
-
} else {
|
|
548
|
-
clauses.push('1=0'); // Empty IN clause should yield no results
|
|
549
|
-
}
|
|
550
|
-
break;
|
|
551
|
-
case '$nin':
|
|
552
|
-
if (isSubquery(value[op])) {
|
|
553
|
-
// 子查询
|
|
554
|
-
const subquery = value[op].toSQL();
|
|
555
|
-
clauses.push(`${quotedKey} NOT IN (${subquery.sql})`);
|
|
556
|
-
params.push(...subquery.params);
|
|
557
|
-
} else if (Array.isArray(value[op]) && value[op].length) {
|
|
558
|
-
const placeholders = value[op].map((_: any, i: number) => this.dialect.getParameterPlaceholder(params.length + i));
|
|
559
|
-
clauses.push(`${quotedKey} NOT IN (${placeholders.join(', ')})`);
|
|
560
|
-
params.push(...value[op]);
|
|
561
|
-
}
|
|
562
|
-
break;
|
|
563
|
-
case '$like':
|
|
564
|
-
clauses.push(`${quotedKey} LIKE ${placeholder}`);
|
|
565
|
-
params.push(value[op]);
|
|
566
|
-
break;
|
|
567
|
-
case '$nlike':
|
|
568
|
-
clauses.push(`${quotedKey} NOT LIKE ${placeholder}`);
|
|
569
|
-
params.push(value[op]);
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
} else {
|
|
574
|
-
const quotedKey = formatField(key);
|
|
575
|
-
// null 值使用 IS NULL / IS NOT NULL
|
|
576
|
-
if (value === null) {
|
|
577
|
-
clauses.push(`${quotedKey} IS NULL`);
|
|
578
|
-
} else {
|
|
579
|
-
const placeholder = this.dialect.getParameterPlaceholder(params.length);
|
|
580
|
-
clauses.push(`${quotedKey} = ${placeholder}`);
|
|
581
|
-
params.push(value);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
return [clauses.join(' AND '), params];
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* 获取模型
|
|
592
|
-
* @param name 模型名称
|
|
593
|
-
* @param options 可选的模型选项(如 softDelete, timestamps)
|
|
594
|
-
*/
|
|
595
|
-
model<T extends keyof S>(name: T, options?: import('../../types.js').ModelOptions): RelatedModel<D,S,T> {
|
|
596
|
-
// 如果有 options,每次都创建新的实例(因为选项可能不同)
|
|
597
|
-
if (options) {
|
|
598
|
-
const model = new RelatedModel(this, name, options);
|
|
599
|
-
this.applyRelationsToModel(model, name);
|
|
600
|
-
return model;
|
|
601
|
-
}
|
|
602
|
-
// 无选项时使用缓存
|
|
603
|
-
let model = this.models.get(name) as RelatedModel<D,S,T> | undefined;
|
|
604
|
-
if (!model) {
|
|
605
|
-
model = new RelatedModel(this, name);
|
|
606
|
-
this.applyRelationsToModel(model, name);
|
|
607
|
-
this.models.set(name, model as any);
|
|
608
|
-
}
|
|
609
|
-
return model as RelatedModel<D,S,T>;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* 应用关系配置到模型
|
|
614
|
-
*/
|
|
615
|
-
private applyRelationsToModel<T extends keyof S>(model: RelatedModel<D, S, T>, tableName: T): void {
|
|
616
|
-
const tableConfig = this.relationsConfig?.[String(tableName) as Extract<keyof S, string>];
|
|
617
|
-
if (!tableConfig) return;
|
|
618
|
-
|
|
619
|
-
// 应用 hasMany 关系
|
|
620
|
-
if (tableConfig.hasMany) {
|
|
621
|
-
for (const [target, foreignKey] of Object.entries(tableConfig.hasMany)) {
|
|
622
|
-
if (foreignKey) {
|
|
623
|
-
const targetModel = new RelatedModel(this, target as keyof S);
|
|
624
|
-
model.hasMany(targetModel as any, foreignKey as any);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// 应用 hasOne 关系
|
|
630
|
-
if (tableConfig.hasOne) {
|
|
631
|
-
for (const [target, foreignKey] of Object.entries(tableConfig.hasOne)) {
|
|
632
|
-
if (foreignKey) {
|
|
633
|
-
const targetModel = new RelatedModel(this, target as keyof S);
|
|
634
|
-
model.hasOne(targetModel as any, foreignKey as any);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// 应用 belongsTo 关系
|
|
640
|
-
if (tableConfig.belongsTo) {
|
|
641
|
-
for (const [target, foreignKey] of Object.entries(tableConfig.belongsTo)) {
|
|
642
|
-
if (foreignKey) {
|
|
643
|
-
const targetModel = new RelatedModel(this, target as keyof S);
|
|
644
|
-
model.belongsTo(targetModel as any, foreignKey as any);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// 应用 belongsToMany 关系
|
|
650
|
-
if (tableConfig.belongsToMany) {
|
|
651
|
-
for (const [target, config] of Object.entries(tableConfig.belongsToMany)) {
|
|
652
|
-
if (config) {
|
|
653
|
-
const targetModel = new RelatedModel(this, target as keyof S);
|
|
654
|
-
model.belongsToMany(
|
|
655
|
-
targetModel as any,
|
|
656
|
-
config.pivot,
|
|
657
|
-
config.foreignKey,
|
|
658
|
-
config.relatedKey,
|
|
659
|
-
'id' as any,
|
|
660
|
-
'id' as any,
|
|
661
|
-
config.pivotFields
|
|
662
|
-
);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* 获取所有模型名称
|
|
670
|
-
*/
|
|
671
|
-
getModelNames(): string[] {
|
|
672
|
-
return Object.keys(this.definitions || {});
|
|
673
|
-
}
|
|
674
|
-
}
|