xcraft-core-pickaxe 0.1.27 → 0.1.30

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.
@@ -3,6 +3,7 @@
3
3
  const {isAnyPick, BasePick} = require('./picks.js');
4
4
 
5
5
  /**
6
+ * @typedef {import("./query-builder.js").DbTable} DbTable
6
7
  * @typedef {import("./operators.js").Expression} Expression
7
8
  */
8
9
 
@@ -103,6 +104,14 @@ const operators = {
103
104
  return `LENGTH(${sql(value, context)})`;
104
105
  },
105
106
 
107
+ lower({value}, context) {
108
+ return `LOWER(${sql(value, context)})`;
109
+ },
110
+
111
+ upper({value}, context) {
112
+ return `UPPER(${sql(value, context)})`;
113
+ },
114
+
106
115
  substr({value, start, length}, context) {
107
116
  const valueSql = sql(value, context);
108
117
  const startSql = sql(start, context);
@@ -192,11 +201,14 @@ const operators = {
192
201
  if (!conditionSql) {
193
202
  return '';
194
203
  }
195
- return `WHEN (${conditionSql}) THEN (${sql(value, context)})`;
204
+ return `WHEN (${conditionSql}) THEN ${sql(value, context)}`;
196
205
  })
197
206
  .filter(Boolean)
198
207
  .join(' ');
199
- return `CASE ${whenSql} ELSE (${sql(elseValue, context)}) END`;
208
+ if (!whenSql) {
209
+ return sql(elseValue, context);
210
+ }
211
+ return `CASE ${whenSql} ELSE ${sql(elseValue, context)} END`;
200
212
  },
201
213
 
202
214
  abs({value}, context) {
@@ -243,7 +255,11 @@ const operators = {
243
255
 
244
256
  query({query}, context) {
245
257
  const {queryToSql} = require('./query-to-sql.js');
246
- return `(${queryToSql(query, context.values).sql})`;
258
+ return `(${
259
+ queryToSql(query, context.values, {
260
+ useTableNames: context.useTableNames,
261
+ }).sql
262
+ })`;
247
263
  },
248
264
 
249
265
  each({value}, context) {
@@ -280,6 +296,14 @@ const operators = {
280
296
  return `${sql(value, context)} DESC`;
281
297
  },
282
298
 
299
+ nullsFirst({value}, context) {
300
+ return `${sql(value, context)} NULLS FIRST`;
301
+ },
302
+
303
+ nullsLast({value}, context) {
304
+ return `${sql(value, context)} NULLS LAST`;
305
+ },
306
+
283
307
  count({field, distinct}, context) {
284
308
  if (!field) {
285
309
  return `COUNT(*)`;
@@ -303,7 +327,13 @@ const operators = {
303
327
  return `SUM(${distinct ? 'DISTINCT ' : ''}${sql(field, context)})`;
304
328
  },
305
329
 
306
- groupArray({field}, context) {
330
+ groupArray({field, orderBy}, context) {
331
+ if (orderBy) {
332
+ return `json_group_array(${sql(field, context)} ORDER BY ${sql(
333
+ orderBy,
334
+ context
335
+ )})`;
336
+ }
307
337
  return `json_group_array(${sql(field, context)})`;
308
338
  },
309
339
  };
@@ -322,6 +352,7 @@ function getExpression(expressionOrPick) {
322
352
  /**
323
353
  * @typedef {{
324
354
  * values: any[] | null,
355
+ * tablesUsed: DbTable[],
325
356
  * scope?: any,
326
357
  * useTableNames?: boolean,
327
358
  * equalOperator: 'IS' | '=',
@@ -345,6 +376,7 @@ function sql(expressionOrPick, context) {
345
376
  function expressionToSql(operator) {
346
377
  const context = {
347
378
  values: [],
379
+ tablesUsed: [],
348
380
  };
349
381
  return {
350
382
  sql: sql(operator, context),
package/lib/operators.js CHANGED
@@ -90,6 +90,20 @@ const operators = {
90
90
  });
91
91
  },
92
92
 
93
+ lower(value) {
94
+ return /** @type {const} */ ({
95
+ operator: 'lower',
96
+ value: op(value),
97
+ });
98
+ },
99
+
100
+ upper(value) {
101
+ return /** @type {const} */ ({
102
+ operator: 'upper',
103
+ value: op(value),
104
+ });
105
+ },
106
+
93
107
  substr(value, start, length) {
94
108
  value = op(value);
95
109
  start = op(start);
@@ -206,14 +220,14 @@ const operators = {
206
220
  and(...conditions) {
207
221
  return /** @type {const} */ ({
208
222
  operator: 'and',
209
- conditions,
223
+ conditions: conditions.map(op),
210
224
  });
211
225
  },
212
226
 
213
227
  or(...conditions) {
214
228
  return /** @type {const} */ ({
215
229
  operator: 'or',
216
- conditions,
230
+ conditions: conditions.map(op),
217
231
  });
218
232
  },
219
233
 
@@ -350,6 +364,20 @@ const operators = {
350
364
  });
351
365
  },
352
366
 
367
+ nullsFirst(value) {
368
+ return /** @type {const} */ ({
369
+ operator: 'nullsFirst',
370
+ value,
371
+ });
372
+ },
373
+
374
+ nullsLast(value) {
375
+ return /** @type {const} */ ({
376
+ operator: 'nullsLast',
377
+ value,
378
+ });
379
+ },
380
+
353
381
  count(field, distinct) {
354
382
  return /** @type {const} */ ({
355
383
  operator: 'count',
@@ -386,10 +414,11 @@ const operators = {
386
414
  });
387
415
  },
388
416
 
389
- groupArray(field) {
417
+ groupArray(field, orderBy) {
390
418
  return /** @type {const} */ ({
391
419
  operator: 'groupArray',
392
420
  field,
421
+ orderBy,
393
422
  });
394
423
  },
395
424
  };
@@ -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
 
@@ -53,7 +56,7 @@ const pickOperators = {
53
56
  },
54
57
 
55
58
  /**
56
- * @param {BooleanPick[]} conditions
59
+ * @param {(BooleanPick | boolean)[]} conditions
57
60
  * @returns {BooleanPick}
58
61
  */
59
62
  and(...conditions) {
@@ -61,7 +64,7 @@ const pickOperators = {
61
64
  },
62
65
 
63
66
  /**
64
- * @param {BooleanPick[]} conditions
67
+ * @param {(BooleanPick | boolean)[]} conditions
65
68
  * @returns {BooleanPick}
66
69
  */
67
70
  or(...conditions) {
@@ -105,6 +108,31 @@ const pickOperators = {
105
108
  return new ValuePick(newType, $o.case(whenConditions, elseValue));
106
109
  },
107
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
+
108
136
  /**
109
137
  * @param {BasePick} [field]
110
138
  * @param {boolean} [distinct]
@@ -146,9 +174,10 @@ const pickOperators = {
146
174
  /**
147
175
  * @template {AnyTypeOrShape} T
148
176
  * @param {ValuePick<T>} field
177
+ * @param {BasePick} [orderBy]
149
178
  */
150
- groupArray(field) {
151
- return new ArrayPick(array(field.type), $o.groupArray(field));
179
+ groupArray(field, orderBy) {
180
+ return new ArrayPick(array(field.type), $o.groupArray(field, orderBy));
152
181
  },
153
182
  };
154
183
 
package/lib/picks.js CHANGED
@@ -86,6 +86,14 @@ class ValuePick extends BasePick {
86
86
  super(type, expression);
87
87
  }
88
88
 
89
+ /**
90
+ * @template {AnyTypeOrShape} T
91
+ * @param {T} type
92
+ */
93
+ as(type) {
94
+ return makePick(type, this.expression);
95
+ }
96
+
89
97
  /**
90
98
  * @template {AnyObjectShape} T
91
99
  * @param {T} shape
@@ -179,6 +187,14 @@ class StringPick extends ValuePick {
179
187
  return new NumberPick(number, $o.stringLength(this.expression));
180
188
  }
181
189
 
190
+ lower() {
191
+ return new StringPick(this.type, $o.lower(this.expression));
192
+ }
193
+
194
+ upper() {
195
+ return new StringPick(this.type, $o.upper(this.expression));
196
+ }
197
+
182
198
  /**
183
199
  * @param {number | BasePick<NumberType>} start
184
200
  * @param {number | BasePick<NumberType>} [length]
@@ -438,6 +454,53 @@ class ArrayPick extends BasePick {
438
454
  );
439
455
  }
440
456
 
457
+ /**
458
+ * @param {(value: PickOf<T>) => BooleanPick} func
459
+ */
460
+ filter(func) {
461
+ return new ArrayPick(
462
+ this.type,
463
+ $o.query({
464
+ from: $o.each(this.expression),
465
+ select: [$o.groupArray($o.eachValue())],
466
+ where: func(makePick(this.type.valuesType, $o.eachValue())),
467
+ })
468
+ );
469
+ }
470
+
471
+ /**
472
+ * @template {AnyTypeOrShape} U
473
+ * @param {(value: PickOf<T>) => BasePick<U>} func
474
+ */
475
+ map(func) {
476
+ const newValuePick = func(makePick(this.type.valuesType, $o.eachValue()));
477
+ return new ArrayPick(
478
+ array(newValuePick.type),
479
+ $o.query({
480
+ from: $o.each(this.expression),
481
+ select: [$o.groupArray(newValuePick)],
482
+ })
483
+ );
484
+ }
485
+
486
+ /**
487
+ * @param {(value: PickOf<T>) => BasePick} [func]
488
+ */
489
+ sort(func = (pick) => pick) {
490
+ return new ArrayPick(
491
+ this.type,
492
+ $o.query({
493
+ from: $o.each(this.expression),
494
+ select: [
495
+ $o.groupArray(
496
+ $o.eachValue(),
497
+ func(makePick(this.type.valuesType, $o.eachValue()))
498
+ ),
499
+ ],
500
+ })
501
+ );
502
+ }
503
+
441
504
  /**
442
505
  * @param {(value: PickOf<T>) => BooleanPick} func
443
506
  */
@@ -1,5 +1,6 @@
1
1
  // @ts-check
2
2
 
3
+ const path = require('node:path');
3
4
  const {
4
5
  ObjectType,
5
6
  ArrayType,
@@ -43,11 +44,15 @@ const partialObjectShape = require('./partial-object-shape.js');
43
44
  */
44
45
 
45
46
  /**
46
- * @typedef {{db?: string, table: string, alias?: string} | {db?: undefined, table: Subquery, alias?: string}} TableAndAlias
47
+ * @typedef {{db?: string, dbId?: string, table: string, alias?: string}} DbTable
47
48
  */
48
49
 
49
50
  /**
50
- * @typedef {string | Subquery | TableAndAlias} QueryTable
51
+ * @typedef {{db?: undefined, dbId?: undefined, table: Subquery, alias?: string}} SubqueryAndAlias
52
+ */
53
+
54
+ /**
55
+ * @typedef {string | Subquery | DbTable | SubqueryAndAlias} QueryTable
51
56
  */
52
57
 
53
58
  /**
@@ -55,6 +60,7 @@ const partialObjectShape = require('./partial-object-shape.js');
55
60
  * @template {AnyObjectShape} U
56
61
  * @typedef {{
57
62
  * db?: string,
63
+ * dbId?: string,
58
64
  * table: string,
59
65
  * alias?: string,
60
66
  * shape?: T,
@@ -281,9 +287,15 @@ function getTableName(table) {
281
287
  * @template {AnyObjectShape} T
282
288
  * @param {AnyTableSchema} tableSchema
283
289
  * @param {T} shape
284
- * @returns {{pick: RowPick<GetShape<T>>, condition: BooleanPick | null}}
290
+ * @returns {{table: DbTable, pick: RowPick<GetShape<T>>, condition: BooleanPick | null}}
285
291
  */
286
292
  function useTableSchema(tableSchema, shape) {
293
+ const table = {
294
+ db: tableSchema.db,
295
+ dbId: tableSchema.dbId,
296
+ table: tableSchema.table,
297
+ alias: tableSchema.alias,
298
+ };
287
299
  const type = toObjectType(shape);
288
300
  const tableName = getTableName(tableSchema);
289
301
  let pick = rowPick(type, tableName);
@@ -295,7 +307,7 @@ function useTableSchema(tableSchema, shape) {
295
307
  }
296
308
  pick = tableSchema.scope(basePick).toRowPick();
297
309
  }
298
- return {pick, condition};
310
+ return {table, pick, condition};
299
311
  }
300
312
 
301
313
  /**
@@ -456,6 +468,15 @@ class FinalQuery {
456
468
  return queryToSql(this.#queryParts, null).sql;
457
469
  }
458
470
 
471
+ json() {
472
+ const {sql, values, tables} = queryToSql(this.#queryParts);
473
+ return {sql, values, tables};
474
+ }
475
+
476
+ getMapRow() {
477
+ return this.#getMapRow();
478
+ }
479
+
459
480
  /**
460
481
  * @returns {R | undefined}
461
482
  */
@@ -693,14 +714,14 @@ class FromQuery {
693
714
  */
694
715
  _join(joinOperator, tableName, shape, joinFct, mergePicks) {
695
716
  const tableSchema = this.#getTableSchema(tableName, shape);
696
- const {pick, condition} = useTableSchema(tableSchema, shape);
717
+ const {table, pick, condition} = useTableSchema(tableSchema, shape);
697
718
 
698
719
  const joinCondition = joinFct(...this.#picks, pick, pickOperators);
699
720
  const queryParts = {
700
721
  ...this.#queryParts,
701
722
  joins: mergeJoin(this.#queryParts.joins, {
702
723
  operator: joinOperator,
703
- table: tableSchema,
724
+ table,
704
725
  constraint: condition
705
726
  ? pickOperators.and(condition, joinCondition)
706
727
  : joinCondition,
@@ -845,7 +866,7 @@ class FromQuery {
845
866
  }
846
867
 
847
868
  /**
848
- * @returns {SelectQuery<T, T[number]>}
869
+ * @returns {SelectQuery<T, {object: T[number]}>}
849
870
  */
850
871
  selectAll() {
851
872
  const isRoot = this.#picks.every((pick) => pick.isRoot());
@@ -945,12 +966,11 @@ class QueryBuilder {
945
966
  */
946
967
  from(tableName, shape) {
947
968
  const tableSchema = this.#getTableSchema(tableName, shape);
948
- const from = tableSchema;
969
+ const {table, pick, condition} = useTableSchema(tableSchema, shape);
949
970
  const queryParts = {
950
- from,
971
+ from: table,
951
972
  withs: this.#withs,
952
973
  };
953
- const {pick, condition} = useTableSchema(tableSchema, shape);
954
974
  if (condition) {
955
975
  queryParts.where = condition;
956
976
  }
@@ -966,4 +986,5 @@ class QueryBuilder {
966
986
  module.exports = {
967
987
  QueryBuilder,
968
988
  FromQuery,
989
+ FinalQuery,
969
990
  };
@@ -1,5 +1,6 @@
1
1
  // @ts-check
2
2
  /**
3
+ * @typedef {import("./query-builder.js").DbTable} DbTable
3
4
  * @typedef {import("./query-builder.js").QueryObj} QueryObj
4
5
  * @typedef {import("./operator-to-sql.js").ExpressionToSqlContext} ExpressionToSqlContext
5
6
  */
@@ -21,7 +22,8 @@ function withSql(withs, context) {
21
22
  .map(({name, query}) => {
22
23
  let sql = '';
23
24
  sql += `${name} AS (\n`;
24
- sql += queryToSql(query, context.values).sql;
25
+ sql += queryToSql(query, context.values, {tablesUsed: context.tablesUsed})
26
+ .sql;
25
27
  sql += '\n)';
26
28
  return sql;
27
29
  })
@@ -37,6 +39,7 @@ function withSql(withs, context) {
37
39
  function tableSql(table, context) {
38
40
  // Note: table is not validated
39
41
  if (typeof table === 'string') {
42
+ context.tablesUsed.push({table});
40
43
  return table;
41
44
  }
42
45
  if (!('table' in table)) {
@@ -44,6 +47,7 @@ function tableSql(table, context) {
44
47
  }
45
48
  let resultSql = '';
46
49
  if (typeof table.table === 'string') {
50
+ context.tablesUsed.push(table);
47
51
  if (typeof table.db === 'string') {
48
52
  resultSql += `${table.db}.`;
49
53
  }
@@ -122,15 +126,18 @@ function groupByFields(groupBy, context) {
122
126
  /**
123
127
  * @param {QueryObj} query
124
128
  * @param {any[] | null} [values]
125
- * @returns {{sql: string, values: any[] | null}}
129
+ * @param {Partial<ExpressionToSqlContext>} [options]
130
+ * @returns {{sql: string, values: any[] | null, tables: DbTable[]}}
126
131
  */
127
- function queryToSql(query, values = []) {
132
+ function queryToSql(query, values = [], options = {}) {
128
133
  /** @type {ExpressionToSqlContext} */
129
134
  const context = {
130
135
  values,
136
+ tablesUsed: [],
131
137
  scope: query.scope,
132
138
  useTableNames: query.joins && query.joins.length > 0,
133
139
  equalOperator: 'IS',
140
+ ...options,
134
141
  };
135
142
  const explain = query.explain ? 'EXPLAIN QUERY PLAN\n' : '';
136
143
  const withs = withSql(query.withs, context);
@@ -167,7 +174,7 @@ function queryToSql(query, values = []) {
167
174
  }
168
175
  result += '\n' + `OFFSET ${query.offset}`;
169
176
  }
170
- return {sql: result, values};
177
+ return {sql: result, values, tables: context.tablesUsed};
171
178
  }
172
179
 
173
180
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xcraft-core-pickaxe",
3
- "version": "0.1.27",
3
+ "version": "0.1.30",
4
4
  "description": "Query builder",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test/sql.spec.js CHANGED
@@ -412,9 +412,9 @@ describe('xcraft.pickaxe', function () {
412
412
  id,
413
413
  (firstname || lastname) AS fullname,
414
414
  CASE
415
- WHEN age < 13 THEN 'kid'
416
- WHEN age < 18 THEN 'teenager'
417
- WHEN age < 65 THEN 'adult'
415
+ WHEN (age < 13) THEN 'kid'
416
+ WHEN (age < 18) THEN 'teenager'
417
+ WHEN (age < 65) THEN 'adult'
418
418
  ELSE 'elder'
419
419
  END AS kind
420
420
  FROM test_table