pqb 0.3.2 → 0.3.4

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.
@@ -0,0 +1,471 @@
1
+ import { assertType, expectSql, Message, Profile, User } from '../test-utils';
2
+ import { QueryReturnType } from '../query';
3
+ import { IntegerColumn, TextColumn } from '../columnSchema';
4
+ import { getValueKey } from './get';
5
+ import { logParamToLogObject } from './log';
6
+ import {
7
+ ColumnInfoQueryData,
8
+ DeleteQueryData,
9
+ InsertQueryData,
10
+ SelectQueryData,
11
+ TruncateQueryData,
12
+ UpdateQueryData,
13
+ } from '../sql';
14
+ import { raw } from '../common';
15
+
16
+ describe('merge queries', () => {
17
+ describe('select', () => {
18
+ it('should use second select when no select', () => {
19
+ const q = User.merge(User.select('id'));
20
+
21
+ assertType<Awaited<typeof q>, { id: number }[]>();
22
+
23
+ expectSql(q.toSql(), `SELECT "user"."id" FROM "user"`);
24
+ });
25
+
26
+ it('should merge selects when both have it', () => {
27
+ const q = User.select('id').merge(User.select('name'));
28
+
29
+ assertType<Awaited<typeof q>, { id: number; name: string }[]>();
30
+
31
+ expectSql(q.toSql(), `SELECT "user"."id", "user"."name" FROM "user"`);
32
+ });
33
+ });
34
+
35
+ describe('returnType', () => {
36
+ it('should have default return type if none of the queries have it', () => {
37
+ const q = User.merge(User);
38
+
39
+ assertType<typeof q.returnType, QueryReturnType>();
40
+ });
41
+
42
+ it('should use left return type unless right has it', () => {
43
+ const q = User.take().merge(User);
44
+
45
+ assertType<typeof q.returnType, 'oneOrThrow'>();
46
+
47
+ expectSql(q.toSql(), `SELECT * FROM "user" LIMIT $1`, [1]);
48
+ });
49
+
50
+ it('should prefer right return type', () => {
51
+ const q = User.take().merge(User.all());
52
+
53
+ assertType<typeof q.returnType, 'all'>();
54
+
55
+ expectSql(q.toSql(), `SELECT * FROM "user"`);
56
+ });
57
+ });
58
+
59
+ describe('where', () => {
60
+ it('should use right where when left has no where', () => {
61
+ const q = User.merge(User.where({ id: 1 }));
62
+
63
+ assertType<typeof q.hasWhere, true>();
64
+
65
+ expectSql(q.toSql(), `SELECT * FROM "user" WHERE "user"."id" = $1`, [1]);
66
+ });
67
+
68
+ it('should merge where when both have it', () => {
69
+ const q = User.where({ id: 1, name: 'name' }).merge(
70
+ User.where({ id: 2 }),
71
+ );
72
+
73
+ assertType<typeof q.hasWhere, true>();
74
+
75
+ expectSql(
76
+ q.toSql(),
77
+ `
78
+ SELECT * FROM "user"
79
+ WHERE "user"."id" = $1 AND "user"."name" = $2 AND "user"."id" = $3
80
+ `,
81
+ [1, 'name', 2],
82
+ );
83
+ });
84
+ });
85
+
86
+ describe('join', () => {
87
+ it('should keep join from left and have joined table in selectable if right query does not have it', () => {
88
+ const joined = User.join(Message, 'userId', 'id');
89
+
90
+ const q = joined.merge(User);
91
+
92
+ assertType<typeof q.selectable, typeof joined.selectable>();
93
+ assertType<typeof q.joinedTables, typeof joined.joinedTables>();
94
+
95
+ expectSql(
96
+ q.toSql(),
97
+ `
98
+ SELECT "user".* FROM "user"
99
+ JOIN "message" ON "message"."userId" = "user"."id"
100
+ `,
101
+ );
102
+ });
103
+
104
+ it('should use join from right and have joined table in selectable when left query does not have it', () => {
105
+ const joined = User.join(Message, 'userId', 'id');
106
+
107
+ const q = User.merge(joined);
108
+
109
+ assertType<typeof q.selectable, typeof joined.selectable>();
110
+ assertType<typeof q.joinedTables, typeof joined.joinedTables>();
111
+
112
+ expectSql(
113
+ q.toSql(),
114
+ `
115
+ SELECT "user".* FROM "user"
116
+ JOIN "message" ON "message"."userId" = "user"."id"
117
+ `,
118
+ );
119
+ });
120
+
121
+ it('should merge joins when both have it', () => {
122
+ const left = User.join(Message, 'userId', 'id');
123
+ const right = User.join(Profile, 'userId', 'id');
124
+
125
+ const q = left.merge(right);
126
+
127
+ assertType<
128
+ typeof q.selectable,
129
+ typeof left.selectable & typeof right.selectable
130
+ >();
131
+ assertType<
132
+ typeof q.joinedTables,
133
+ typeof left.joinedTables & typeof right.joinedTables
134
+ >();
135
+
136
+ expectSql(
137
+ q.toSql(),
138
+ `
139
+ SELECT "user".* FROM "user"
140
+ JOIN "message" ON "message"."userId" = "user"."id"
141
+ JOIN "profile" ON "profile"."userId" = "user"."id"
142
+ `,
143
+ );
144
+ });
145
+ });
146
+
147
+ describe('windows', () => {
148
+ it('should keep windows from left when right does not have it', () => {
149
+ const q = User.window({
150
+ w: {
151
+ partitionBy: 'id',
152
+ },
153
+ }).merge(User);
154
+
155
+ assertType<typeof q.windows, { w: true }>();
156
+
157
+ expectSql(
158
+ q.toSql(),
159
+ `
160
+ SELECT * FROM "user"
161
+ WINDOW "w" AS (PARTITION BY "user"."id")
162
+ `,
163
+ );
164
+ });
165
+
166
+ it('should use windows from right when left does not have it', () => {
167
+ const q = User.merge(
168
+ User.window({
169
+ w: {
170
+ partitionBy: 'id',
171
+ },
172
+ }),
173
+ );
174
+
175
+ assertType<typeof q.windows, { w: true }>();
176
+
177
+ expectSql(
178
+ q.toSql(),
179
+ `
180
+ SELECT * FROM "user"
181
+ WINDOW "w" AS (PARTITION BY "user"."id")
182
+ `,
183
+ );
184
+ });
185
+
186
+ it('should merge windows when both have it', () => {
187
+ const q = User.window({
188
+ a: {
189
+ partitionBy: 'id',
190
+ },
191
+ }).merge(
192
+ User.window({
193
+ b: {
194
+ partitionBy: 'name',
195
+ },
196
+ }),
197
+ );
198
+
199
+ assertType<typeof q.windows, { a: true; b: true }>();
200
+
201
+ expectSql(
202
+ q.toSql(),
203
+ `
204
+ SELECT * FROM "user"
205
+ WINDOW "a" AS (PARTITION BY "user"."id"),
206
+ "b" AS (PARTITION BY "user"."name")
207
+ `,
208
+ );
209
+ });
210
+ });
211
+
212
+ describe('with', () => {
213
+ it('should keep with from left when right does not have it', () => {
214
+ const withQuery = User.select('id');
215
+
216
+ const q = User.with('withAlias', withQuery).merge(User);
217
+
218
+ assertType<
219
+ typeof q.withData,
220
+ {
221
+ withAlias: {
222
+ table: 'withAlias';
223
+ shape: typeof withQuery.result;
224
+ type: { id: number };
225
+ };
226
+ }
227
+ >();
228
+
229
+ expectSql(
230
+ q.toSql(),
231
+ `
232
+ WITH "withAlias" AS (
233
+ SELECT "user"."id" FROM "user"
234
+ )
235
+ SELECT * FROM "user"
236
+ `,
237
+ );
238
+ });
239
+
240
+ it('should use with from right when left does not have it', () => {
241
+ const withQuery = User.select('id');
242
+
243
+ const q = User.merge(User.with('withAlias', withQuery));
244
+
245
+ assertType<
246
+ typeof q.withData,
247
+ {
248
+ withAlias: {
249
+ table: 'withAlias';
250
+ shape: typeof withQuery.result;
251
+ type: { id: number };
252
+ };
253
+ }
254
+ >();
255
+
256
+ expectSql(
257
+ q.toSql(),
258
+ `
259
+ WITH "withAlias" AS (
260
+ SELECT "user"."id" FROM "user"
261
+ )
262
+ SELECT * FROM "user"
263
+ `,
264
+ );
265
+ });
266
+
267
+ it('should merge withes when both have it', () => {
268
+ const firstWith = User.select('id');
269
+ const secondWith = User.select('name');
270
+
271
+ const q = User.with('a', firstWith).merge(User.with('b', secondWith));
272
+
273
+ assertType<
274
+ typeof q.withData,
275
+ {
276
+ a: {
277
+ table: 'a';
278
+ shape: typeof firstWith.result;
279
+ type: { id: number };
280
+ };
281
+ b: {
282
+ table: 'b';
283
+ shape: typeof secondWith.result;
284
+ type: { name: string };
285
+ };
286
+ }
287
+ >();
288
+
289
+ expectSql(
290
+ q.toSql(),
291
+ `
292
+ WITH "a" AS (
293
+ SELECT "user"."id" FROM "user"
294
+ ), "b" AS (
295
+ SELECT "user"."name" FROM "user"
296
+ )
297
+ SELECT * FROM "user"
298
+ `,
299
+ );
300
+ });
301
+ });
302
+
303
+ describe('queryData', () => {
304
+ it('should merge query data', () => {
305
+ const q1 = User.clone();
306
+ const q2 = User.clone();
307
+
308
+ q1.query.inTransaction = false;
309
+ q2.query.inTransaction = true;
310
+ q1.query.wrapInTransaction = false;
311
+ q2.query.wrapInTransaction = true;
312
+ q1.query.throwOnNotFound = false;
313
+ q2.query.throwOnNotFound = true;
314
+ q1.query.withShapes = { a: { id: new IntegerColumn() } };
315
+ q2.query.withShapes = { b: { name: new TextColumn() } };
316
+ q1.query.schema = 'a';
317
+ q2.query.schema = 'b';
318
+ q1.query.as = 'a';
319
+ q2.query.as = 'b';
320
+ q1.query.from = 'a';
321
+ q2.query.from = 'b';
322
+ q1.query.coalesceValue = 'a';
323
+ q2.query.coalesceValue = 'b';
324
+ q1.query.parsers = { [getValueKey]: (x) => x, a: (x) => x };
325
+ q2.query.parsers = { [getValueKey]: (x) => x, b: (x) => x };
326
+ q1.query.notFoundDefault = 1;
327
+ q2.query.notFoundDefault = 2;
328
+ q1.query.defaults = { a: 1 };
329
+ q2.query.defaults = { b: 2 };
330
+ q1.query.beforeQuery = [() => {}];
331
+ q2.query.beforeQuery = [() => {}];
332
+ q1.query.log = logParamToLogObject(console, true);
333
+ q2.query.log = logParamToLogObject(console, true);
334
+ q1.query.logger = { log() {}, error() {} };
335
+ q2.query.logger = console;
336
+ q1.query.type = 'update';
337
+ q2.query.type = 'insert';
338
+
339
+ const s1 = q1.query as unknown as SelectQueryData;
340
+ const s2 = q2.query as unknown as SelectQueryData;
341
+ s1.distinct = ['id'];
342
+ s2.distinct = ['name'];
343
+ s1.fromOnly = false;
344
+ s2.fromOnly = true;
345
+ s1.joinedParsers = { a: q1.query.parsers };
346
+ s2.joinedParsers = { b: q2.query.parsers };
347
+ s1.group = ['a'];
348
+ s2.group = ['b'];
349
+ s1.having = [{ a: { a: 1 } }];
350
+ s2.having = [{ b: { b: 2 } }];
351
+ s1.havingOr = [[{ a: { a: 1 } }]];
352
+ s2.havingOr = [[{ b: { b: 2 } }]];
353
+ s1.union = [{ arg: raw('a'), kind: 'UNION' }];
354
+ s2.union = [{ arg: raw('b'), kind: 'EXCEPT' }];
355
+ s1.order = [{ id: 'ASC' }];
356
+ s2.order = [{ name: 'DESC' }];
357
+ s1.limit = 1;
358
+ s2.limit = 2;
359
+ s1.offset = 1;
360
+ s2.offset = 2;
361
+ s1.for = { type: 'UPDATE' };
362
+ s2.for = { type: 'SHARE' };
363
+
364
+ const i1 = q1.query as unknown as InsertQueryData;
365
+ const i2 = q2.query as unknown as InsertQueryData;
366
+ i1.columns = ['id'];
367
+ i2.columns = ['name'];
368
+ i1.values = [[1]];
369
+ i2.values = [['name']];
370
+ i1.using = [{ type: 'a', args: ['a'] }];
371
+ i2.using = [{ type: 'b', args: ['b'] }];
372
+ i1.join = [{ type: 'a', args: ['a'] }];
373
+ i2.join = [{ type: 'b', args: ['b'] }];
374
+ i1.onConflict = { type: 'ignore' };
375
+ i2.onConflict = { type: 'merge' };
376
+ i1.beforeInsert = [() => {}];
377
+ i2.beforeInsert = [() => {}];
378
+
379
+ const u1 = q1.query as unknown as UpdateQueryData;
380
+ const u2 = q2.query as unknown as UpdateQueryData;
381
+ u1.updateData = [{ id: 1 }];
382
+ u2.updateData = [{ name: 'name' }];
383
+ u1.beforeUpdate = [() => {}];
384
+ u2.beforeUpdate = [() => {}];
385
+
386
+ const d1 = q1.query as unknown as DeleteQueryData;
387
+ const d2 = q2.query as unknown as DeleteQueryData;
388
+ d1.beforeDelete = [() => {}];
389
+ d2.beforeDelete = [() => {}];
390
+
391
+ const t1 = q1.query as unknown as TruncateQueryData;
392
+ const t2 = q2.query as unknown as TruncateQueryData;
393
+ t1.restartIdentity = false;
394
+ t2.restartIdentity = true;
395
+ t1.cascade = false;
396
+ t2.cascade = true;
397
+
398
+ const c1 = q1.query as unknown as ColumnInfoQueryData;
399
+ const c2 = q2.query as unknown as ColumnInfoQueryData;
400
+ c1.column = 'id';
401
+ c2.column = 'name';
402
+
403
+ const { query: q } = q1.merge(q2);
404
+ expect(q.inTransaction).toBe(true);
405
+ expect(q.wrapInTransaction).toBe(true);
406
+ expect(q.throwOnNotFound).toBe(true);
407
+ expect(q.withShapes).toEqual({
408
+ ...q1.query.withShapes,
409
+ ...q2.query.withShapes,
410
+ });
411
+ expect(q.schema).toBe('b');
412
+ expect(q.as).toBe('b');
413
+ expect(q.from).toBe('b');
414
+ expect(q.coalesceValue).toBe('b');
415
+ expect(q.parsers).toEqual({
416
+ ...q1.query.parsers,
417
+ ...q2.query.parsers,
418
+ });
419
+ expect(q.notFoundDefault).toBe(2);
420
+ expect(q.defaults).toEqual({
421
+ ...q1.query.defaults,
422
+ ...q2.query.defaults,
423
+ });
424
+ expect(q.beforeQuery).toEqual([
425
+ ...q1.query.beforeQuery,
426
+ ...q2.query.beforeQuery,
427
+ ]);
428
+ expect(q.log).toBe(q2.query.log);
429
+ expect(q.logger).toBe(q2.query.logger);
430
+ expect(q.type).toBe(q2.query.type);
431
+
432
+ const s = q as SelectQueryData;
433
+ expect(s.distinct).toEqual([...s1.distinct, ...s2.distinct]);
434
+ expect(s.fromOnly).toEqual(s2.fromOnly);
435
+ expect(s.joinedParsers).toEqual({
436
+ ...s1.joinedParsers,
437
+ ...s2.joinedParsers,
438
+ });
439
+ expect(s.group).toEqual([...s1.group, ...s2.group]);
440
+ expect(s.having).toEqual([...s1.having, ...s2.having]);
441
+ expect(s.havingOr).toEqual([...s1.havingOr, ...s2.havingOr]);
442
+ expect(s.union).toEqual([...s1.union, ...s2.union]);
443
+ expect(s.order).toEqual([...s1.order, ...s2.order]);
444
+ expect(s.limit).toEqual(s2.limit);
445
+ expect(s.offset).toEqual(s2.offset);
446
+ expect(s.for).toEqual(s2.for);
447
+
448
+ const i = q as InsertQueryData;
449
+ expect(i.columns).toEqual([...i1.columns, ...i2.columns]);
450
+ expect(i.values).toEqual([...i1.values, ...i2.values]);
451
+ expect(i.using).toEqual([...i1.using, ...i2.using]);
452
+ expect(i.join).toEqual([...i1.join, ...i2.join]);
453
+ expect(i.onConflict).toEqual(i2.onConflict);
454
+ expect(i.beforeInsert).toEqual([...i1.beforeInsert, ...i2.beforeInsert]);
455
+
456
+ const u = q as UpdateQueryData;
457
+ expect(u.updateData).toEqual([...u1.updateData, ...u2.updateData]);
458
+ expect(u.beforeUpdate).toEqual([...u1.beforeUpdate, ...u2.beforeUpdate]);
459
+
460
+ const d = q as DeleteQueryData;
461
+ expect(d.beforeDelete).toEqual([...d1.beforeDelete, ...d2.beforeDelete]);
462
+
463
+ const t = q as TruncateQueryData;
464
+ expect(t.restartIdentity).toBe(t2.restartIdentity);
465
+ expect(t.cascade).toBe(t2.cascade);
466
+
467
+ const c = q as ColumnInfoQueryData;
468
+ expect(c.column).toBe(c2.column);
469
+ });
470
+ });
471
+ });
@@ -0,0 +1,67 @@
1
+ import { Query, QueryReturnType, QueryThen } from '../query';
2
+ import { Spread } from '../utils';
3
+
4
+ export type MergeQuery<
5
+ T extends Query,
6
+ Q extends Query,
7
+ ReturnType extends QueryReturnType = QueryReturnType extends Q['returnType']
8
+ ? T['returnType']
9
+ : Q['returnType'],
10
+ > = Omit<T, 'result' | 'returnType' | 'then'> & {
11
+ hasSelect: Q['hasSelect'] extends true ? true : T['hasSelect'];
12
+ hasWhere: Q['hasWhere'] extends true ? true : T['hasWhere'];
13
+ result: T['hasSelect'] extends true
14
+ ? Spread<[T['result'], Q['result']]>
15
+ : Q['result'];
16
+ returnType: ReturnType;
17
+ then: T['hasSelect'] extends true
18
+ ? QueryThen<ReturnType, Spread<[T['result'], Q['result']]>>
19
+ : QueryThen<ReturnType, Q['result']>;
20
+ selectable: T['selectable'] & Q['selectable'];
21
+ joinedTables: T['joinedTables'] & Q['joinedTables'];
22
+ windows: T['windows'] & Q['windows'];
23
+ withData: T['withData'] & Q['withData'];
24
+ };
25
+
26
+ const mergableObjects: Record<string, boolean> = {
27
+ withShapes: true,
28
+ parsers: true,
29
+ defaults: true,
30
+ joinedParsers: true,
31
+ };
32
+
33
+ export class MergeQueryMethods {
34
+ merge<T extends Query, Q extends Query>(this: T, q: Q): MergeQuery<T, Q> {
35
+ return this.clone()._merge(q);
36
+ }
37
+ _merge<T extends Query, Q extends Query>(this: T, q: Q): MergeQuery<T, Q> {
38
+ const a = this.query as Record<string, unknown>;
39
+ const b = q.query as Record<string, unknown>;
40
+
41
+ for (const key in b) {
42
+ const value = b[key];
43
+ switch (typeof value) {
44
+ case 'boolean':
45
+ case 'string':
46
+ case 'number':
47
+ a[key] = value;
48
+ break;
49
+ case 'object':
50
+ if (Array.isArray(value)) {
51
+ a[key] = a[key] ? [...(a[key] as unknown[]), ...value] : value;
52
+ } else if (mergableObjects[key]) {
53
+ a[key] = a[key]
54
+ ? { ...(a[key] as Record<string, unknown>), ...value }
55
+ : value;
56
+ } else {
57
+ a[key] = value;
58
+ }
59
+ break;
60
+ }
61
+ }
62
+
63
+ if (b.returnType) a.returnType = b.returnType;
64
+
65
+ return this as unknown as MergeQuery<T, Q>;
66
+ }
67
+ }