@zhin.js/database 1.0.3 → 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.
Files changed (119) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1360 -34
  3. package/lib/base/database.d.ts +71 -13
  4. package/lib/base/database.d.ts.map +1 -1
  5. package/lib/base/database.js +128 -4
  6. package/lib/base/database.js.map +1 -1
  7. package/lib/base/dialect.d.ts +27 -10
  8. package/lib/base/dialect.d.ts.map +1 -1
  9. package/lib/base/dialect.js +32 -0
  10. package/lib/base/dialect.js.map +1 -1
  11. package/lib/base/index.d.ts +1 -0
  12. package/lib/base/index.d.ts.map +1 -1
  13. package/lib/base/index.js +1 -0
  14. package/lib/base/index.js.map +1 -1
  15. package/lib/base/model.d.ts +105 -12
  16. package/lib/base/model.d.ts.map +1 -1
  17. package/lib/base/model.js +224 -3
  18. package/lib/base/model.js.map +1 -1
  19. package/lib/base/query-classes.d.ts +204 -33
  20. package/lib/base/query-classes.d.ts.map +1 -1
  21. package/lib/base/query-classes.js +276 -0
  22. package/lib/base/query-classes.js.map +1 -1
  23. package/lib/base/thenable.d.ts +7 -7
  24. package/lib/base/thenable.d.ts.map +1 -1
  25. package/lib/base/thenable.js +5 -4
  26. package/lib/base/thenable.js.map +1 -1
  27. package/lib/base/transaction.d.ts +46 -0
  28. package/lib/base/transaction.d.ts.map +1 -0
  29. package/lib/base/transaction.js +186 -0
  30. package/lib/base/transaction.js.map +1 -0
  31. package/lib/dialects/memory.d.ts +13 -8
  32. package/lib/dialects/memory.d.ts.map +1 -1
  33. package/lib/dialects/memory.js +10 -7
  34. package/lib/dialects/memory.js.map +1 -1
  35. package/lib/dialects/mongodb.d.ts +12 -8
  36. package/lib/dialects/mongodb.d.ts.map +1 -1
  37. package/lib/dialects/mongodb.js +21 -18
  38. package/lib/dialects/mongodb.js.map +1 -1
  39. package/lib/dialects/mysql.d.ts +36 -7
  40. package/lib/dialects/mysql.d.ts.map +1 -1
  41. package/lib/dialects/mysql.js +140 -21
  42. package/lib/dialects/mysql.js.map +1 -1
  43. package/lib/dialects/pg.d.ts +36 -7
  44. package/lib/dialects/pg.d.ts.map +1 -1
  45. package/lib/dialects/pg.js +140 -21
  46. package/lib/dialects/pg.js.map +1 -1
  47. package/lib/dialects/redis.d.ts +13 -8
  48. package/lib/dialects/redis.d.ts.map +1 -1
  49. package/lib/dialects/redis.js +14 -11
  50. package/lib/dialects/redis.js.map +1 -1
  51. package/lib/dialects/sqlite.d.ts +20 -7
  52. package/lib/dialects/sqlite.d.ts.map +1 -1
  53. package/lib/dialects/sqlite.js +66 -13
  54. package/lib/dialects/sqlite.js.map +1 -1
  55. package/lib/index.d.ts +1 -0
  56. package/lib/index.d.ts.map +1 -1
  57. package/lib/index.js +1 -0
  58. package/lib/index.js.map +1 -1
  59. package/lib/migration.d.ts +132 -0
  60. package/lib/migration.d.ts.map +1 -0
  61. package/lib/migration.js +475 -0
  62. package/lib/migration.js.map +1 -0
  63. package/lib/registry.d.ts +26 -23
  64. package/lib/registry.d.ts.map +1 -1
  65. package/lib/registry.js +1 -5
  66. package/lib/registry.js.map +1 -1
  67. package/lib/type/document/database.d.ts +12 -12
  68. package/lib/type/document/database.d.ts.map +1 -1
  69. package/lib/type/document/database.js +1 -1
  70. package/lib/type/document/database.js.map +1 -1
  71. package/lib/type/document/model.d.ts +8 -8
  72. package/lib/type/document/model.d.ts.map +1 -1
  73. package/lib/type/document/model.js +1 -1
  74. package/lib/type/document/model.js.map +1 -1
  75. package/lib/type/keyvalue/database.d.ts +12 -12
  76. package/lib/type/keyvalue/database.d.ts.map +1 -1
  77. package/lib/type/keyvalue/database.js +1 -1
  78. package/lib/type/keyvalue/database.js.map +1 -1
  79. package/lib/type/keyvalue/model.d.ts +3 -3
  80. package/lib/type/keyvalue/model.d.ts.map +1 -1
  81. package/lib/type/keyvalue/model.js +1 -1
  82. package/lib/type/keyvalue/model.js.map +1 -1
  83. package/lib/type/related/database.d.ts +49 -14
  84. package/lib/type/related/database.d.ts.map +1 -1
  85. package/lib/type/related/database.js +259 -28
  86. package/lib/type/related/database.js.map +1 -1
  87. package/lib/type/related/model.d.ts +252 -16
  88. package/lib/type/related/model.d.ts.map +1 -1
  89. package/lib/type/related/model.js +648 -23
  90. package/lib/type/related/model.js.map +1 -1
  91. package/lib/types.d.ts +475 -37
  92. package/lib/types.d.ts.map +1 -1
  93. package/lib/types.js +6 -0
  94. package/lib/types.js.map +1 -1
  95. package/package.json +10 -5
  96. package/src/base/database.ts +168 -24
  97. package/src/base/dialect.ts +49 -10
  98. package/src/base/index.ts +2 -1
  99. package/src/base/model.ts +258 -18
  100. package/src/base/query-classes.ts +471 -63
  101. package/src/base/thenable.ts +12 -11
  102. package/src/base/transaction.ts +213 -0
  103. package/src/dialects/memory.ts +17 -16
  104. package/src/dialects/mongodb.ts +44 -42
  105. package/src/dialects/mysql.ts +155 -26
  106. package/src/dialects/pg.ts +152 -25
  107. package/src/dialects/redis.ts +45 -43
  108. package/src/dialects/sqlite.ts +77 -19
  109. package/src/index.ts +1 -2
  110. package/src/migration.ts +544 -0
  111. package/src/registry.ts +33 -33
  112. package/src/type/document/database.ts +33 -33
  113. package/src/type/document/model.ts +15 -15
  114. package/src/type/keyvalue/database.ts +33 -33
  115. package/src/type/keyvalue/model.ts +19 -19
  116. package/src/type/related/database.ts +309 -34
  117. package/src/type/related/model.ts +801 -34
  118. package/src/types.ts +559 -44
  119. package/tests/database.test.ts +1738 -0
@@ -1,4 +1,4 @@
1
- import { Database,Dialect } from '../../base';
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.Definitions<S>,
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<U extends object = any>(params: QueryParams<U>): BuildQueryResult<string> {
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 object>(params: CreateQueryParams<T>): BuildQueryResult<string> {
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 object>(params: SelectQueryParams<T>): BuildQueryResult<string> {
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 => this.dialect.quoteIdentifier(String(f))).join(', ')
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(params.tableName)}`;
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 => this.dialect.quoteIdentifier(String(f))).join(', ');
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 => `${this.dialect.quoteIdentifier(String(o.field))} ${o.direction}`)
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 object>(params: InsertQueryParams<T>): BuildQueryResult<string> {
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
- const values = Object.values(params.data).map(v => this.dialect.formatDefaultValue(v));
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 object>(params: UpdateQueryParams<T>): BuildQueryResult<string> {
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 object>(params: DeleteQueryParams<T>): BuildQueryResult<string> {
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 object>(params: AlterQueryParams<T>): BuildQueryResult<string> {
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 object>(params: DropTableQueryParams<T>): BuildQueryResult<string> {
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
- protected parseCondition<T extends object>(condition: Condition<T>): [string, any[]] {
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 = this.dialect.quoteIdentifier(key);
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 (Array.isArray(value[op]) && value[op].length) {
338
- const placeholders = value[op].map(() => this.dialect.getParameterPlaceholder(params.length + value[op].indexOf(value[op])));
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 (Array.isArray(value[op]) && value[op].length) {
347
- const placeholders = value[op].map(() => this.dialect.getParameterPlaceholder(params.length + value[op].indexOf(value[op])));
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 = this.dialect.quoteIdentifier(key);
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[T], Dialect<D,string>> {
378
- let model = this.models.get(name as string);
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 as unknown as RelatedDatabase<D>, name as string);
381
- this.models.set(name as string, model);
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
  /**