pqb 0.3.2 → 0.3.3
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 +1229 -1307
- package/dist/index.esm.js +456 -403
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +458 -404
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/columnSchema/columnType.test.ts +2 -1
- package/src/db.test.ts +1 -1
- package/src/db.ts +16 -17
- package/src/query.ts +33 -20
- package/src/queryDataUtils.ts +0 -7
- package/src/queryMethods/aggregate.ts +2 -3
- package/src/queryMethods/clear.ts +3 -4
- package/src/queryMethods/delete.ts +3 -3
- package/src/queryMethods/from.test.ts +2 -3
- package/src/queryMethods/get.ts +0 -1
- package/src/queryMethods/index.ts +1 -0
- package/src/queryMethods/insert.ts +14 -13
- package/src/queryMethods/json.ts +7 -3
- package/src/queryMethods/merge.test.ts +471 -0
- package/src/queryMethods/merge.ts +67 -0
- package/src/queryMethods/queryMethods.test.ts +0 -19
- package/src/queryMethods/queryMethods.ts +8 -16
- package/src/queryMethods/select.test.ts +4 -0
- package/src/queryMethods/select.ts +4 -2
- package/src/queryMethods/then.ts +3 -3
- package/src/queryMethods/update.ts +13 -8
- package/src/sql/fromAndAs.ts +2 -3
- package/src/sql/select.ts +1 -2
- package/src/sql/toSql.ts +4 -3
- package/src/sql/types.ts +7 -3
- package/src/sql/window.ts +1 -1
- package/src/sql/with.ts +25 -20
- package/src/utils.ts +0 -18
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { raw } from '../common';
|
|
2
|
-
import { SelectQueryData } from '../sql';
|
|
3
2
|
import {
|
|
4
3
|
expectQueryNotMutated,
|
|
5
4
|
adapter,
|
|
@@ -43,12 +42,6 @@ describe('queryMethods', () => {
|
|
|
43
42
|
});
|
|
44
43
|
|
|
45
44
|
describe('.all', () => {
|
|
46
|
-
it('should remove `take` from query if it is set', () => {
|
|
47
|
-
const q = User.take();
|
|
48
|
-
expect((q.query as SelectQueryData)?.take).toBe(true);
|
|
49
|
-
expect((q.all().query as SelectQueryData)?.take).toBe(undefined);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
45
|
it('should produce correct sql', () => {
|
|
53
46
|
expectSql(User.all().toSql(), `SELECT * FROM "user"`);
|
|
54
47
|
});
|
|
@@ -122,12 +115,6 @@ describe('queryMethods', () => {
|
|
|
122
115
|
const received = await User.rows();
|
|
123
116
|
expect(received).toEqual(expected);
|
|
124
117
|
});
|
|
125
|
-
|
|
126
|
-
it('removes `take` from query data', () => {
|
|
127
|
-
expect((User.take().rows().query as SelectQueryData)?.take).toBe(
|
|
128
|
-
undefined,
|
|
129
|
-
);
|
|
130
|
-
});
|
|
131
118
|
});
|
|
132
119
|
|
|
133
120
|
describe('pluck', () => {
|
|
@@ -159,12 +146,6 @@ describe('queryMethods', () => {
|
|
|
159
146
|
const received = await User.exec();
|
|
160
147
|
expect(received).toEqual(undefined);
|
|
161
148
|
});
|
|
162
|
-
|
|
163
|
-
it('removes `take` from query data', () => {
|
|
164
|
-
expect((User.take().exec().query as SelectQueryData)?.take).toBe(
|
|
165
|
-
undefined,
|
|
166
|
-
);
|
|
167
|
-
});
|
|
168
149
|
});
|
|
169
150
|
|
|
170
151
|
describe('distinct', () => {
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
} from '../query';
|
|
16
16
|
import {
|
|
17
17
|
applyMixins,
|
|
18
|
+
EmptyObject,
|
|
18
19
|
getClonedQueryData,
|
|
19
20
|
GetTypesOrRaw,
|
|
20
|
-
PropertyKeyUnionToArray,
|
|
21
21
|
} from '../utils';
|
|
22
22
|
import {
|
|
23
23
|
SelectItem,
|
|
@@ -28,11 +28,7 @@ import {
|
|
|
28
28
|
ToSqlOptions,
|
|
29
29
|
TruncateQueryData,
|
|
30
30
|
} from '../sql';
|
|
31
|
-
import {
|
|
32
|
-
pushQueryArray,
|
|
33
|
-
pushQueryValue,
|
|
34
|
-
removeFromQuery,
|
|
35
|
-
} from '../queryDataUtils';
|
|
31
|
+
import { pushQueryArray, pushQueryValue } from '../queryDataUtils';
|
|
36
32
|
import { Then } from './then';
|
|
37
33
|
import { BooleanColumn } from '../columnSchema';
|
|
38
34
|
import { Aggregate } from './aggregate';
|
|
@@ -56,6 +52,7 @@ import { QueryLog } from './log';
|
|
|
56
52
|
import { QueryCallbacks } from './callbacks';
|
|
57
53
|
import { QueryUpsert } from './upsert';
|
|
58
54
|
import { QueryGet } from './get';
|
|
55
|
+
import { MergeQueryMethods } from './merge';
|
|
59
56
|
|
|
60
57
|
export type WindowArg<T extends Query> = Record<
|
|
61
58
|
string,
|
|
@@ -69,7 +66,7 @@ export type WindowArgDeclaration<T extends Query = Query> = {
|
|
|
69
66
|
|
|
70
67
|
type WindowResult<T extends Query, W extends WindowArg<T>> = SetQueryWindows<
|
|
71
68
|
T,
|
|
72
|
-
|
|
69
|
+
Record<keyof W, true>
|
|
73
70
|
>;
|
|
74
71
|
|
|
75
72
|
export type OrderArg<T extends Query> =
|
|
@@ -103,10 +100,11 @@ export interface QueryMethods
|
|
|
103
100
|
QueryLog,
|
|
104
101
|
QueryCallbacks,
|
|
105
102
|
QueryUpsert,
|
|
106
|
-
QueryGet
|
|
103
|
+
QueryGet,
|
|
104
|
+
MergeQueryMethods {}
|
|
107
105
|
|
|
108
106
|
export class QueryMethods {
|
|
109
|
-
windows!:
|
|
107
|
+
windows!: EmptyObject;
|
|
110
108
|
__model!: Query;
|
|
111
109
|
|
|
112
110
|
all<T extends Query>(this: T): SetQueryReturnsAll<T> {
|
|
@@ -115,7 +113,6 @@ export class QueryMethods {
|
|
|
115
113
|
|
|
116
114
|
_all<T extends Query>(this: T): SetQueryReturnsAll<T> {
|
|
117
115
|
this.query.returnType = 'all';
|
|
118
|
-
removeFromQuery(this, 'take');
|
|
119
116
|
return this as unknown as SetQueryReturnsAll<T>;
|
|
120
117
|
}
|
|
121
118
|
|
|
@@ -125,7 +122,6 @@ export class QueryMethods {
|
|
|
125
122
|
|
|
126
123
|
_take<T extends Query>(this: T): SetQueryReturnsOne<T> {
|
|
127
124
|
this.query.returnType = 'oneOrThrow';
|
|
128
|
-
this.query.take = true;
|
|
129
125
|
return this as unknown as SetQueryReturnsOne<T>;
|
|
130
126
|
}
|
|
131
127
|
|
|
@@ -135,7 +131,6 @@ export class QueryMethods {
|
|
|
135
131
|
|
|
136
132
|
_takeOptional<T extends Query>(this: T): SetQueryReturnsOneOptional<T> {
|
|
137
133
|
this.query.returnType = 'one';
|
|
138
|
-
this.query.take = true;
|
|
139
134
|
return this as unknown as SetQueryReturnsOneOptional<T>;
|
|
140
135
|
}
|
|
141
136
|
|
|
@@ -145,7 +140,6 @@ export class QueryMethods {
|
|
|
145
140
|
|
|
146
141
|
_rows<T extends Query>(this: T): SetQueryReturnsRows<T> {
|
|
147
142
|
this.query.returnType = 'rows';
|
|
148
|
-
removeFromQuery(this, 'take');
|
|
149
143
|
return this as unknown as SetQueryReturnsRows<T>;
|
|
150
144
|
}
|
|
151
145
|
|
|
@@ -161,7 +155,6 @@ export class QueryMethods {
|
|
|
161
155
|
select: S,
|
|
162
156
|
): SetQueryReturnsPluck<T, S> {
|
|
163
157
|
this.query.returnType = 'pluck';
|
|
164
|
-
removeFromQuery(this, 'take');
|
|
165
158
|
(this.query as SelectQueryData).select = [select as SelectItem];
|
|
166
159
|
addParserForSelectItem(this, this.query.as || this.table, 'pluck', select);
|
|
167
160
|
return this as unknown as SetQueryReturnsPluck<T, S>;
|
|
@@ -173,7 +166,6 @@ export class QueryMethods {
|
|
|
173
166
|
|
|
174
167
|
_exec<T extends Query>(this: T): SetQueryReturnsVoid<T> {
|
|
175
168
|
this.query.returnType = 'void';
|
|
176
|
-
removeFromQuery(this, 'take');
|
|
177
169
|
return this as unknown as SetQueryReturnsVoid<T>;
|
|
178
170
|
}
|
|
179
171
|
|
|
@@ -362,7 +354,6 @@ export class QueryMethods {
|
|
|
362
354
|
const q = this._getOptional(raw<BooleanColumn>('true'));
|
|
363
355
|
q.query.notFoundDefault = false;
|
|
364
356
|
q.query.coalesceValue = false;
|
|
365
|
-
delete q.query.take;
|
|
366
357
|
return q as unknown as SetQueryReturnsValue<T, BooleanColumn>;
|
|
367
358
|
}
|
|
368
359
|
|
|
@@ -412,4 +403,5 @@ applyMixins(QueryMethods, [
|
|
|
412
403
|
QueryCallbacks,
|
|
413
404
|
QueryUpsert,
|
|
414
405
|
QueryGet,
|
|
406
|
+
MergeQueryMethods,
|
|
415
407
|
]);
|
|
@@ -29,6 +29,10 @@ const insertUserAndProfile = async () => {
|
|
|
29
29
|
describe('selectMethods', () => {
|
|
30
30
|
useTestDatabase();
|
|
31
31
|
|
|
32
|
+
it('table should have all columns selected if select was not applied', () => {
|
|
33
|
+
assertType<Awaited<typeof User>, UserRecord[]>();
|
|
34
|
+
});
|
|
35
|
+
|
|
32
36
|
describe('select', () => {
|
|
33
37
|
it('should have no effect if no columns provided', () => {
|
|
34
38
|
const q = User.all();
|