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.
@@ -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 `(${queryToSql(query, context.values).sql})`;
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;
@@ -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
 
@@ -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.#tableName) {
546
+ if (!this.#context) {
495
547
  return makePick(
496
548
  this.type.properties[fieldName],
497
- $o.field(fieldName, this.#tableName)
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
 
@@ -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
- * @typedef {BasePick} SelectValue
78
+ * @template {BasePick} T
79
+ * @typedef {T["type"]} PickType
78
80
  */
79
81
  /**
80
- * @template T
81
- * @typedef {T extends SelectValue ? T["type"]: never} SelectValueType
82
+ * @typedef {[BasePick, ...BasePick[]]} SelectPicksTuple
83
+ * @typedef {Record<string, BasePick>} SelectPicksObject
82
84
  */
83
85
  /**
84
- * @typedef {Record<string, SelectValue> | [SelectValue, ...SelectValue[]]} SelectResult
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
- * @typedef {{[K in keyof T]: t<SelectValueType<T[K]>>}} QueryResultOf
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: SelectResult | '*',
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 R
430
- * @extends {FinalQuery<R>}
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,R>}
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(this.#picks, queryParts, this.#database);
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: FctArgs<T>) => BooleanPick} fct
466
- * @returns {SelectQuery<T,R>}
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(this.#picks, queryParts, this.#database);
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: FctArgs<T>) => OrderByResult} fct
481
- * @returns {SelectQuery<T,R>}
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(this.#picks, queryParts, this.#database);
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: FctArgs<T>) => GroupByResult} fct
493
- * @returns {SelectQuery<T,R>}
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(this.#picks, queryParts, this.#database);
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,R>}
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(this.#picks, queryParts, this.#database);
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,R>}
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(this.#picks, queryParts, this.#database);
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, t<T[0][F]>>}
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, t<Pick<T[0], F[number]>>>}
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
- return new SelectQuery(this.#picks, queryParts, this.#database);
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 {SelectResult} R
708
- * @param {(...args: FctArgs<T>) => R} fct
709
- * @returns {SelectQuery<T, QueryResultOf<R>>}
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: fct(...this.#picks, pickOperators),
841
+ select: selectPicks,
715
842
  };
716
- return new SelectQuery(this.#picks, queryParts, this.#database);
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, t<T[number]>>}
848
+ * @returns {SelectQuery<T, {object: T[number]}>}
721
849
  */
722
850
  selectAll() {
723
851
  const isRoot = this.#picks.every((pick) => pick.isRoot());
724
- /** @type {QueryObj["select"]} */
725
- let select;
726
- if (isRoot) {
727
- select = '*';
728
- } else {
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
- return new SelectQuery(this.#picks, queryParts, this.#database);
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
  /**
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xcraft-core-pickaxe",
3
- "version": "0.1.26",
3
+ "version": "0.1.29",
4
4
  "description": "Query builder",
5
5
  "main": "index.js",
6
6
  "scripts": {
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)