xcraft-core-pickaxe 0.1.26 → 0.1.29
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/lib/operator-to-sql.js +37 -2
- package/lib/operators.js +29 -3
- package/lib/partial-object-shape.js +17 -0
- package/lib/pick-operators.js +62 -4
- package/lib/pick-or-value.js +6 -2
- package/lib/picks.js +69 -16
- package/lib/query-builder.js +180 -56
- package/lib/query-to-sql.js +3 -1
- package/package.json +1 -1
- package/test/sql.spec.js +39 -0
package/lib/operator-to-sql.js
CHANGED
|
@@ -185,6 +185,23 @@ const operators = {
|
|
|
185
185
|
)})`;
|
|
186
186
|
},
|
|
187
187
|
|
|
188
|
+
case({whenConditions, elseValue}, context) {
|
|
189
|
+
const whenSql = whenConditions
|
|
190
|
+
.map(([condition, value]) => {
|
|
191
|
+
const conditionSql = sql(condition, context);
|
|
192
|
+
if (!conditionSql) {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
return `WHEN (${conditionSql}) THEN ${sql(value, context)}`;
|
|
196
|
+
})
|
|
197
|
+
.filter(Boolean)
|
|
198
|
+
.join(' ');
|
|
199
|
+
if (!whenSql) {
|
|
200
|
+
return sql(elseValue, context);
|
|
201
|
+
}
|
|
202
|
+
return `CASE ${whenSql} ELSE ${sql(elseValue, context)} END`;
|
|
203
|
+
},
|
|
204
|
+
|
|
188
205
|
abs({value}, context) {
|
|
189
206
|
return `ABS(${sql(value, context)})`;
|
|
190
207
|
},
|
|
@@ -229,7 +246,11 @@ const operators = {
|
|
|
229
246
|
|
|
230
247
|
query({query}, context) {
|
|
231
248
|
const {queryToSql} = require('./query-to-sql.js');
|
|
232
|
-
return `(${
|
|
249
|
+
return `(${
|
|
250
|
+
queryToSql(query, context.values, {
|
|
251
|
+
useTableNames: context.useTableNames,
|
|
252
|
+
}).sql
|
|
253
|
+
})`;
|
|
233
254
|
},
|
|
234
255
|
|
|
235
256
|
each({value}, context) {
|
|
@@ -266,6 +287,14 @@ const operators = {
|
|
|
266
287
|
return `${sql(value, context)} DESC`;
|
|
267
288
|
},
|
|
268
289
|
|
|
290
|
+
nullsFirst({value}, context) {
|
|
291
|
+
return `${sql(value, context)} NULLS FIRST`;
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
nullsLast({value}, context) {
|
|
295
|
+
return `${sql(value, context)} NULLS LAST`;
|
|
296
|
+
},
|
|
297
|
+
|
|
269
298
|
count({field, distinct}, context) {
|
|
270
299
|
if (!field) {
|
|
271
300
|
return `COUNT(*)`;
|
|
@@ -289,7 +318,13 @@ const operators = {
|
|
|
289
318
|
return `SUM(${distinct ? 'DISTINCT ' : ''}${sql(field, context)})`;
|
|
290
319
|
},
|
|
291
320
|
|
|
292
|
-
groupArray({field}, context) {
|
|
321
|
+
groupArray({field, orderBy}, context) {
|
|
322
|
+
if (orderBy) {
|
|
323
|
+
return `json_group_array(${sql(field, context)} ORDER BY ${sql(
|
|
324
|
+
orderBy,
|
|
325
|
+
context
|
|
326
|
+
)})`;
|
|
327
|
+
}
|
|
293
328
|
return `json_group_array(${sql(field, context)})`;
|
|
294
329
|
},
|
|
295
330
|
};
|
package/lib/operators.js
CHANGED
|
@@ -206,14 +206,14 @@ const operators = {
|
|
|
206
206
|
and(...conditions) {
|
|
207
207
|
return /** @type {const} */ ({
|
|
208
208
|
operator: 'and',
|
|
209
|
-
conditions,
|
|
209
|
+
conditions: conditions.map(op),
|
|
210
210
|
});
|
|
211
211
|
},
|
|
212
212
|
|
|
213
213
|
or(...conditions) {
|
|
214
214
|
return /** @type {const} */ ({
|
|
215
215
|
operator: 'or',
|
|
216
|
-
conditions,
|
|
216
|
+
conditions: conditions.map(op),
|
|
217
217
|
});
|
|
218
218
|
},
|
|
219
219
|
|
|
@@ -234,6 +234,17 @@ const operators = {
|
|
|
234
234
|
});
|
|
235
235
|
},
|
|
236
236
|
|
|
237
|
+
case(whenConditions, elseValue) {
|
|
238
|
+
return /** @type {const} */ ({
|
|
239
|
+
operator: 'case',
|
|
240
|
+
whenConditions: whenConditions.map(([condition, value]) => [
|
|
241
|
+
op(condition),
|
|
242
|
+
op(value),
|
|
243
|
+
]),
|
|
244
|
+
elseValue: op(elseValue),
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
|
|
237
248
|
abs(value) {
|
|
238
249
|
value = op(value);
|
|
239
250
|
return /** @type {const} */ ({
|
|
@@ -339,6 +350,20 @@ const operators = {
|
|
|
339
350
|
});
|
|
340
351
|
},
|
|
341
352
|
|
|
353
|
+
nullsFirst(value) {
|
|
354
|
+
return /** @type {const} */ ({
|
|
355
|
+
operator: 'nullsFirst',
|
|
356
|
+
value,
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
nullsLast(value) {
|
|
361
|
+
return /** @type {const} */ ({
|
|
362
|
+
operator: 'nullsLast',
|
|
363
|
+
value,
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
|
|
342
367
|
count(field, distinct) {
|
|
343
368
|
return /** @type {const} */ ({
|
|
344
369
|
operator: 'count',
|
|
@@ -375,10 +400,11 @@ const operators = {
|
|
|
375
400
|
});
|
|
376
401
|
},
|
|
377
402
|
|
|
378
|
-
groupArray(field) {
|
|
403
|
+
groupArray(field, orderBy) {
|
|
379
404
|
return /** @type {const} */ ({
|
|
380
405
|
operator: 'groupArray',
|
|
381
406
|
field,
|
|
407
|
+
orderBy,
|
|
382
408
|
});
|
|
383
409
|
},
|
|
384
410
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const {ObjectType} = require('xcraft-core-stones');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template {ObjectType} T
|
|
5
|
+
* @template {(keyof T["properties"])[]} F
|
|
6
|
+
* @param {T} objectType
|
|
7
|
+
* @param {F} fields
|
|
8
|
+
* @returns {Pick<T["properties"], F[number]>}
|
|
9
|
+
*/
|
|
10
|
+
function partialObjectShape(objectType, fields) {
|
|
11
|
+
const properties = objectType.properties;
|
|
12
|
+
return /** @type {Pick<T["properties"], F[number]>} */ (Object.fromEntries(
|
|
13
|
+
fields.map((fieldName) => [fieldName, properties[fieldName]])
|
|
14
|
+
));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = partialObjectShape;
|
package/lib/pick-operators.js
CHANGED
|
@@ -8,6 +8,8 @@ const {
|
|
|
8
8
|
union,
|
|
9
9
|
UnionType,
|
|
10
10
|
any,
|
|
11
|
+
string,
|
|
12
|
+
StringType,
|
|
11
13
|
} = require('xcraft-core-stones');
|
|
12
14
|
const $o = require('./operators.js');
|
|
13
15
|
const {
|
|
@@ -17,6 +19,7 @@ const {
|
|
|
17
19
|
NumberPick,
|
|
18
20
|
makePick,
|
|
19
21
|
ArrayPick,
|
|
22
|
+
StringPick,
|
|
20
23
|
} = require('./picks.js');
|
|
21
24
|
const {getPickOrValueType} = require('./pick-or-value.js');
|
|
22
25
|
|
|
@@ -29,6 +32,11 @@ const {getPickOrValueType} = require('./pick-or-value.js');
|
|
|
29
32
|
* @typedef {import('./pick-or-value.js').PickOrValueType<T>} PickOrValueType
|
|
30
33
|
*/
|
|
31
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @template {PickOrValue[]} T
|
|
37
|
+
* @typedef {{[K in keyof T]: PickOrValueType<T[K]>}} PickOrValueTypes
|
|
38
|
+
*/
|
|
39
|
+
|
|
32
40
|
const pickOperators = {
|
|
33
41
|
...$o,
|
|
34
42
|
|
|
@@ -48,7 +56,7 @@ const pickOperators = {
|
|
|
48
56
|
},
|
|
49
57
|
|
|
50
58
|
/**
|
|
51
|
-
* @param {BooleanPick[]} conditions
|
|
59
|
+
* @param {(BooleanPick | boolean)[]} conditions
|
|
52
60
|
* @returns {BooleanPick}
|
|
53
61
|
*/
|
|
54
62
|
and(...conditions) {
|
|
@@ -56,7 +64,7 @@ const pickOperators = {
|
|
|
56
64
|
},
|
|
57
65
|
|
|
58
66
|
/**
|
|
59
|
-
* @param {BooleanPick[]} conditions
|
|
67
|
+
* @param {(BooleanPick | boolean)[]} conditions
|
|
60
68
|
* @returns {BooleanPick}
|
|
61
69
|
*/
|
|
62
70
|
or(...conditions) {
|
|
@@ -76,6 +84,55 @@ const pickOperators = {
|
|
|
76
84
|
return new ValuePick(newType, $o.if(condition, a, b));
|
|
77
85
|
},
|
|
78
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @template {PickOrValue[]} T
|
|
89
|
+
* @typedef {{[K in keyof T]: [BooleanPick | boolean, T[K]]}} CaseConditions
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @template {[PickOrValue, ...PickOrValue[]]} T
|
|
94
|
+
* @template {PickOrValue} U
|
|
95
|
+
* @param {[...CaseConditions<T>, U]} conditions
|
|
96
|
+
* @returns {ValuePick<UnionType<[...PickOrValueTypes<T>, PickOrValueType<U>]>>}
|
|
97
|
+
*/
|
|
98
|
+
case(...conditions) {
|
|
99
|
+
const whenConditions = /** @type {CaseConditions<T>} */ (
|
|
100
|
+
//
|
|
101
|
+
conditions.slice(0, -1)
|
|
102
|
+
);
|
|
103
|
+
const whenTypes = /** @type {PickOrValueTypes<T>} */ (whenConditions.map(
|
|
104
|
+
(condition) => getPickOrValueType(condition[1])
|
|
105
|
+
));
|
|
106
|
+
const elseValue = /** @type {U} */ (conditions.at(-1));
|
|
107
|
+
const newType = union(...whenTypes, getPickOrValueType(elseValue));
|
|
108
|
+
return new ValuePick(newType, $o.case(whenConditions, elseValue));
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @template {StringPick | string} T
|
|
113
|
+
* @typedef {T extends StringPick<infer U> ? U : T} StringValue
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @template {(StringPick | string)[]} T
|
|
118
|
+
* @typedef {{[K in keyof T]: StringValue<T[K]>}} StringValues
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @template {string[]} T
|
|
123
|
+
* @typedef {T extends [infer First extends string, ...infer Rest extends string[]] ? `${First}${ConcatStringLiterals<Rest>}`: ''} ConcatStringLiterals
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @template {(StringPick | string)[]} T
|
|
128
|
+
* @param {T} values
|
|
129
|
+
* @returns {StringPick<ConcatStringLiterals<StringValues<T>>>}
|
|
130
|
+
*/
|
|
131
|
+
stringConcat(...values) {
|
|
132
|
+
const type = /** @type {StringType<ConcatStringLiterals<StringValues<T>>>} */ (string);
|
|
133
|
+
return new StringPick(type, $o.stringConcat(...values));
|
|
134
|
+
},
|
|
135
|
+
|
|
79
136
|
/**
|
|
80
137
|
* @param {BasePick} [field]
|
|
81
138
|
* @param {boolean} [distinct]
|
|
@@ -117,9 +174,10 @@ const pickOperators = {
|
|
|
117
174
|
/**
|
|
118
175
|
* @template {AnyTypeOrShape} T
|
|
119
176
|
* @param {ValuePick<T>} field
|
|
177
|
+
* @param {BasePick} [orderBy]
|
|
120
178
|
*/
|
|
121
|
-
groupArray(field) {
|
|
122
|
-
return new ArrayPick(array(field.type), $o.groupArray(field));
|
|
179
|
+
groupArray(field, orderBy) {
|
|
180
|
+
return new ArrayPick(array(field.type), $o.groupArray(field, orderBy));
|
|
123
181
|
},
|
|
124
182
|
};
|
|
125
183
|
|
package/lib/pick-or-value.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
NumberType,
|
|
7
7
|
string,
|
|
8
8
|
StringType,
|
|
9
|
+
ValueType,
|
|
9
10
|
boolean,
|
|
10
11
|
BooleanType,
|
|
11
12
|
} = require('xcraft-core-stones');
|
|
@@ -13,7 +14,7 @@ const {BasePick, makeTypePick} = require('./picks.js');
|
|
|
13
14
|
const $o = require('./operators.js');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* @typedef {string | number | boolean} PickValue
|
|
17
|
+
* @typedef {string | number | boolean | null} PickValue
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -22,7 +23,7 @@ const $o = require('./operators.js');
|
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* @template {PickValue} T
|
|
25
|
-
* @typedef {T extends boolean ? BooleanType : T extends number ? NumberType : T extends string ? StringType : never} GetValueType
|
|
26
|
+
* @typedef {T extends boolean ? BooleanType : T extends number ? NumberType : T extends string ? StringType<T> : T extends null ? ValueType<null> : never} GetValueType
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -45,6 +46,9 @@ function getValueType(value) {
|
|
|
45
46
|
if (typeof value === 'boolean') {
|
|
46
47
|
return /** @type {any} */ (boolean);
|
|
47
48
|
}
|
|
49
|
+
if (value === null) {
|
|
50
|
+
return /** @type {any} */ (new ValueType(null));
|
|
51
|
+
}
|
|
48
52
|
throw new Error('Unknown value type');
|
|
49
53
|
}
|
|
50
54
|
|
package/lib/picks.js
CHANGED
|
@@ -186,6 +186,14 @@ class StringPick extends ValuePick {
|
|
|
186
186
|
substr(start, length) {
|
|
187
187
|
return new StringPick(string, $o.substr(this.expression, start, length));
|
|
188
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {(StringPick | string)[]} values
|
|
192
|
+
* @returns {StringPick}
|
|
193
|
+
*/
|
|
194
|
+
concat(...values) {
|
|
195
|
+
return new StringPick(string, $o.stringConcat(this.expression, ...values));
|
|
196
|
+
}
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
/**
|
|
@@ -430,6 +438,53 @@ class ArrayPick extends BasePick {
|
|
|
430
438
|
);
|
|
431
439
|
}
|
|
432
440
|
|
|
441
|
+
/**
|
|
442
|
+
* @param {(value: PickOf<T>) => BooleanPick} func
|
|
443
|
+
*/
|
|
444
|
+
filter(func) {
|
|
445
|
+
return new ArrayPick(
|
|
446
|
+
this.type,
|
|
447
|
+
$o.query({
|
|
448
|
+
from: $o.each(this.expression),
|
|
449
|
+
select: [$o.groupArray($o.eachValue())],
|
|
450
|
+
where: func(makePick(this.type.valuesType, $o.eachValue())),
|
|
451
|
+
})
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* @template {AnyTypeOrShape} U
|
|
457
|
+
* @param {(value: PickOf<T>) => BasePick<U>} func
|
|
458
|
+
*/
|
|
459
|
+
map(func) {
|
|
460
|
+
const newValuePick = func(makePick(this.type.valuesType, $o.eachValue()));
|
|
461
|
+
return new ArrayPick(
|
|
462
|
+
array(newValuePick.type),
|
|
463
|
+
$o.query({
|
|
464
|
+
from: $o.each(this.expression),
|
|
465
|
+
select: [$o.groupArray(newValuePick)],
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @param {(value: PickOf<T>) => BasePick} [func]
|
|
472
|
+
*/
|
|
473
|
+
sort(func = (pick) => pick) {
|
|
474
|
+
return new ArrayPick(
|
|
475
|
+
this.type,
|
|
476
|
+
$o.query({
|
|
477
|
+
from: $o.each(this.expression),
|
|
478
|
+
select: [
|
|
479
|
+
$o.groupArray(
|
|
480
|
+
$o.eachValue(),
|
|
481
|
+
func(makePick(this.type.valuesType, $o.eachValue()))
|
|
482
|
+
),
|
|
483
|
+
],
|
|
484
|
+
})
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
433
488
|
/**
|
|
434
489
|
* @param {(value: PickOf<T>) => BooleanPick} func
|
|
435
490
|
*/
|
|
@@ -467,22 +522,19 @@ function isAnyPick(value) {
|
|
|
467
522
|
* @extends {BasePick<ObjectType<T>>}
|
|
468
523
|
*/
|
|
469
524
|
class RowPick extends BasePick {
|
|
470
|
-
|
|
471
|
-
* @type {string | null}
|
|
472
|
-
*/
|
|
473
|
-
#tableName = null;
|
|
525
|
+
#context;
|
|
474
526
|
|
|
475
527
|
/**
|
|
476
528
|
* @param {ObjectType<T>} type
|
|
477
|
-
* @param {{expression: Expression} | {tableName: string}} context
|
|
529
|
+
* @param {{expression: Expression} | {tableName: string} | null} context
|
|
478
530
|
*/
|
|
479
531
|
constructor(type, context) {
|
|
480
|
-
if ('expression' in context) {
|
|
532
|
+
if (context && 'expression' in context) {
|
|
481
533
|
super(type, context.expression);
|
|
482
534
|
} else {
|
|
483
535
|
super(type, null);
|
|
484
|
-
this.#tableName = context.tableName;
|
|
485
536
|
}
|
|
537
|
+
this.#context = context;
|
|
486
538
|
}
|
|
487
539
|
|
|
488
540
|
/**
|
|
@@ -491,10 +543,16 @@ class RowPick extends BasePick {
|
|
|
491
543
|
* @returns {PickOf<T[K]>}
|
|
492
544
|
*/
|
|
493
545
|
field(fieldName) {
|
|
494
|
-
if (this.#
|
|
546
|
+
if (!this.#context) {
|
|
495
547
|
return makePick(
|
|
496
548
|
this.type.properties[fieldName],
|
|
497
|
-
$o.field(fieldName,
|
|
549
|
+
$o.field(fieldName, null)
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
if ('tableName' in this.#context) {
|
|
553
|
+
return makePick(
|
|
554
|
+
this.type.properties[fieldName],
|
|
555
|
+
$o.field(fieldName, this.#context.tableName)
|
|
498
556
|
);
|
|
499
557
|
}
|
|
500
558
|
return makePick(
|
|
@@ -514,16 +572,11 @@ class RowPick extends BasePick {
|
|
|
514
572
|
}
|
|
515
573
|
|
|
516
574
|
isRoot() {
|
|
517
|
-
return Boolean(this.#tableName);
|
|
575
|
+
return Boolean(!this.#context || 'tableName' in this.#context);
|
|
518
576
|
}
|
|
519
577
|
|
|
520
578
|
toOptional() {
|
|
521
|
-
return new RowPick(
|
|
522
|
-
optionalObjectType(this.type),
|
|
523
|
-
this.#tableName
|
|
524
|
-
? {tableName: this.#tableName}
|
|
525
|
-
: {expression: this.expression}
|
|
526
|
-
);
|
|
579
|
+
return new RowPick(optionalObjectType(this.type), this.#context);
|
|
527
580
|
}
|
|
528
581
|
}
|
|
529
582
|
|
package/lib/query-builder.js
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
} = require('./picks.js');
|
|
23
23
|
const {joinOperators} = require('./join-operators.js');
|
|
24
24
|
const {queryToSql} = require('./query-to-sql.js');
|
|
25
|
+
const partialObjectShape = require('./partial-object-shape.js');
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* @typedef {import("./operators.js").Operators} Operators
|
|
@@ -74,18 +75,108 @@ const {queryToSql} = require('./query-to-sql.js');
|
|
|
74
75
|
*/
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
* @
|
|
78
|
+
* @template {BasePick} T
|
|
79
|
+
* @typedef {T["type"]} PickType
|
|
78
80
|
*/
|
|
79
81
|
/**
|
|
80
|
-
* @
|
|
81
|
-
* @typedef {
|
|
82
|
+
* @typedef {[BasePick, ...BasePick[]]} SelectPicksTuple
|
|
83
|
+
* @typedef {Record<string, BasePick>} SelectPicksObject
|
|
82
84
|
*/
|
|
83
85
|
/**
|
|
84
|
-
* @typedef {
|
|
86
|
+
* @typedef {SelectPicksObject | SelectPicksTuple} SelectPicks
|
|
85
87
|
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @template {SelectPicksTuple} T
|
|
91
|
+
* @typedef {{[K in keyof T]: PickType<T[K]>}} SelectTypeTuple
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* @template {SelectPicksTuple} T
|
|
95
|
+
* @param {T} selectPicks
|
|
96
|
+
* @returns {SelectTypeTuple<T>}
|
|
97
|
+
*/
|
|
98
|
+
function selectTypeTuple(selectPicks) {
|
|
99
|
+
return /** @type {SelectTypeTuple<T>} */ (selectPicks.map(
|
|
100
|
+
(result) => result.type
|
|
101
|
+
));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @template {SelectPicksObject} T
|
|
106
|
+
* @typedef {flatten<{[K in keyof T]: PickType<T[K]>}>} SelectTypeObject
|
|
107
|
+
*/
|
|
108
|
+
/**
|
|
109
|
+
* @template {SelectPicksObject} T
|
|
110
|
+
* @param {T} selectPicks
|
|
111
|
+
* @returns {SelectTypeObject<T>}
|
|
112
|
+
*/
|
|
113
|
+
function selectTypeObject(selectPicks) {
|
|
114
|
+
return /** @type {SelectTypeObject<T>} */ (Object.fromEntries(
|
|
115
|
+
Object.entries(selectPicks).map(([name, pick]) => [name, pick.type])
|
|
116
|
+
));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @template {SelectPicks} T
|
|
121
|
+
* @typedef {T extends SelectPicksTuple ? {values: SelectTypeTuple<T>} : T extends SelectPicksObject ? {object: SelectTypeObject<T>} : never} SelectResultOf
|
|
122
|
+
*/
|
|
123
|
+
/**
|
|
124
|
+
* @template {SelectPicks} T
|
|
125
|
+
* @param {T} selectPicks
|
|
126
|
+
* @returns {SelectResultOf<T>}
|
|
127
|
+
*/
|
|
128
|
+
function selectResultOf(selectPicks) {
|
|
129
|
+
if (Array.isArray(selectPicks)) {
|
|
130
|
+
return /** @type {SelectResultOf<T>} */ ({
|
|
131
|
+
values: selectTypeTuple(selectPicks),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return /** @type {SelectResultOf<T>} */ ({
|
|
135
|
+
object: selectTypeObject(selectPicks),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @typedef {{value: AnyTypeOrShape}} SelectResultValue
|
|
141
|
+
* @typedef {{values: [AnyTypeOrShape, ...AnyTypeOrShape[]]}} SelectResultValues
|
|
142
|
+
* @typedef {{object: Record<string, AnyTypeOrShape>}} SelectResultObject
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @typedef {SelectResultValue | SelectResultValues | SelectResultObject} SelectResult
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @template {SelectResult} T
|
|
151
|
+
* @typedef {T extends SelectResultObject ? RowPick<T['object']> : null} SelectResultPick
|
|
152
|
+
*/
|
|
153
|
+
|
|
86
154
|
/**
|
|
87
155
|
* @template {SelectResult} T
|
|
88
|
-
* @
|
|
156
|
+
* @param {T} selectResult
|
|
157
|
+
* @returns {SelectResultPick<T>}
|
|
158
|
+
*/
|
|
159
|
+
function selectResultPick(selectResult) {
|
|
160
|
+
if ('object' in selectResult) {
|
|
161
|
+
const rowPick = new RowPick(object(selectResult.object), null);
|
|
162
|
+
return /** @type {SelectResultPick<T>} */ (rowPick);
|
|
163
|
+
}
|
|
164
|
+
return /** @type {SelectResultPick<T>} */ (null);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @template {SelectResultValues['values']} T
|
|
169
|
+
* @typedef {{[K in keyof T]: t<T[K]>}} QueryReturnValues
|
|
170
|
+
*/
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @template {SelectResultObject['object']} T
|
|
174
|
+
* @typedef {flatten<{[K in keyof T]: t<T[K]>}>} QueryReturnObject
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @template {SelectResult} T
|
|
179
|
+
* @typedef {T extends SelectResultValue ? t<T['value']> : T extends SelectResultValues ? QueryReturnValues<T['values']> : T extends SelectResultObject ? QueryReturnObject<T['object']> : never} QueryReturn
|
|
89
180
|
*/
|
|
90
181
|
|
|
91
182
|
/**
|
|
@@ -157,6 +248,12 @@ function getTableName(table) {
|
|
|
157
248
|
* @typedef {[...objs: WrapRowPick<T>, operators: typeof pickOperators]} FctArgs
|
|
158
249
|
*/
|
|
159
250
|
|
|
251
|
+
/**
|
|
252
|
+
* @template {[...ObjectShape[], ObjectShape]} T
|
|
253
|
+
* @template {SelectResult} Q
|
|
254
|
+
* @typedef {[...objs: WrapRowPick<T>, operators: typeof pickOperators, selectFields: SelectResultPick<Q>]} FctArgs2
|
|
255
|
+
*/
|
|
256
|
+
|
|
160
257
|
/**
|
|
161
258
|
* @typedef {{
|
|
162
259
|
* explain?: boolean,
|
|
@@ -164,7 +261,7 @@ function getTableName(table) {
|
|
|
164
261
|
* from: QueryTable,
|
|
165
262
|
* scope?: ObjectPick<any>
|
|
166
263
|
* joins?: JoinsResult[],
|
|
167
|
-
* select:
|
|
264
|
+
* select: SelectPicks | '*',
|
|
168
265
|
* selectOneField?: boolean,
|
|
169
266
|
* distinct?: boolean,
|
|
170
267
|
* where?: BooleanPick,
|
|
@@ -426,23 +523,26 @@ class FinalQuery {
|
|
|
426
523
|
|
|
427
524
|
/**
|
|
428
525
|
* @template {[...ObjectShape[], ObjectShape]} T
|
|
429
|
-
* @template
|
|
430
|
-
* @extends {FinalQuery<
|
|
526
|
+
* @template {SelectResult} S
|
|
527
|
+
* @extends {FinalQuery<QueryReturn<S>>}
|
|
431
528
|
*/
|
|
432
529
|
class SelectQuery extends FinalQuery {
|
|
433
530
|
#database;
|
|
434
531
|
#picks;
|
|
532
|
+
#selectPick;
|
|
435
533
|
#queryParts;
|
|
436
534
|
|
|
437
535
|
/**
|
|
438
536
|
* @param {WrapRowPick<T>} picks
|
|
537
|
+
* @param {SelectResultPick<S>} selectPick
|
|
439
538
|
* @param {QueryParts<'from' | 'select'>} queryParts
|
|
440
539
|
* @param {*} database
|
|
441
540
|
*/
|
|
442
|
-
constructor(picks, queryParts, database) {
|
|
541
|
+
constructor(picks, selectPick, queryParts, database) {
|
|
443
542
|
super(database, queryParts);
|
|
444
543
|
this.#database = database;
|
|
445
544
|
this.#picks = picks;
|
|
545
|
+
this.#selectPick = selectPick;
|
|
446
546
|
this.#queryParts = queryParts;
|
|
447
547
|
}
|
|
448
548
|
|
|
@@ -451,77 +551,107 @@ class SelectQuery extends FinalQuery {
|
|
|
451
551
|
}
|
|
452
552
|
|
|
453
553
|
/**
|
|
454
|
-
* @returns {SelectQuery<T,
|
|
554
|
+
* @returns {SelectQuery<T,S>}
|
|
455
555
|
*/
|
|
456
556
|
distinct() {
|
|
457
557
|
const queryParts = {
|
|
458
558
|
...this.#queryParts,
|
|
459
559
|
distinct: true,
|
|
460
560
|
};
|
|
461
|
-
return new SelectQuery(
|
|
561
|
+
return new SelectQuery(
|
|
562
|
+
this.#picks,
|
|
563
|
+
this.#selectPick,
|
|
564
|
+
queryParts,
|
|
565
|
+
this.#database
|
|
566
|
+
);
|
|
462
567
|
}
|
|
463
568
|
|
|
464
569
|
/**
|
|
465
|
-
* @param {(...args:
|
|
466
|
-
* @returns {SelectQuery<T,
|
|
570
|
+
* @param {(...args: FctArgs2<T,S>) => BooleanPick} fct
|
|
571
|
+
* @returns {SelectQuery<T,S>}
|
|
467
572
|
*/
|
|
468
573
|
where(fct) {
|
|
469
574
|
const queryParts = {
|
|
470
575
|
...this.#queryParts,
|
|
471
576
|
where: mergeWhere(
|
|
472
577
|
this.#queryParts.where,
|
|
473
|
-
fct(...this.#picks, pickOperators)
|
|
578
|
+
fct(...this.#picks, pickOperators, this.#selectPick)
|
|
474
579
|
),
|
|
475
580
|
};
|
|
476
|
-
return new SelectQuery(
|
|
581
|
+
return new SelectQuery(
|
|
582
|
+
this.#picks,
|
|
583
|
+
this.#selectPick,
|
|
584
|
+
queryParts,
|
|
585
|
+
this.#database
|
|
586
|
+
);
|
|
477
587
|
}
|
|
478
588
|
|
|
479
589
|
/**
|
|
480
|
-
* @param {(...args:
|
|
481
|
-
* @returns {SelectQuery<T,
|
|
590
|
+
* @param {(...args: FctArgs2<T,S>) => OrderByResult} fct
|
|
591
|
+
* @returns {SelectQuery<T,S>}
|
|
482
592
|
*/
|
|
483
593
|
orderBy(fct) {
|
|
484
594
|
const queryParts = {
|
|
485
595
|
...this.#queryParts,
|
|
486
|
-
orderBy: fct(...this.#picks, pickOperators),
|
|
596
|
+
orderBy: fct(...this.#picks, pickOperators, this.#selectPick),
|
|
487
597
|
};
|
|
488
|
-
return new SelectQuery(
|
|
598
|
+
return new SelectQuery(
|
|
599
|
+
this.#picks,
|
|
600
|
+
this.#selectPick,
|
|
601
|
+
queryParts,
|
|
602
|
+
this.#database
|
|
603
|
+
);
|
|
489
604
|
}
|
|
490
605
|
|
|
491
606
|
/**
|
|
492
|
-
* @param {(...args:
|
|
493
|
-
* @returns {SelectQuery<T,
|
|
607
|
+
* @param {(...args: FctArgs2<T,S>) => GroupByResult} fct
|
|
608
|
+
* @returns {SelectQuery<T,S>}
|
|
494
609
|
*/
|
|
495
610
|
groupBy(fct) {
|
|
496
611
|
const queryParts = {
|
|
497
612
|
...this.#queryParts,
|
|
498
|
-
groupBy: fct(...this.#picks, pickOperators),
|
|
613
|
+
groupBy: fct(...this.#picks, pickOperators, this.#selectPick),
|
|
499
614
|
};
|
|
500
|
-
return new SelectQuery(
|
|
615
|
+
return new SelectQuery(
|
|
616
|
+
this.#picks,
|
|
617
|
+
this.#selectPick,
|
|
618
|
+
queryParts,
|
|
619
|
+
this.#database
|
|
620
|
+
);
|
|
501
621
|
}
|
|
502
622
|
|
|
503
623
|
/**
|
|
504
624
|
* @param {number} count
|
|
505
|
-
* @returns {SelectQuery<T,
|
|
625
|
+
* @returns {SelectQuery<T,S>}
|
|
506
626
|
*/
|
|
507
627
|
limit(count) {
|
|
508
628
|
const queryParts = {
|
|
509
629
|
...this.#queryParts,
|
|
510
630
|
limit: count,
|
|
511
631
|
};
|
|
512
|
-
return new SelectQuery(
|
|
632
|
+
return new SelectQuery(
|
|
633
|
+
this.#picks,
|
|
634
|
+
this.#selectPick,
|
|
635
|
+
queryParts,
|
|
636
|
+
this.#database
|
|
637
|
+
);
|
|
513
638
|
}
|
|
514
639
|
|
|
515
640
|
/**
|
|
516
641
|
* @param {number} count
|
|
517
|
-
* @returns {SelectQuery<T,
|
|
642
|
+
* @returns {SelectQuery<T,S>}
|
|
518
643
|
*/
|
|
519
644
|
offset(count) {
|
|
520
645
|
const queryParts = {
|
|
521
646
|
...this.#queryParts,
|
|
522
647
|
offset: count,
|
|
523
648
|
};
|
|
524
|
-
return new SelectQuery(
|
|
649
|
+
return new SelectQuery(
|
|
650
|
+
this.#picks,
|
|
651
|
+
this.#selectPick,
|
|
652
|
+
queryParts,
|
|
653
|
+
this.#database
|
|
654
|
+
);
|
|
525
655
|
}
|
|
526
656
|
}
|
|
527
657
|
|
|
@@ -587,12 +717,6 @@ class FromQuery {
|
|
|
587
717
|
);
|
|
588
718
|
}
|
|
589
719
|
|
|
590
|
-
/**
|
|
591
|
-
* @template {AnyObjectShape} U
|
|
592
|
-
* @template {[...ObjectShape[], ObjectShape]} V
|
|
593
|
-
* @typedef {(tableName: string, shape: U, joinFct: (...args: FctArgs<AllShapes<T,U>>) => BooleanPick) => FromQuery<V>} JoinFunction
|
|
594
|
-
*/
|
|
595
|
-
|
|
596
720
|
/**
|
|
597
721
|
* @template {AnyObjectShape} U
|
|
598
722
|
* @param {string} tableName
|
|
@@ -672,7 +796,7 @@ class FromQuery {
|
|
|
672
796
|
* Select field
|
|
673
797
|
* @template {keyof (T[0])} F
|
|
674
798
|
* @param {F} field
|
|
675
|
-
* @returns {SelectQuery<T,
|
|
799
|
+
* @returns {SelectQuery<T, {value: T[0][F]}>}
|
|
676
800
|
*/
|
|
677
801
|
field(field) {
|
|
678
802
|
/** @type {RowPick<T[0]>} */
|
|
@@ -682,14 +806,14 @@ class FromQuery {
|
|
|
682
806
|
select: {[field]: row.field(field)},
|
|
683
807
|
selectOneField: true,
|
|
684
808
|
};
|
|
685
|
-
return new SelectQuery(this.#picks, queryParts, this.#database);
|
|
809
|
+
return new SelectQuery(this.#picks, null, queryParts, this.#database);
|
|
686
810
|
}
|
|
687
811
|
|
|
688
812
|
/**
|
|
689
813
|
* Select fields
|
|
690
814
|
* @template {(keyof T[0])[]} F
|
|
691
815
|
* @param {F} fields
|
|
692
|
-
* @returns {SelectQuery<T,
|
|
816
|
+
* @returns {SelectQuery<T, {object: Pick<T[0], F[number]>}>}
|
|
693
817
|
*/
|
|
694
818
|
fields(fields) {
|
|
695
819
|
/** @type {RowPick<T[0]>} */
|
|
@@ -700,43 +824,43 @@ class FromQuery {
|
|
|
700
824
|
fields.map((fieldName) => [fieldName, row.field(fieldName)])
|
|
701
825
|
),
|
|
702
826
|
};
|
|
703
|
-
|
|
827
|
+
const shape = partialObjectShape(row.type, fields);
|
|
828
|
+
const selectPick = selectResultPick({object: shape});
|
|
829
|
+
return new SelectQuery(this.#picks, selectPick, queryParts, this.#database);
|
|
704
830
|
}
|
|
705
831
|
|
|
706
832
|
/**
|
|
707
|
-
* @template {
|
|
708
|
-
* @param {(...args: FctArgs<T>) =>
|
|
709
|
-
* @returns {SelectQuery<T,
|
|
833
|
+
* @template {SelectPicks} U
|
|
834
|
+
* @param {(...args: FctArgs<T>) => U} fct
|
|
835
|
+
* @returns {SelectQuery<T, SelectResultOf<U>>}
|
|
710
836
|
*/
|
|
711
837
|
select(fct) {
|
|
838
|
+
const selectPicks = fct(...this.#picks, pickOperators);
|
|
712
839
|
const queryParts = {
|
|
713
840
|
...this.#queryParts,
|
|
714
|
-
select:
|
|
841
|
+
select: selectPicks,
|
|
715
842
|
};
|
|
716
|
-
|
|
843
|
+
const selectPick = selectResultPick(selectResultOf(selectPicks));
|
|
844
|
+
return new SelectQuery(this.#picks, selectPick, queryParts, this.#database);
|
|
717
845
|
}
|
|
718
846
|
|
|
719
847
|
/**
|
|
720
|
-
* @returns {SelectQuery<T,
|
|
848
|
+
* @returns {SelectQuery<T, {object: T[number]}>}
|
|
721
849
|
*/
|
|
722
850
|
selectAll() {
|
|
723
851
|
const isRoot = this.#picks.every((pick) => pick.isRoot());
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
select = Object.fromEntries(
|
|
730
|
-
this.#picks.flatMap((pick) =>
|
|
731
|
-
Object.keys(pick.type.properties).map((key) => [key, pick.get(key)])
|
|
732
|
-
)
|
|
733
|
-
);
|
|
734
|
-
}
|
|
852
|
+
const selectPicks = Object.fromEntries(
|
|
853
|
+
this.#picks.flatMap((pick) =>
|
|
854
|
+
Object.keys(pick.type.properties).map((key) => [key, pick.get(key)])
|
|
855
|
+
)
|
|
856
|
+
);
|
|
735
857
|
const queryParts = {
|
|
736
858
|
...this.#queryParts,
|
|
737
|
-
select,
|
|
859
|
+
select: isRoot ? /** @type {const} */ ('*') : selectPicks,
|
|
738
860
|
};
|
|
739
|
-
|
|
861
|
+
/** @type {RowPick<T[number]>} */
|
|
862
|
+
const selectPick = selectResultPick(selectResultOf(selectPicks));
|
|
863
|
+
return new SelectQuery(this.#picks, selectPick, queryParts, this.#database);
|
|
740
864
|
}
|
|
741
865
|
|
|
742
866
|
/**
|
package/lib/query-to-sql.js
CHANGED
|
@@ -122,15 +122,17 @@ function groupByFields(groupBy, context) {
|
|
|
122
122
|
/**
|
|
123
123
|
* @param {QueryObj} query
|
|
124
124
|
* @param {any[] | null} [values]
|
|
125
|
+
* @param {Partial<ExpressionToSqlContext>} [options]
|
|
125
126
|
* @returns {{sql: string, values: any[] | null}}
|
|
126
127
|
*/
|
|
127
|
-
function queryToSql(query, values = []) {
|
|
128
|
+
function queryToSql(query, values = [], options = {}) {
|
|
128
129
|
/** @type {ExpressionToSqlContext} */
|
|
129
130
|
const context = {
|
|
130
131
|
values,
|
|
131
132
|
scope: query.scope,
|
|
132
133
|
useTableNames: query.joins && query.joins.length > 0,
|
|
133
134
|
equalOperator: 'IS',
|
|
135
|
+
...options,
|
|
134
136
|
};
|
|
135
137
|
const explain = query.explain ? 'EXPLAIN QUERY PLAN\n' : '';
|
|
136
138
|
const withs = withSql(query.withs, context);
|
package/package.json
CHANGED
package/test/sql.spec.js
CHANGED
|
@@ -388,6 +388,45 @@ describe('xcraft.pickaxe', function () {
|
|
|
388
388
|
expect(trimSql(result.sql)).to.be.equal(trimSql(sql));
|
|
389
389
|
});
|
|
390
390
|
|
|
391
|
+
it('reuse selected value', function () {
|
|
392
|
+
const builder = new QueryBuilder()
|
|
393
|
+
.from('test_table', TestUserShape)
|
|
394
|
+
.select((user, $) => ({
|
|
395
|
+
id: user.get('id'),
|
|
396
|
+
fullname: user.get('firstname').concat(user.get('lastname')),
|
|
397
|
+
kind: $.case(
|
|
398
|
+
[user.get('age').lt(13), 'kid'],
|
|
399
|
+
[user.get('age').lt(18), 'teenager'],
|
|
400
|
+
[user.get('age').lt(65), 'adult'],
|
|
401
|
+
'elder'
|
|
402
|
+
),
|
|
403
|
+
}))
|
|
404
|
+
.where((user, $, fields) =>
|
|
405
|
+
$.or(fields.get('fullname').length.gt(10), fields.get('kind').eq('kid'))
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const result = queryToSql(builder.query, null);
|
|
409
|
+
|
|
410
|
+
const sql = `
|
|
411
|
+
SELECT
|
|
412
|
+
id,
|
|
413
|
+
(firstname || lastname) AS fullname,
|
|
414
|
+
CASE
|
|
415
|
+
WHEN (age < 13) THEN 'kid'
|
|
416
|
+
WHEN (age < 18) THEN 'teenager'
|
|
417
|
+
WHEN (age < 65) THEN 'adult'
|
|
418
|
+
ELSE 'elder'
|
|
419
|
+
END AS kind
|
|
420
|
+
FROM test_table
|
|
421
|
+
WHERE (
|
|
422
|
+
LENGTH(fullname) > 10 OR
|
|
423
|
+
kind IS 'kid'
|
|
424
|
+
)
|
|
425
|
+
`;
|
|
426
|
+
|
|
427
|
+
expect(trimSql(result.sql)).to.be.equal(trimSql(sql));
|
|
428
|
+
});
|
|
429
|
+
|
|
391
430
|
it('join tables', function () {
|
|
392
431
|
const builder = new QueryBuilder()
|
|
393
432
|
.from('users', TestUserShape)
|