pqb 0.0.1
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/dist/index.d.ts +3630 -0
- package/dist/index.esm.js +4587 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +4691 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/rollup.config.js +35 -0
- package/src/adapter.test.ts +10 -0
- package/src/adapter.ts +171 -0
- package/src/columnSchema/array.ts +21 -0
- package/src/columnSchema/boolean.ts +10 -0
- package/src/columnSchema/columnType.test.ts +129 -0
- package/src/columnSchema/columnType.ts +77 -0
- package/src/columnSchema/columnTypes.ts +145 -0
- package/src/columnSchema/columnsSchema.test.ts +32 -0
- package/src/columnSchema/columnsSchema.ts +100 -0
- package/src/columnSchema/commonMethods.ts +130 -0
- package/src/columnSchema/dateTime.ts +104 -0
- package/src/columnSchema/enum.ts +13 -0
- package/src/columnSchema/index.ts +11 -0
- package/src/columnSchema/json/array.ts +55 -0
- package/src/columnSchema/json/discriminatedUnion.ts +91 -0
- package/src/columnSchema/json/enum.ts +29 -0
- package/src/columnSchema/json/instanceOf.ts +16 -0
- package/src/columnSchema/json/intersection.ts +23 -0
- package/src/columnSchema/json/lazy.ts +22 -0
- package/src/columnSchema/json/literal.ts +12 -0
- package/src/columnSchema/json/map.ts +29 -0
- package/src/columnSchema/json/nativeEnum.ts +30 -0
- package/src/columnSchema/json/nullable.ts +33 -0
- package/src/columnSchema/json/nullish.ts +30 -0
- package/src/columnSchema/json/object.ts +206 -0
- package/src/columnSchema/json/optional.ts +28 -0
- package/src/columnSchema/json/record.ts +40 -0
- package/src/columnSchema/json/scalarTypes.ts +117 -0
- package/src/columnSchema/json/set.ts +34 -0
- package/src/columnSchema/json/tuple.ts +40 -0
- package/src/columnSchema/json/typeBase.ts +202 -0
- package/src/columnSchema/json/union.ts +16 -0
- package/src/columnSchema/json.ts +64 -0
- package/src/columnSchema/number.ts +122 -0
- package/src/columnSchema/string.ts +222 -0
- package/src/columnSchema/utils.ts +27 -0
- package/src/common.ts +86 -0
- package/src/db.test.ts +67 -0
- package/src/db.ts +212 -0
- package/src/errors.ts +7 -0
- package/src/index.ts +18 -0
- package/src/operators.test.ts +608 -0
- package/src/operators.ts +177 -0
- package/src/query.ts +292 -0
- package/src/queryDataUtils.ts +50 -0
- package/src/queryMethods/aggregate.test.ts +583 -0
- package/src/queryMethods/aggregate.ts +878 -0
- package/src/queryMethods/callbacks.test.ts +69 -0
- package/src/queryMethods/callbacks.ts +55 -0
- package/src/queryMethods/clear.test.ts +64 -0
- package/src/queryMethods/clear.ts +58 -0
- package/src/queryMethods/columnInfo.test.ts +45 -0
- package/src/queryMethods/columnInfo.ts +67 -0
- package/src/queryMethods/delete.test.ts +135 -0
- package/src/queryMethods/delete.ts +50 -0
- package/src/queryMethods/for.test.ts +57 -0
- package/src/queryMethods/for.ts +99 -0
- package/src/queryMethods/from.test.ts +66 -0
- package/src/queryMethods/from.ts +58 -0
- package/src/queryMethods/get.test.ts +66 -0
- package/src/queryMethods/get.ts +88 -0
- package/src/queryMethods/having.test.ts +247 -0
- package/src/queryMethods/having.ts +99 -0
- package/src/queryMethods/insert.test.ts +555 -0
- package/src/queryMethods/insert.ts +453 -0
- package/src/queryMethods/join.test.ts +150 -0
- package/src/queryMethods/join.ts +508 -0
- package/src/queryMethods/json.test.ts +398 -0
- package/src/queryMethods/json.ts +259 -0
- package/src/queryMethods/log.test.ts +172 -0
- package/src/queryMethods/log.ts +123 -0
- package/src/queryMethods/queryMethods.test.ts +629 -0
- package/src/queryMethods/queryMethods.ts +428 -0
- package/src/queryMethods/select.test.ts +479 -0
- package/src/queryMethods/select.ts +249 -0
- package/src/queryMethods/then.ts +236 -0
- package/src/queryMethods/transaction.test.ts +66 -0
- package/src/queryMethods/transaction.ts +66 -0
- package/src/queryMethods/union.test.ts +59 -0
- package/src/queryMethods/union.ts +89 -0
- package/src/queryMethods/update.test.ts +417 -0
- package/src/queryMethods/update.ts +350 -0
- package/src/queryMethods/upsert.test.ts +56 -0
- package/src/queryMethods/upsert.ts +43 -0
- package/src/queryMethods/where.test.ts +1594 -0
- package/src/queryMethods/where.ts +450 -0
- package/src/queryMethods/window.test.ts +66 -0
- package/src/queryMethods/window.ts +108 -0
- package/src/queryMethods/with.test.ts +191 -0
- package/src/queryMethods/with.ts +92 -0
- package/src/quote.ts +36 -0
- package/src/relations.ts +194 -0
- package/src/sql/aggregate.ts +80 -0
- package/src/sql/columnInfo.ts +22 -0
- package/src/sql/common.ts +42 -0
- package/src/sql/delete.ts +41 -0
- package/src/sql/distinct.ts +19 -0
- package/src/sql/fromAndAs.ts +51 -0
- package/src/sql/having.ts +140 -0
- package/src/sql/index.ts +2 -0
- package/src/sql/insert.ts +102 -0
- package/src/sql/join.ts +242 -0
- package/src/sql/orderBy.ts +41 -0
- package/src/sql/select.ts +153 -0
- package/src/sql/toSql.ts +153 -0
- package/src/sql/truncate.ts +13 -0
- package/src/sql/types.ts +355 -0
- package/src/sql/update.ts +62 -0
- package/src/sql/where.ts +314 -0
- package/src/sql/window.ts +38 -0
- package/src/sql/with.ts +32 -0
- package/src/test-utils.ts +172 -0
- package/src/utils.ts +140 -0
- package/tsconfig.build.json +6 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { Query, SetQueryReturnsRowCount } from '../query';
|
|
2
|
+
import { pushQueryArray, pushQueryValue } from '../queryDataUtils';
|
|
3
|
+
import { isRaw, RawExpression } from '../common';
|
|
4
|
+
import {
|
|
5
|
+
BelongsToNestedUpdate,
|
|
6
|
+
HasOneNestedUpdate,
|
|
7
|
+
NestedUpdateOneItem,
|
|
8
|
+
Relation,
|
|
9
|
+
} from '../relations';
|
|
10
|
+
import { WhereArg, WhereResult } from './where';
|
|
11
|
+
import { MaybeArray } from '../utils';
|
|
12
|
+
import { InsertData } from './insert';
|
|
13
|
+
import { parseResult, queryMethodByReturnType } from './then';
|
|
14
|
+
|
|
15
|
+
export type UpdateData<T extends Query> = {
|
|
16
|
+
[K in keyof T['type']]?: T['type'][K] | RawExpression;
|
|
17
|
+
} & (T['relations'] extends Record<string, Relation>
|
|
18
|
+
? {
|
|
19
|
+
[K in keyof T['relations']]?: T['relations'][K]['type'] extends 'belongsTo'
|
|
20
|
+
?
|
|
21
|
+
| { disconnect: boolean }
|
|
22
|
+
| { set: WhereArg<T['relations'][K]['model']> }
|
|
23
|
+
| { delete: boolean }
|
|
24
|
+
| { update: UpdateData<T['relations'][K]['model']> }
|
|
25
|
+
| (T['returnType'] extends 'one' | 'oneOrThrow'
|
|
26
|
+
?
|
|
27
|
+
| {
|
|
28
|
+
create: InsertData<
|
|
29
|
+
T['relations'][K]['nestedCreateQuery']
|
|
30
|
+
>;
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
upsert: {
|
|
34
|
+
update: UpdateData<T['relations'][K]['model']>;
|
|
35
|
+
create: InsertData<
|
|
36
|
+
T['relations'][K]['nestedCreateQuery']
|
|
37
|
+
>;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
: never)
|
|
41
|
+
: T['relations'][K]['type'] extends 'hasOne'
|
|
42
|
+
?
|
|
43
|
+
| { disconnect: boolean }
|
|
44
|
+
| { delete: boolean }
|
|
45
|
+
| { update: UpdateData<T['relations'][K]['model']> }
|
|
46
|
+
| (T['returnType'] extends 'one' | 'oneOrThrow'
|
|
47
|
+
?
|
|
48
|
+
| { set: WhereArg<T['relations'][K]['model']> }
|
|
49
|
+
| {
|
|
50
|
+
upsert: {
|
|
51
|
+
update: UpdateData<T['relations'][K]['model']>;
|
|
52
|
+
create: InsertData<
|
|
53
|
+
T['relations'][K]['nestedCreateQuery']
|
|
54
|
+
>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
create: InsertData<
|
|
59
|
+
T['relations'][K]['nestedCreateQuery']
|
|
60
|
+
>;
|
|
61
|
+
}
|
|
62
|
+
: never)
|
|
63
|
+
: T['relations'][K]['type'] extends 'hasMany'
|
|
64
|
+
?
|
|
65
|
+
| { disconnect: MaybeArray<WhereArg<T['relations'][K]['model']>> }
|
|
66
|
+
| { delete: MaybeArray<WhereArg<T['relations'][K]['model']>> }
|
|
67
|
+
| {
|
|
68
|
+
update: {
|
|
69
|
+
where: MaybeArray<WhereArg<T['relations'][K]['model']>>;
|
|
70
|
+
data: UpdateData<T['relations'][K]['model']>;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
| (T['returnType'] extends 'one' | 'oneOrThrow'
|
|
74
|
+
?
|
|
75
|
+
| { set: MaybeArray<WhereArg<T['relations'][K]['model']>> }
|
|
76
|
+
| {
|
|
77
|
+
create: InsertData<
|
|
78
|
+
T['relations'][K]['nestedCreateQuery']
|
|
79
|
+
>[];
|
|
80
|
+
}
|
|
81
|
+
: never)
|
|
82
|
+
: T['relations'][K]['type'] extends 'hasAndBelongsToMany'
|
|
83
|
+
?
|
|
84
|
+
| { disconnect: MaybeArray<WhereArg<T['relations'][K]['model']>> }
|
|
85
|
+
| {
|
|
86
|
+
set: MaybeArray<WhereArg<T['relations'][K]['model']>>;
|
|
87
|
+
}
|
|
88
|
+
| { delete: MaybeArray<WhereArg<T['relations'][K]['model']>> }
|
|
89
|
+
| {
|
|
90
|
+
update: {
|
|
91
|
+
where: MaybeArray<WhereArg<T['relations'][K]['model']>>;
|
|
92
|
+
data: UpdateData<T['relations'][K]['model']>;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
| {
|
|
96
|
+
create: InsertData<T['relations'][K]['nestedCreateQuery']>[];
|
|
97
|
+
}
|
|
98
|
+
: never;
|
|
99
|
+
}
|
|
100
|
+
: // eslint-disable-next-line @typescript-eslint/ban-types
|
|
101
|
+
{});
|
|
102
|
+
|
|
103
|
+
type UpdateArgs<T extends Query, ForceAll extends boolean> = (
|
|
104
|
+
T['hasWhere'] extends true ? true : ForceAll
|
|
105
|
+
) extends true
|
|
106
|
+
? [update: RawExpression | UpdateData<T>, forceAll?: ForceAll]
|
|
107
|
+
: [update: RawExpression | UpdateData<T>, forceAll: true];
|
|
108
|
+
|
|
109
|
+
type UpdateResult<T extends Query> = T['hasSelect'] extends false
|
|
110
|
+
? SetQueryReturnsRowCount<T>
|
|
111
|
+
: T;
|
|
112
|
+
|
|
113
|
+
type ChangeCountArg<T extends Query> =
|
|
114
|
+
| keyof T['shape']
|
|
115
|
+
| Partial<Record<keyof T['shape'], number>>;
|
|
116
|
+
|
|
117
|
+
const applyCountChange = <T extends Query>(
|
|
118
|
+
self: T,
|
|
119
|
+
op: string,
|
|
120
|
+
data: ChangeCountArg<T>,
|
|
121
|
+
) => {
|
|
122
|
+
self.query.type = 'update';
|
|
123
|
+
|
|
124
|
+
let map: Record<string, { op: string; arg: number }>;
|
|
125
|
+
if (typeof data === 'object') {
|
|
126
|
+
map = {};
|
|
127
|
+
for (const key in data) {
|
|
128
|
+
map[key] = { op, arg: data[key] as number };
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
map = { [data as string]: { op, arg: 1 } };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pushQueryValue(self, 'data', map);
|
|
135
|
+
return self as unknown as UpdateResult<T>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export class Update {
|
|
139
|
+
update<T extends Query, ForceAll extends boolean = false>(
|
|
140
|
+
this: T,
|
|
141
|
+
...args: UpdateArgs<T, ForceAll>
|
|
142
|
+
): UpdateResult<T> {
|
|
143
|
+
const q = this.clone() as T;
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
return q._update(...(args as any));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_update<T extends Query, ForceAll extends boolean = false>(
|
|
149
|
+
this: T,
|
|
150
|
+
...args: UpdateArgs<T, ForceAll>
|
|
151
|
+
): UpdateResult<T> {
|
|
152
|
+
const [data, forceAll] = args as unknown as [
|
|
153
|
+
Record<string, unknown>,
|
|
154
|
+
boolean | undefined,
|
|
155
|
+
];
|
|
156
|
+
const { query } = this;
|
|
157
|
+
query.type = 'update';
|
|
158
|
+
|
|
159
|
+
if (!query.and?.length && !query.or?.length && !forceAll) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
'No where conditions or forceAll flag provided to update',
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isRaw(data)) {
|
|
166
|
+
pushQueryValue(this, 'data', data);
|
|
167
|
+
} else {
|
|
168
|
+
const relations = this.relations as Record<string, Relation>;
|
|
169
|
+
|
|
170
|
+
const prependRelations: Record<string, Record<string, unknown>> = {};
|
|
171
|
+
const appendRelations: Record<string, Record<string, unknown>> = {};
|
|
172
|
+
|
|
173
|
+
const originalReturnType = this.query.returnType;
|
|
174
|
+
|
|
175
|
+
const update: Record<string, unknown> = { ...data };
|
|
176
|
+
for (const key in data) {
|
|
177
|
+
if (relations[key]) {
|
|
178
|
+
delete update[key];
|
|
179
|
+
if (relations[key].type === 'belongsTo') {
|
|
180
|
+
prependRelations[key] = data[key] as Record<string, unknown>;
|
|
181
|
+
} else {
|
|
182
|
+
if (!query.select?.includes('*')) {
|
|
183
|
+
const primaryKey = relations[key].primaryKey;
|
|
184
|
+
if (!query.select?.includes(primaryKey)) {
|
|
185
|
+
this._select(primaryKey);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
appendRelations[key] = data[key] as Record<string, unknown>;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const state: {
|
|
194
|
+
updateLater?: Record<string, unknown>;
|
|
195
|
+
updateLaterPromises?: Promise<void>[];
|
|
196
|
+
} = {};
|
|
197
|
+
|
|
198
|
+
const prependRelationKeys = Object.keys(prependRelations);
|
|
199
|
+
if (prependRelationKeys.length) {
|
|
200
|
+
const willSetKeys = prependRelationKeys.some((relationName) => {
|
|
201
|
+
const data = prependRelations[relationName] as NestedUpdateOneItem;
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
relations[relationName] as {
|
|
205
|
+
nestedUpdate: BelongsToNestedUpdate;
|
|
206
|
+
}
|
|
207
|
+
).nestedUpdate(this, update, data, state);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (!willSetKeys && !Object.keys(update).length) {
|
|
211
|
+
delete this.query.type;
|
|
212
|
+
}
|
|
213
|
+
} else if (!Object.keys(update).length) {
|
|
214
|
+
delete this.query.type;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const appendRelationKeys = Object.keys(appendRelations);
|
|
218
|
+
|
|
219
|
+
let resultOfTypeAll: Record<string, unknown>[] | undefined;
|
|
220
|
+
|
|
221
|
+
if (
|
|
222
|
+
state?.updateLater ||
|
|
223
|
+
(appendRelationKeys.length && originalReturnType !== 'all')
|
|
224
|
+
) {
|
|
225
|
+
this.query.returnType = 'all';
|
|
226
|
+
|
|
227
|
+
if (state?.updateLater) {
|
|
228
|
+
this.schema.primaryKeys.forEach((key: string) => {
|
|
229
|
+
if (!query.select?.includes('*') && !query.select?.includes(key)) {
|
|
230
|
+
this._select(key);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const { handleResult } = this.query;
|
|
236
|
+
this.query.handleResult = async (q, queryResult) => {
|
|
237
|
+
resultOfTypeAll = (await handleResult(q, queryResult)) as Record<
|
|
238
|
+
string,
|
|
239
|
+
unknown
|
|
240
|
+
>[];
|
|
241
|
+
|
|
242
|
+
if (state?.updateLater) {
|
|
243
|
+
await Promise.all(state.updateLaterPromises as Promise<void>[]);
|
|
244
|
+
|
|
245
|
+
const t = this.__model.clone().transacting(q);
|
|
246
|
+
const keys = this.schema.primaryKeys as string[];
|
|
247
|
+
(
|
|
248
|
+
t._whereIn as unknown as (
|
|
249
|
+
keys: string[],
|
|
250
|
+
values: unknown[][],
|
|
251
|
+
) => Query
|
|
252
|
+
)(
|
|
253
|
+
keys,
|
|
254
|
+
resultOfTypeAll.map((item) => keys.map((key) => item[key])),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
await (t as WhereResult<Query>)._update(state.updateLater);
|
|
258
|
+
|
|
259
|
+
resultOfTypeAll.forEach((item) =>
|
|
260
|
+
Object.assign(item, state.updateLater),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (queryMethodByReturnType[originalReturnType] === 'arrays') {
|
|
265
|
+
queryResult.rows.forEach(
|
|
266
|
+
(row, i) =>
|
|
267
|
+
((queryResult.rows as unknown as unknown[][])[i] =
|
|
268
|
+
Object.values(row)),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return parseResult(q, originalReturnType, queryResult);
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (appendRelationKeys.length) {
|
|
277
|
+
pushQueryArray(
|
|
278
|
+
this,
|
|
279
|
+
'afterQuery',
|
|
280
|
+
appendRelationKeys.map((relationName) => {
|
|
281
|
+
return (q: Query, result: Record<string, unknown>[]) => {
|
|
282
|
+
const all = resultOfTypeAll || result;
|
|
283
|
+
return (
|
|
284
|
+
relations[relationName].nestedUpdate as HasOneNestedUpdate
|
|
285
|
+
)?.(q, all, appendRelations[relationName] as NestedUpdateOneItem);
|
|
286
|
+
};
|
|
287
|
+
}),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (prependRelationKeys.length || appendRelationKeys.length) {
|
|
292
|
+
query.wrapInTransaction = true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
pushQueryValue(this, 'data', update);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!query.select) {
|
|
299
|
+
this.query.returnType = 'rowCount';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return this as unknown as UpdateResult<T>;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
updateOrThrow<T extends Query, ForceAll extends boolean = false>(
|
|
306
|
+
this: T,
|
|
307
|
+
...args: UpdateArgs<T, ForceAll>
|
|
308
|
+
): UpdateResult<T> {
|
|
309
|
+
const q = this.clone() as T;
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
311
|
+
return q._updateOrThrow(...(args as any));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_updateOrThrow<T extends Query, ForceAll extends boolean = false>(
|
|
315
|
+
this: T,
|
|
316
|
+
...args: UpdateArgs<T, ForceAll>
|
|
317
|
+
): UpdateResult<T> {
|
|
318
|
+
this.query.throwOnNotFound = true;
|
|
319
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
320
|
+
return this._update(...(args as any)) as unknown as UpdateResult<T>;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
increment<T extends Query>(
|
|
324
|
+
this: T,
|
|
325
|
+
data: ChangeCountArg<T>,
|
|
326
|
+
): UpdateResult<T> {
|
|
327
|
+
return this.clone()._increment(data) as unknown as UpdateResult<T>;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
_increment<T extends Query>(
|
|
331
|
+
this: T,
|
|
332
|
+
data: ChangeCountArg<T>,
|
|
333
|
+
): UpdateResult<T> {
|
|
334
|
+
return applyCountChange(this, '+', data);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
decrement<T extends Query>(
|
|
338
|
+
this: T,
|
|
339
|
+
data: ChangeCountArg<T>,
|
|
340
|
+
): UpdateResult<T> {
|
|
341
|
+
return this.clone()._decrement(data) as unknown as UpdateResult<T>;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
_decrement<T extends Query>(
|
|
345
|
+
this: T,
|
|
346
|
+
data: ChangeCountArg<T>,
|
|
347
|
+
): UpdateResult<T> {
|
|
348
|
+
return applyCountChange(this, '-', data);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { AssertEqual, User, userData, useTestDatabase } from '../test-utils';
|
|
2
|
+
|
|
3
|
+
describe('upsert', () => {
|
|
4
|
+
useTestDatabase();
|
|
5
|
+
|
|
6
|
+
it('should return void by default', () => {
|
|
7
|
+
const query = User.find(1).upsert({
|
|
8
|
+
update: { name: 'name' },
|
|
9
|
+
create: userData,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const eq: AssertEqual<Awaited<typeof query>, void> = true;
|
|
13
|
+
expect(eq).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should update record if exists', async () => {
|
|
17
|
+
const { id } = await User.create(userData);
|
|
18
|
+
|
|
19
|
+
const user = await User.selectAll()
|
|
20
|
+
.find(id)
|
|
21
|
+
.upsert({
|
|
22
|
+
update: {
|
|
23
|
+
name: 'updated',
|
|
24
|
+
},
|
|
25
|
+
create: userData,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(user.name).toBe('updated');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should create record if not exists', async () => {
|
|
32
|
+
const user = await User.selectAll()
|
|
33
|
+
.find(123)
|
|
34
|
+
.upsert({
|
|
35
|
+
update: {
|
|
36
|
+
name: 'updated',
|
|
37
|
+
},
|
|
38
|
+
create: { ...userData, name: 'created' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(user.name).toBe('created');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should throw if more than one row was updated', async () => {
|
|
45
|
+
await User.create([userData, userData]);
|
|
46
|
+
|
|
47
|
+
await expect(
|
|
48
|
+
User.findBy({ name: userData.name }).upsert({
|
|
49
|
+
update: {
|
|
50
|
+
name: 'updated',
|
|
51
|
+
},
|
|
52
|
+
create: userData,
|
|
53
|
+
}),
|
|
54
|
+
).rejects.toThrow();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Query, SetQueryReturnsOne, SetQueryReturnsVoid } from '../query';
|
|
2
|
+
import { UpdateData } from './update';
|
|
3
|
+
import { InsertData } from './insert';
|
|
4
|
+
import { WhereResult } from './where';
|
|
5
|
+
import { MoreThanOneRowError } from '../errors';
|
|
6
|
+
|
|
7
|
+
export type UpsertData<T extends Query> = {
|
|
8
|
+
update: UpdateData<T>;
|
|
9
|
+
create: InsertData<T>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type UpsertResult<T extends Query> = T['hasSelect'] extends true
|
|
13
|
+
? SetQueryReturnsOne<T>
|
|
14
|
+
: SetQueryReturnsVoid<T>;
|
|
15
|
+
|
|
16
|
+
export type UpsertThis = WhereResult<Query> & {
|
|
17
|
+
returnType: 'one' | 'oneOrThrow';
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class QueryUpsert {
|
|
21
|
+
upsert<T extends UpsertThis>(this: T, data: UpsertData<T>): UpsertResult<T> {
|
|
22
|
+
return this.clone()._upsert(data);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_upsert<T extends UpsertThis>(this: T, data: UpsertData<T>): UpsertResult<T> {
|
|
26
|
+
this._update<WhereResult<Query>>(data.update);
|
|
27
|
+
this.query.returnType = 'one';
|
|
28
|
+
this.query.wrapInTransaction = true;
|
|
29
|
+
const { handleResult } = this.query;
|
|
30
|
+
this.query.handleResult = async (q, queryResult) => {
|
|
31
|
+
if (queryResult.rowCount === 0) {
|
|
32
|
+
return q.insert(data.create);
|
|
33
|
+
} else if (queryResult.rowCount > 1) {
|
|
34
|
+
throw new MoreThanOneRowError(
|
|
35
|
+
`Only one row was expected to find for upsert, found ${queryResult.rowCount} rows.`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return handleResult(q, queryResult);
|
|
40
|
+
};
|
|
41
|
+
return this as unknown as UpsertResult<T>;
|
|
42
|
+
}
|
|
43
|
+
}
|