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