@trenskow/pged 5.1.37 → 5.1.38

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.
@@ -50,6 +50,25 @@ export default class QueryBuilder extends CustomPromise {
50
50
 
51
51
  }
52
52
 
53
+ get _transforms() {
54
+ return {
55
+ function: ({ name, parameters }) => {
56
+ return `${name}(${parameters.map((parameter) => this._resolveConstruct(parameter)).join(', ')})`;
57
+ }
58
+ };
59
+ }
60
+
61
+ _resolveConstruct(construct, transforms) {
62
+ if (typeof construct === 'string') {
63
+ if (construct[0] === ':') return this._dbCase(construct.substring(1))
64
+ else {
65
+ this._queryParameters = (this._queryParameters || []).concat([construct]);
66
+ return `$${this._queryParameters.length}`;
67
+ }
68
+ }
69
+ return (transforms || this._transforms)[construct.type](construct);
70
+ }
71
+
53
72
  _dbCasePart(input, quote) {
54
73
  return input
55
74
  .split(/"|'/)
@@ -101,7 +120,23 @@ export default class QueryBuilder extends CustomPromise {
101
120
  }
102
121
 
103
122
  select(keys = ['*']) {
104
- if (typeof keys !== 'string' || keys[0] !== ':') {
123
+ if (typeof keys === 'function') {
124
+ keys = keys({
125
+ rank: ({ column, language, query }) => ({
126
+ type: 'alias',
127
+ name: 'rank',
128
+ value: {
129
+ type: 'function',
130
+ name: 'ts_rank',
131
+ parameters: [ column, {
132
+ type: 'function',
133
+ name: 'websearch_to_tsquery',
134
+ parameters: (language ? [language] : []).concat([query])
135
+ } ]
136
+ }
137
+ })
138
+ });
139
+ } else if (typeof keys !== 'string' || keys[0] !== ':') {
105
140
  if (!Array.isArray(keys)) {
106
141
  keys = [].concat(...keys.split('"').map((key, idx) => {
107
142
  if (idx % 2 == 0) return key.split(/, ?/);
@@ -113,6 +148,11 @@ export default class QueryBuilder extends CustomPromise {
113
148
  return this;
114
149
  }
115
150
 
151
+ distinct(column) {
152
+ this._distinct = column || true;
153
+ return this;
154
+ }
155
+
116
156
  groupBy(element) {
117
157
  this._groupBy = element;
118
158
  return this;
@@ -202,6 +242,7 @@ export default class QueryBuilder extends CustomPromise {
202
242
  }));
203
243
  } else {
204
244
  if (typeof conditions !== 'object') throw new TypeError('Conditions must be an object.');
245
+ else if (['comparison'].includes(conditions?.type)) return conditions;
205
246
  return Object.keys(conditions).map((key) => {
206
247
  let obj = {};
207
248
  const dbKey = this._dbCase(key);
@@ -218,6 +259,20 @@ export default class QueryBuilder extends CustomPromise {
218
259
  }
219
260
 
220
261
  where(conditions) {
262
+ if (typeof conditions === 'function') {
263
+ conditions = conditions({
264
+ search: ({ column, language, query }) => ({
265
+ type: 'comparison',
266
+ lhs: column,
267
+ operator: '@@',
268
+ rhs: {
269
+ type: 'function',
270
+ name: 'websearch_to_tsquery',
271
+ parameters: (language ? [language] : []).concat([query])
272
+ }
273
+ })
274
+ })
275
+ }
221
276
  this._conditions = this._conditions.concat(this._formalizeConditions(conditions));
222
277
  return this;
223
278
  }
@@ -307,45 +362,6 @@ export default class QueryBuilder extends CustomPromise {
307
362
  return this;
308
363
  }
309
364
 
310
- search({ column, query, language, type = 'web' }) {
311
-
312
- if (language?.[0] === ':') language = this._dbCase(language.substring(1));
313
- else {
314
- this._queryParameters = (this._queryParameters || []).concat([language]);
315
- language = `$${this._queryParameters.length}`;
316
- }
317
-
318
- this._queryParameters = (this._queryParameters || []).concat([query]);
319
- query = `$${this._queryParameters.length}`;
320
-
321
- const vectorConverter = type === 'web' ? 'websearch_to_tsquery' : 'to_tsquery';
322
-
323
- let vectorConverterArgs = [query];
324
-
325
- if (language) {
326
- vectorConverterArgs.unshift(language);
327
- }
328
-
329
- column = this._dbCase(column);
330
-
331
- this._selectKeys = (this._selectKeys || []).concat([
332
- `ts_rank(${column}, ${vectorConverter}(${vectorConverterArgs.join(', ')})) as rank`
333
- ]);
334
-
335
- this._sortingKeys = this._sortingKeys.concat([{
336
- key: 'rank',
337
- order: 'desc'
338
- }]);
339
-
340
- return this
341
- .where({
342
- $fullText: {
343
- [`:${column}`]: `:${vectorConverter}(${vectorConverterArgs.join(', ')})`
344
- }
345
- });
346
-
347
- }
348
-
349
365
  _canQuote(key) {
350
366
  if (key === '*') return false;
351
367
  if (key.toLowerCase().includes(' as ')) return false;
@@ -354,12 +370,15 @@ export default class QueryBuilder extends CustomPromise {
354
370
  }
355
371
 
356
372
  _buildKeys(keys = ['*'], quote) {
357
- return keys.map((key) => {
373
+ return (this._distinct ? [this._distinct === true ? 'distinct' : `distinct on (${this._dbCase(this._distinct)})`] : []).concat(keys.map((key) => {
374
+ if (typeof key === 'object' && key !== null && !Array.isArray(key)) return this._resolveConstruct(key, {
375
+ alias: ({ name, value }) => `${this._resolveConstruct(value)} as ${name}`
376
+ })
358
377
  if (key.substring(0, 1) == ':') return key.substring(1);
359
378
  let as = key.split(':');
360
379
  if (as.length == 1) return this._dbCase(as[0], quote && this._canQuote(key));
361
380
  return `${this._dbCase(as[0], quote)} as ${this._dbCase(as[1])}`;
362
- }).concat(this._paginated ? `count(${this._table}.*) over() as total` : []).join(', ');
381
+ }).concat(this._paginated ? `count(${this._table}.*) over() as total` : []).join(', ')).join(' ');
363
382
  }
364
383
 
365
384
  _formatParameter(keyPath, value) {
@@ -417,6 +436,14 @@ export default class QueryBuilder extends CustomPromise {
417
436
 
418
437
  const result = conditions.map((condition) => {
419
438
 
439
+ if (['comparison'].includes(condition?.type)) {
440
+ return this._resolveConstruct(condition, {
441
+ comparison: ({ lhs, operator, rhs }) => {
442
+ return `${this._resolveConstruct(lhs)} ${operator} ${this._resolveConstruct(rhs)}`;
443
+ }
444
+ })
445
+ }
446
+
420
447
  let key = Object.keys(condition)[0];
421
448
 
422
449
  if (key.substring(0, 1) == '$') {
@@ -680,7 +707,7 @@ export default class QueryBuilder extends CustomPromise {
680
707
  .filter((table) => !Object.keys(tableInformation).includes(table));
681
708
 
682
709
  await Promise.all(tables.map(async (table) => {
683
- const rows = await this._connection.exec(`SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${table}';`, [], { format: 'raw' });
710
+ const rows = await this._connection.exec(`SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${this._dbCase(table)}';`, [], { format: 'raw' });
684
711
  Object.assign(tableInformation, Object.fromEntries([[table, Object.fromEntries(rows.map((row) => {
685
712
  return [row.column_name, row.data_type];
686
713
  }))]]));
@@ -693,8 +720,21 @@ export default class QueryBuilder extends CustomPromise {
693
720
 
694
721
  await this._resolveTableInformation();
695
722
 
723
+ if (this._paginated && !this._first && this._limit === 0) {
724
+ delete this._paginated;
725
+ delete this._offset;
726
+ delete this._limit;
727
+ this._sortingKeys = [];
728
+ return {
729
+ total: parseInt(await this.count('*')._exec()),
730
+ items: []
731
+ };
732
+ }
733
+
696
734
  const [query, parameters] = this._build();
697
735
 
736
+ console.info(query);
737
+
698
738
  let rows = await this._connection.exec(
699
739
  query,
700
740
  parameters,
@@ -708,14 +748,8 @@ export default class QueryBuilder extends CustomPromise {
708
748
  }
709
749
 
710
750
  if (this._paginated && !this._first) {
711
- let total;
712
- if (rows.length == 0) {
713
- delete this._paginated;
714
- delete this._offset;
715
- delete this._limit;
716
- this._sortingKeys = [];
717
- total = parseInt(await this.count('*')._exec());
718
- } else {
751
+ let total = 0;
752
+ if (rows.length > 0) {
719
753
  total = parseInt(((rows || [])[0] || {})['total'] || 0);
720
754
  }
721
755
  rows.forEach((item) => delete item.total);
@@ -737,4 +771,4 @@ export default class QueryBuilder extends CustomPromise {
737
771
 
738
772
  }
739
773
 
740
- }
774
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trenskow/pged",
3
- "version": "5.1.37",
3
+ "version": "5.1.38",
4
4
  "description": "Just a silly little db management and query builder for PostgreSQL.",
5
5
  "main": "index.js",
6
6
  "type": "module",