xcraft-core-pickaxe 0.1.26 → 0.1.27
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 +14 -0
- package/lib/operators.js +11 -0
- package/lib/partial-object-shape.js +17 -0
- package/lib/pick-operators.js +29 -0
- package/lib/pick-or-value.js +6 -2
- package/lib/picks.js +22 -16
- package/lib/query-builder.js +180 -56
- package/package.json +1 -1
- package/test/sql.spec.js +39 -0
package/lib/operator-to-sql.js
CHANGED
|
@@ -185,6 +185,20 @@ 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
|
+
return `CASE ${whenSql} ELSE (${sql(elseValue, context)}) END`;
|
|
200
|
+
},
|
|
201
|
+
|
|
188
202
|
abs({value}, context) {
|
|
189
203
|
return `ABS(${sql(value, context)})`;
|
|
190
204
|
},
|
package/lib/operators.js
CHANGED
|
@@ -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} */ ({
|
|
@@ -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
|
@@ -29,6 +29,11 @@ const {getPickOrValueType} = require('./pick-or-value.js');
|
|
|
29
29
|
* @typedef {import('./pick-or-value.js').PickOrValueType<T>} PickOrValueType
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @template {PickOrValue[]} T
|
|
34
|
+
* @typedef {{[K in keyof T]: PickOrValueType<T[K]>}} PickOrValueTypes
|
|
35
|
+
*/
|
|
36
|
+
|
|
32
37
|
const pickOperators = {
|
|
33
38
|
...$o,
|
|
34
39
|
|
|
@@ -76,6 +81,30 @@ const pickOperators = {
|
|
|
76
81
|
return new ValuePick(newType, $o.if(condition, a, b));
|
|
77
82
|
},
|
|
78
83
|
|
|
84
|
+
/**
|
|
85
|
+
* @template {PickOrValue[]} T
|
|
86
|
+
* @typedef {{[K in keyof T]: [BooleanPick | boolean, T[K]]}} CaseConditions
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @template {[PickOrValue, ...PickOrValue[]]} T
|
|
91
|
+
* @template {PickOrValue} U
|
|
92
|
+
* @param {[...CaseConditions<T>, U]} conditions
|
|
93
|
+
* @returns {ValuePick<UnionType<[...PickOrValueTypes<T>, PickOrValueType<U>]>>}
|
|
94
|
+
*/
|
|
95
|
+
case(...conditions) {
|
|
96
|
+
const whenConditions = /** @type {CaseConditions<T>} */ (
|
|
97
|
+
//
|
|
98
|
+
conditions.slice(0, -1)
|
|
99
|
+
);
|
|
100
|
+
const whenTypes = /** @type {PickOrValueTypes<T>} */ (whenConditions.map(
|
|
101
|
+
(condition) => getPickOrValueType(condition[1])
|
|
102
|
+
));
|
|
103
|
+
const elseValue = /** @type {U} */ (conditions.at(-1));
|
|
104
|
+
const newType = union(...whenTypes, getPickOrValueType(elseValue));
|
|
105
|
+
return new ValuePick(newType, $o.case(whenConditions, elseValue));
|
|
106
|
+
},
|
|
107
|
+
|
|
79
108
|
/**
|
|
80
109
|
* @param {BasePick} [field]
|
|
81
110
|
* @param {boolean} [distinct]
|
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
|
/**
|
|
@@ -467,22 +475,19 @@ function isAnyPick(value) {
|
|
|
467
475
|
* @extends {BasePick<ObjectType<T>>}
|
|
468
476
|
*/
|
|
469
477
|
class RowPick extends BasePick {
|
|
470
|
-
|
|
471
|
-
* @type {string | null}
|
|
472
|
-
*/
|
|
473
|
-
#tableName = null;
|
|
478
|
+
#context;
|
|
474
479
|
|
|
475
480
|
/**
|
|
476
481
|
* @param {ObjectType<T>} type
|
|
477
|
-
* @param {{expression: Expression} | {tableName: string}} context
|
|
482
|
+
* @param {{expression: Expression} | {tableName: string} | null} context
|
|
478
483
|
*/
|
|
479
484
|
constructor(type, context) {
|
|
480
|
-
if ('expression' in context) {
|
|
485
|
+
if (context && 'expression' in context) {
|
|
481
486
|
super(type, context.expression);
|
|
482
487
|
} else {
|
|
483
488
|
super(type, null);
|
|
484
|
-
this.#tableName = context.tableName;
|
|
485
489
|
}
|
|
490
|
+
this.#context = context;
|
|
486
491
|
}
|
|
487
492
|
|
|
488
493
|
/**
|
|
@@ -491,10 +496,16 @@ class RowPick extends BasePick {
|
|
|
491
496
|
* @returns {PickOf<T[K]>}
|
|
492
497
|
*/
|
|
493
498
|
field(fieldName) {
|
|
494
|
-
if (this.#
|
|
499
|
+
if (!this.#context) {
|
|
495
500
|
return makePick(
|
|
496
501
|
this.type.properties[fieldName],
|
|
497
|
-
$o.field(fieldName,
|
|
502
|
+
$o.field(fieldName, null)
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
if ('tableName' in this.#context) {
|
|
506
|
+
return makePick(
|
|
507
|
+
this.type.properties[fieldName],
|
|
508
|
+
$o.field(fieldName, this.#context.tableName)
|
|
498
509
|
);
|
|
499
510
|
}
|
|
500
511
|
return makePick(
|
|
@@ -514,16 +525,11 @@ class RowPick extends BasePick {
|
|
|
514
525
|
}
|
|
515
526
|
|
|
516
527
|
isRoot() {
|
|
517
|
-
return Boolean(this.#tableName);
|
|
528
|
+
return Boolean(!this.#context || 'tableName' in this.#context);
|
|
518
529
|
}
|
|
519
530
|
|
|
520
531
|
toOptional() {
|
|
521
|
-
return new RowPick(
|
|
522
|
-
optionalObjectType(this.type),
|
|
523
|
-
this.#tableName
|
|
524
|
-
? {tableName: this.#tableName}
|
|
525
|
-
: {expression: this.expression}
|
|
526
|
-
);
|
|
532
|
+
return new RowPick(optionalObjectType(this.type), this.#context);
|
|
527
533
|
}
|
|
528
534
|
}
|
|
529
535
|
|
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, 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/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)
|