orchid-orm 1.3.15 → 1.3.16

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.
@@ -8,8 +8,6 @@ import { Model } from '../model';
8
8
  import {
9
9
  addQueryOn,
10
10
  getQueryAs,
11
- HasManyNestedInsert,
12
- HasManyNestedUpdate,
13
11
  HasManyRelation,
14
12
  CreateData,
15
13
  JoinCallback,
@@ -21,8 +19,18 @@ import {
21
19
  WhereResult,
22
20
  InsertQueryData,
23
21
  isQueryReturnsAll,
22
+ VirtualColumn,
23
+ CreateCtx,
24
+ UpdateCtx,
24
25
  } from 'pqb';
25
- import { getSourceRelation, getThroughRelation } from './utils';
26
+ import {
27
+ getSourceRelation,
28
+ getThroughRelation,
29
+ hasRelationHandleCreate,
30
+ hasRelationHandleUpdate,
31
+ NestedInsertManyItems,
32
+ NestedUpdateManyItems,
33
+ } from './utils';
26
34
 
27
35
  export interface HasMany extends RelationThunkBase {
28
36
  type: 'hasMany';
@@ -56,6 +64,65 @@ export type HasManyInfo<
56
64
  chainedDelete: true;
57
65
  };
58
66
 
67
+ type State = {
68
+ query: Query;
69
+ primaryKey: string;
70
+ foreignKey: string;
71
+ };
72
+
73
+ export type HasManyNestedUpdate = (
74
+ query: Query,
75
+ data: Record<string, unknown>[],
76
+ relationData: NestedUpdateManyItems,
77
+ ) => Promise<void>;
78
+
79
+ export type HasManyNestedInsert = (
80
+ query: Query,
81
+ data: [
82
+ selfData: Record<string, unknown>,
83
+ relationData: NestedInsertManyItems,
84
+ ][],
85
+ ) => Promise<void>;
86
+
87
+ class HasManyVirtualColumn extends VirtualColumn {
88
+ private readonly nestedInsert: HasManyNestedInsert;
89
+ private readonly nestedUpdate: HasManyNestedUpdate;
90
+
91
+ constructor(private key: string, private state: State) {
92
+ super();
93
+ this.nestedInsert = nestedInsert(state);
94
+ this.nestedUpdate = nestedUpdate(state);
95
+ }
96
+
97
+ create(
98
+ q: Query,
99
+ ctx: CreateCtx,
100
+ item: Record<string, unknown>,
101
+ rowIndex: number,
102
+ ) {
103
+ hasRelationHandleCreate(
104
+ q,
105
+ ctx,
106
+ item,
107
+ rowIndex,
108
+ this.key,
109
+ this.state.primaryKey,
110
+ this.nestedInsert,
111
+ );
112
+ }
113
+
114
+ update(q: Query, ctx: UpdateCtx, set: Record<string, unknown>) {
115
+ hasRelationHandleUpdate(
116
+ q,
117
+ ctx,
118
+ set,
119
+ this.key,
120
+ this.state.primaryKey,
121
+ this.nestedUpdate,
122
+ );
123
+ }
124
+ }
125
+
59
126
  export const makeHasManyMethod = (
60
127
  model: Query,
61
128
  relation: HasMany,
@@ -92,8 +159,6 @@ export const makeHasManyMethod = (
92
159
  whereExistsCallback as unknown as JoinCallback<Query, Query>,
93
160
  );
94
161
  },
95
- nestedInsert: undefined,
96
- nestedUpdate: undefined,
97
162
  joinQuery(fromQuery, toQuery) {
98
163
  return toQuery.whereExists<Query, Query>(
99
164
  throughRelation.joinQuery(fromQuery, throughRelation.query),
@@ -123,6 +188,7 @@ export const makeHasManyMethod = (
123
188
  }
124
189
 
125
190
  const { primaryKey, foreignKey } = relation.options;
191
+ const state: State = { query, primaryKey, foreignKey };
126
192
 
127
193
  const fromQuerySelect = [{ selectAs: { [foreignKey]: primaryKey } }];
128
194
 
@@ -132,167 +198,7 @@ export const makeHasManyMethod = (
132
198
  const values = { [foreignKey]: params[primaryKey] };
133
199
  return query.where(values)._defaults(values);
134
200
  },
135
- nestedInsert: (async (q, data) => {
136
- const connect = data.filter(
137
- (
138
- item,
139
- ): item is [
140
- Record<string, unknown>,
141
- { connect: WhereArg<QueryBase>[] },
142
- ] => Boolean(item[1].connect),
143
- );
144
-
145
- const t = query.transacting(q);
146
-
147
- if (connect.length) {
148
- await Promise.all(
149
- connect.flatMap(([selfData, { connect }]) =>
150
- t
151
- .or<Query>(...connect)
152
- ._updateOrThrow({ [foreignKey]: selfData[primaryKey] }),
153
- ),
154
- );
155
- }
156
-
157
- const connectOrCreate = data.filter(
158
- (
159
- item,
160
- ): item is [
161
- Record<string, unknown>,
162
- {
163
- connectOrCreate: {
164
- where: WhereArg<QueryBase>;
165
- create: Record<string, unknown>;
166
- }[];
167
- },
168
- ] => Boolean(item[1].connectOrCreate),
169
- );
170
-
171
- let connected: number[];
172
- if (connectOrCreate.length) {
173
- connected = await Promise.all(
174
- connectOrCreate.flatMap(([selfData, { connectOrCreate }]) =>
175
- connectOrCreate.map((item) =>
176
- (
177
- t.where(item.where) as WhereResult<Query & { hasSelect: false }>
178
- )._update({
179
- [foreignKey]: selfData[primaryKey],
180
- }),
181
- ),
182
- ),
183
- );
184
- } else {
185
- connected = [];
186
- }
187
-
188
- let connectedI = 0;
189
- const create = data.filter(
190
- (
191
- item,
192
- ): item is [
193
- Record<string, unknown>,
194
- {
195
- create?: Record<string, unknown>[];
196
- connectOrCreate?: {
197
- where: WhereArg<QueryBase>;
198
- create: Record<string, unknown>;
199
- }[];
200
- },
201
- ] => {
202
- if (item[1].connectOrCreate) {
203
- const length = item[1].connectOrCreate.length;
204
- connectedI += length;
205
- for (let i = length; i > 0; i--) {
206
- if (connected[connectedI - i] === 0) return true;
207
- }
208
- }
209
- return Boolean(item[1].create);
210
- },
211
- );
212
-
213
- connectedI = 0;
214
- if (create.length) {
215
- await t._createMany(
216
- create.flatMap(
217
- ([selfData, { create = [], connectOrCreate = [] }]) => {
218
- return [
219
- ...create.map((item) => ({
220
- [foreignKey]: selfData[primaryKey],
221
- ...item,
222
- })),
223
- ...connectOrCreate
224
- .filter(() => connected[connectedI++] === 0)
225
- .map((item) => ({
226
- [foreignKey]: selfData[primaryKey],
227
- ...item.create,
228
- })),
229
- ];
230
- },
231
- ) as CreateData<Query>[],
232
- );
233
- }
234
- }) as HasManyNestedInsert,
235
- nestedUpdate: (async (q, data, params) => {
236
- if ((params.set || params.create) && isQueryReturnsAll(q)) {
237
- const key = params.set ? 'set' : 'create';
238
- throw new Error(`\`${key}\` option is not allowed in a batch update`);
239
- }
240
-
241
- const t = query.transacting(q);
242
- if (params.create) {
243
- await t._count()._createMany(
244
- params.create.map((create) => ({
245
- ...create,
246
- [foreignKey]: data[0][primaryKey],
247
- })),
248
- );
249
- delete t.query[toSqlCacheKey];
250
- }
251
-
252
- if (params.disconnect || params.set) {
253
- await t
254
- .where<Query>(
255
- getWhereForNestedUpdate(
256
- data,
257
- params.disconnect,
258
- primaryKey,
259
- foreignKey,
260
- ),
261
- )
262
- ._update({ [foreignKey]: null });
263
-
264
- if (params.set) {
265
- delete t.query[toSqlCacheKey];
266
- await t
267
- .where<Query>(
268
- Array.isArray(params.set)
269
- ? {
270
- OR: params.set,
271
- }
272
- : params.set,
273
- )
274
- ._update({ [foreignKey]: data[0][primaryKey] });
275
- }
276
- }
277
-
278
- if (params.delete || params.update) {
279
- delete t.query[toSqlCacheKey];
280
- const q = t._where(
281
- getWhereForNestedUpdate(
282
- data,
283
- params.delete || params.update?.where,
284
- primaryKey,
285
- foreignKey,
286
- ),
287
- );
288
-
289
- if (params.delete) {
290
- await q._delete();
291
- } else if (params.update) {
292
- await q._update<WhereResult<Query>>(params.update.data);
293
- }
294
- }
295
- }) as HasManyNestedUpdate,
201
+ virtualColumn: new HasManyVirtualColumn(relationName, state),
296
202
  joinQuery(fromQuery, toQuery) {
297
203
  return addQueryOn(toQuery, fromQuery, toQuery, foreignKey, primaryKey);
298
204
  },
@@ -328,3 +234,168 @@ const getWhereForNestedUpdate = (
328
234
  }
329
235
  return where;
330
236
  };
237
+
238
+ const nestedInsert = ({ query, primaryKey, foreignKey }: State) => {
239
+ return (async (q, data) => {
240
+ const connect = data.filter(
241
+ (
242
+ item,
243
+ ): item is [
244
+ Record<string, unknown>,
245
+ { connect: WhereArg<QueryBase>[] },
246
+ ] => Boolean(item[1].connect),
247
+ );
248
+
249
+ const t = query.transacting(q);
250
+
251
+ if (connect.length) {
252
+ await Promise.all(
253
+ connect.flatMap(([selfData, { connect }]) =>
254
+ t
255
+ .or<Query>(...connect)
256
+ ._updateOrThrow({ [foreignKey]: selfData[primaryKey] }),
257
+ ),
258
+ );
259
+ }
260
+
261
+ const connectOrCreate = data.filter(
262
+ (
263
+ item,
264
+ ): item is [
265
+ Record<string, unknown>,
266
+ {
267
+ connectOrCreate: {
268
+ where: WhereArg<QueryBase>;
269
+ create: Record<string, unknown>;
270
+ }[];
271
+ },
272
+ ] => Boolean(item[1].connectOrCreate),
273
+ );
274
+
275
+ let connected: number[];
276
+ if (connectOrCreate.length) {
277
+ connected = await Promise.all(
278
+ connectOrCreate.flatMap(([selfData, { connectOrCreate }]) =>
279
+ connectOrCreate.map((item) =>
280
+ (
281
+ t.where(item.where) as WhereResult<Query & { hasSelect: false }>
282
+ )._update({
283
+ [foreignKey]: selfData[primaryKey],
284
+ }),
285
+ ),
286
+ ),
287
+ );
288
+ } else {
289
+ connected = [];
290
+ }
291
+
292
+ let connectedI = 0;
293
+ const create = data.filter(
294
+ (
295
+ item,
296
+ ): item is [
297
+ Record<string, unknown>,
298
+ {
299
+ create?: Record<string, unknown>[];
300
+ connectOrCreate?: {
301
+ where: WhereArg<QueryBase>;
302
+ create: Record<string, unknown>;
303
+ }[];
304
+ },
305
+ ] => {
306
+ if (item[1].connectOrCreate) {
307
+ const length = item[1].connectOrCreate.length;
308
+ connectedI += length;
309
+ for (let i = length; i > 0; i--) {
310
+ if (connected[connectedI - i] === 0) return true;
311
+ }
312
+ }
313
+ return Boolean(item[1].create);
314
+ },
315
+ );
316
+
317
+ connectedI = 0;
318
+ if (create.length) {
319
+ await t._createMany(
320
+ create.flatMap(([selfData, { create = [], connectOrCreate = [] }]) => {
321
+ return [
322
+ ...create.map((item) => ({
323
+ [foreignKey]: selfData[primaryKey],
324
+ ...item,
325
+ })),
326
+ ...connectOrCreate
327
+ .filter(() => connected[connectedI++] === 0)
328
+ .map((item) => ({
329
+ [foreignKey]: selfData[primaryKey],
330
+ ...item.create,
331
+ })),
332
+ ];
333
+ }) as CreateData<Query>[],
334
+ );
335
+ }
336
+ }) as HasManyNestedInsert;
337
+ };
338
+
339
+ const nestedUpdate = ({ query, primaryKey, foreignKey }: State) => {
340
+ return (async (q, data, params) => {
341
+ if ((params.set || params.create) && isQueryReturnsAll(q)) {
342
+ const key = params.set ? 'set' : 'create';
343
+ throw new Error(`\`${key}\` option is not allowed in a batch update`);
344
+ }
345
+
346
+ const t = query.transacting(q);
347
+ if (params.create) {
348
+ await t._count()._createMany(
349
+ params.create.map((create) => ({
350
+ ...create,
351
+ [foreignKey]: data[0][primaryKey],
352
+ })),
353
+ );
354
+ delete t.query[toSqlCacheKey];
355
+ }
356
+
357
+ if (params.disconnect || params.set) {
358
+ await t
359
+ .where<Query>(
360
+ getWhereForNestedUpdate(
361
+ data,
362
+ params.disconnect,
363
+ primaryKey,
364
+ foreignKey,
365
+ ),
366
+ )
367
+ ._update({ [foreignKey]: null });
368
+
369
+ if (params.set) {
370
+ delete t.query[toSqlCacheKey];
371
+ await t
372
+ .where<Query>(
373
+ Array.isArray(params.set)
374
+ ? {
375
+ OR: params.set,
376
+ }
377
+ : params.set,
378
+ )
379
+ ._update({ [foreignKey]: data[0][primaryKey] });
380
+ }
381
+ }
382
+
383
+ if (params.delete || params.update) {
384
+ delete t.query[toSqlCacheKey];
385
+ const q = t._where(
386
+ getWhereForNestedUpdate(
387
+ data,
388
+ params.delete || params.update?.where,
389
+ primaryKey,
390
+ foreignKey,
391
+ ),
392
+ );
393
+
394
+ if (params.delete) {
395
+ await q._delete();
396
+ } else if (params.update) {
397
+ await q._update<WhereResult<Query>>(params.update.data);
398
+ }
399
+ }
400
+ }) as HasManyNestedUpdate;
401
+ };