pqb 0.0.7 → 0.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pqb",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Postgres query builder",
5
5
  "homepage": "https://porm.netlify.app/guide/query-builder.html",
6
6
  "repository": {
@@ -38,6 +38,10 @@ export class ArrayOfColumnsObjects<
38
38
  }
39
39
  }
40
40
 
41
+ export abstract class PluckResultColumnType<
42
+ C extends ColumnType,
43
+ > extends ColumnType<C['type'][], typeof Operators.any> {}
44
+
41
45
  type UnionKeyofToOvlds<S, U> = UnionToIntersection<
42
46
  U extends keyof S ? (f: U) => void : never
43
47
  >;
package/src/errors.ts CHANGED
@@ -1,7 +1,17 @@
1
- export class NotFoundError extends Error {
1
+ export class PormError extends Error {}
2
+
3
+ export class NotFoundError extends PormError {
2
4
  constructor(message = 'Record is not found') {
3
5
  super(message);
4
6
  }
5
7
  }
6
8
 
7
- export class MoreThanOneRowError extends Error {}
9
+ export class MoreThanOneRowError extends PormError {}
10
+
11
+ export class PormInternalError extends Error {}
12
+
13
+ export class UnhandledTypeError extends PormInternalError {
14
+ constructor(value: never) {
15
+ super(`Unhandled type: ${JSON.stringify(value)} received`);
16
+ }
17
+ }
@@ -10,6 +10,7 @@ import {
10
10
  ArrayOfColumnsObjects,
11
11
  ColumnsObject,
12
12
  NullableColumn,
13
+ PluckResultColumnType,
13
14
  } from '../columnSchema';
14
15
  import { getQueryParsers, isRaw, RawExpression } from '../common';
15
16
  import { pushQueryArray } from '../queryDataUtils';
@@ -59,6 +60,8 @@ type SelectResult<
59
60
  ? ArrayOfColumnsObjects<Arg['result']>
60
61
  : Arg['returnType'] extends 'valueOrThrow'
61
62
  ? Arg['result']['value']
63
+ : Arg['returnType'] extends 'pluck'
64
+ ? PluckResultColumnType<Arg['result']['pluck']>
62
65
  : Arg[isRequiredRelationKey] extends true
63
66
  ? ColumnsObject<Arg['result']>
64
67
  : NullableColumn<ColumnsObject<Arg['result']>>
@@ -1546,7 +1546,11 @@ export const testJoin = (
1546
1546
  describe('relation', () => {
1547
1547
  const withRelation = q as Query & {
1548
1548
  relations: {
1549
- message: { key: 'message'; query: typeof Message; joinQuery: Query };
1549
+ message: {
1550
+ key: 'message';
1551
+ query: typeof Message;
1552
+ joinQuery(fromQuery: Query, toQuery: Query): Query;
1553
+ };
1550
1554
  };
1551
1555
  };
1552
1556
 
@@ -1554,7 +1558,15 @@ export const testJoin = (
1554
1558
  message: {
1555
1559
  key: 'message',
1556
1560
  query: Message,
1557
- joinQuery: pushQueryOn(Message.clone(), Message, q, 'authorId', 'id'),
1561
+ joinQuery(fromQuery, toQuery) {
1562
+ return pushQueryOn(
1563
+ toQuery.clone(),
1564
+ toQuery,
1565
+ fromQuery,
1566
+ 'authorId',
1567
+ 'id',
1568
+ );
1569
+ },
1558
1570
  },
1559
1571
  };
1560
1572
 
@@ -440,6 +440,9 @@ export class WhereQueryBuilder<Q extends QueryBase = QueryBase>
440
440
  constructor(public table: Q['table'], public tableAlias: Q['tableAlias']) {
441
441
  super();
442
442
  this.__model = this as unknown as Query;
443
+ if (tableAlias) {
444
+ this.query.as = tableAlias;
445
+ }
443
446
  }
444
447
 
445
448
  clone<T extends this>(this: T): T {
package/src/relations.ts CHANGED
@@ -92,7 +92,7 @@ export type BaseRelation = {
92
92
  key: string;
93
93
  model: QueryWithTable;
94
94
  query: QueryWithTable;
95
- joinQuery: Query;
95
+ joinQuery(fromQuery: Query, toQuery: Query): Query;
96
96
  nestedCreateQuery: Query;
97
97
  nestedInsert?:
98
98
  | BelongsToNestedInsert
@@ -7,10 +7,7 @@ import { whereToSql } from './where';
7
7
  import { Query } from '../query';
8
8
 
9
9
  export const aggregateToSql = (
10
- model: Pick<
11
- Query,
12
- 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
13
- >,
10
+ model: Query,
14
11
  values: unknown[],
15
12
  item: AggregateItem,
16
13
  quotedAs?: string,
package/src/sql/having.ts CHANGED
@@ -15,10 +15,7 @@ const aggregateOptionNames: (keyof AggregateItemOptions)[] = [
15
15
 
16
16
  export const pushHavingSql = (
17
17
  sql: string[],
18
- model: Pick<
19
- Query,
20
- 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
21
- >,
18
+ model: Query,
22
19
  query: SelectQueryData,
23
20
  values: unknown[],
24
21
  quotedAs?: string,
@@ -28,10 +25,7 @@ export const pushHavingSql = (
28
25
  };
29
26
 
30
27
  export const havingToSql = (
31
- model: Pick<
32
- Query,
33
- 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
34
- >,
28
+ model: Query,
35
29
  query: SelectQueryData,
36
30
  values: unknown[],
37
31
  quotedAs?: string,
package/src/sql/insert.ts CHANGED
@@ -8,10 +8,7 @@ import { selectToSql } from './select';
8
8
  export const pushInsertSql = (
9
9
  sql: string[],
10
10
  values: unknown[],
11
- model: Pick<
12
- Query,
13
- 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
14
- >,
11
+ model: Query,
15
12
  query: InsertQueryData,
16
13
  quotedAs: string,
17
14
  ) => {
@@ -88,10 +85,7 @@ export const pushInsertSql = (
88
85
 
89
86
  export const pushReturningSql = (
90
87
  sql: string[],
91
- model: Pick<
92
- Query,
93
- 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
94
- >,
88
+ model: Query,
95
89
  query: QueryData,
96
90
  values: unknown[],
97
91
  quotedAs: string,
package/src/sql/join.ts CHANGED
@@ -19,10 +19,7 @@ type ItemOf3Or4Length =
19
19
  ];
20
20
 
21
21
  export const processJoinItem = (
22
- model: Pick<
23
- Query,
24
- 'whereQueryBuilder' | 'onQueryBuilder' | 'table' | 'shape' | 'relations'
25
- >,
22
+ model: Query,
26
23
  query: Pick<QueryData, 'as'>,
27
24
  values: unknown[],
28
25
  args: JoinItem['args'],
@@ -31,33 +28,40 @@ export const processJoinItem = (
31
28
  const [first] = args;
32
29
  if (typeof first === 'string') {
33
30
  if (first in model.relations) {
34
- const { key, joinQuery } = (model.relations as Record<string, Relation>)[
35
- first
36
- ];
31
+ const {
32
+ key,
33
+ query: toQuery,
34
+ joinQuery,
35
+ } = (model.relations as Record<string, Relation>)[first];
36
+
37
+ const joinedQuery = joinQuery(model, toQuery);
38
+ const joinedQueryData = joinedQuery.query;
37
39
 
38
40
  const table = (
39
- typeof joinQuery.query.from === 'string'
40
- ? joinQuery.query.from
41
- : joinQuery.table
41
+ typeof joinedQueryData.from === 'string'
42
+ ? joinedQueryData.from
43
+ : joinedQuery.table
42
44
  ) as string;
43
45
 
44
- let target = quoteSchemaAndTable(joinQuery.query.schema, table);
46
+ let target = quoteSchemaAndTable(joinedQueryData.schema, table);
45
47
 
46
- const as = joinQuery.query.as || key;
48
+ const as = joinedQueryData.as || key;
47
49
  if (as !== table) {
48
50
  target += ` AS ${q(as as string)}`;
49
51
  }
50
52
 
51
53
  const queryData = {
54
+ as: joinedQuery.query.as,
52
55
  and: [],
53
56
  or: [],
54
57
  } as {
58
+ as?: string;
55
59
  and: Exclude<QueryData['and'], undefined>;
56
60
  or: Exclude<QueryData['or'], undefined>;
57
61
  };
58
62
 
59
- if (joinQuery.query.and) queryData.and.push(...joinQuery.query.and);
60
- if (joinQuery.query.or) queryData.or.push(...joinQuery.query.or);
63
+ if (joinedQueryData.and) queryData.and.push(...joinedQueryData.and);
64
+ if (joinedQueryData.or) queryData.or.push(...joinedQueryData.or);
61
65
 
62
66
  const arg = (args[1] as ((q: unknown) => QueryBase) | undefined)?.(
63
67
  new model.onQueryBuilder({ table: model.table, query }, args[0]),
@@ -70,7 +74,7 @@ export const processJoinItem = (
70
74
 
71
75
  const joinAs = q(as as string);
72
76
  const onConditions = whereToSql(
73
- joinQuery,
77
+ joinedQuery,
74
78
  queryData,
75
79
  values,
76
80
  quotedAs,
package/src/sql/select.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { JsonItem, SelectFunctionItem, SelectQueryData } from './types';
2
- import { Expression, getRaw, isRaw } from '../common';
2
+ import { Expression, getRaw, isRaw, raw } from '../common';
3
3
  import { Query } from '../query';
4
4
  import { addValue, q, quoteFullColumn } from './common';
5
5
  import { aggregateToSql } from './aggregate';
6
6
  import { getQueryAs } from '../utils';
7
7
  import { RelationQuery, relationQueryKey } from '../relations';
8
+ import { PormInternalError, UnhandledTypeError } from '../errors';
8
9
 
9
10
  const jsonColumnOrMethodToSql = (
10
11
  column: string | JsonItem,
@@ -62,10 +63,7 @@ const jsonToSql = (
62
63
 
63
64
  export const pushSelectSql = (
64
65
  sql: string[],
65
- model: Pick<
66
- Query,
67
- 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
68
- >,
66
+ model: Query,
69
67
  query: Pick<SelectQueryData, 'select' | 'join'>,
70
68
  values: unknown[],
71
69
  quotedAs?: string,
@@ -74,10 +72,7 @@ export const pushSelectSql = (
74
72
  };
75
73
 
76
74
  export const selectToSql = (
77
- model: Pick<
78
- Query,
79
- 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
80
- >,
75
+ model: Query,
81
76
  query: Pick<SelectQueryData, 'select' | 'join'>,
82
77
  values: unknown[],
83
78
  quotedAs?: string,
@@ -99,12 +94,38 @@ export const selectToSql = (
99
94
  relationQuery._as(relationQuery.query[relationQueryKey] as string);
100
95
 
101
96
  const { returnType } = relationQuery.query;
102
- if (
103
- returnType === 'all' ||
104
- returnType === 'one' ||
105
- returnType === 'oneOrThrow'
106
- ) {
107
- relationQuery = relationQuery._json() as unknown as RelationQuery;
97
+ switch (returnType) {
98
+ case 'all':
99
+ case 'one':
100
+ case 'oneOrThrow':
101
+ relationQuery =
102
+ relationQuery._json() as unknown as typeof relationQuery;
103
+ break;
104
+ case 'pluck': {
105
+ const first = relationQuery.query.select?.[0];
106
+ if (!first)
107
+ throw new PormInternalError(`Nothing was selected for pluck`);
108
+
109
+ const selection = selectToSql(
110
+ relationQuery.__model,
111
+ relationQuery.query,
112
+ values,
113
+ q(getQueryAs(relationQuery)),
114
+ );
115
+
116
+ relationQuery.query.select = [
117
+ raw(`COALESCE(json_agg(${selection}), '[]')`),
118
+ ];
119
+ break;
120
+ }
121
+ case 'rows':
122
+ case 'value':
123
+ case 'valueOrThrow':
124
+ case 'rowCount':
125
+ case 'void':
126
+ break;
127
+ default:
128
+ throw new UnhandledTypeError(returnType);
108
129
  }
109
130
 
110
131
  list.push(`(${relationQuery.toSql(values).text}) AS ${as}`);
package/src/sql/types.ts CHANGED
@@ -277,13 +277,17 @@ export type WhereJsonPathEqualsItem = [
277
277
  ];
278
278
 
279
279
  export type WhereOnItem = {
280
- joinFrom: { table?: string; query: { as?: string } } | string;
281
- joinTo: { table?: string; query: { as?: string } } | string;
280
+ joinFrom: WhereOnJoinItem;
281
+ joinTo: WhereOnJoinItem;
282
282
  on:
283
283
  | [leftFullColumn: string, rightFullColumn: string]
284
284
  | [leftFullColumn: string, op: string, rightFullColumn: string];
285
285
  };
286
286
 
287
+ export type WhereOnJoinItem =
288
+ | { table?: string; query: { as?: string } }
289
+ | string;
290
+
287
291
  export type AggregateItemOptions = {
288
292
  as?: string;
289
293
  distinct?: boolean;
package/src/sql/where.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  WhereItem,
7
7
  WhereJsonPathEqualsItem,
8
8
  WhereOnItem,
9
+ WhereOnJoinItem,
9
10
  } from './types';
10
11
  import { addValue, q, qc, quoteFullColumn } from './common';
11
12
  import { EMPTY_OBJECT, getRaw, isRaw, RawExpression } from '../common';
@@ -14,10 +15,7 @@ import { processJoinItem } from './join';
14
15
 
15
16
  export const pushWhereSql = (
16
17
  sql: string[],
17
- model: Pick<
18
- Query,
19
- 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
20
- >,
18
+ model: Query,
21
19
  query: Pick<QueryData, 'as' | 'and' | 'or'>,
22
20
  values: unknown[],
23
21
  quotedAs?: string,
@@ -36,10 +34,7 @@ export const pushWhereSql = (
36
34
  };
37
35
 
38
36
  export const whereToSql = (
39
- model: Pick<
40
- Query,
41
- 'whereQueryBuilder' | 'onQueryBuilder' | 'table' | 'shape' | 'relations'
42
- >,
37
+ model: Query,
43
38
  query: Pick<QueryData, 'as' | 'and' | 'or'>,
44
39
  values: unknown[],
45
40
  quotedAs?: string,
@@ -234,15 +229,10 @@ const whereHandlers: Record<
234
229
  const item = value as WhereOnItem;
235
230
  const leftColumn = quoteFullColumn(
236
231
  item.on[0],
237
- typeof item.joinTo === 'string'
238
- ? q(item.joinTo)
239
- : q(getQueryAs(item.joinTo)),
232
+ getJoinItemSource(item.joinTo),
240
233
  );
241
234
 
242
- const joinTo =
243
- typeof item.joinFrom === 'string'
244
- ? item.joinFrom
245
- : q(getQueryAs(item.joinFrom));
235
+ const joinTo = getJoinItemSource(item.joinFrom);
246
236
 
247
237
  const [op, rightColumn] =
248
238
  item.on.length === 2
@@ -275,6 +265,10 @@ const whereHandlers: Record<
275
265
  },
276
266
  };
277
267
 
268
+ const getJoinItemSource = (joinItem: WhereOnJoinItem) => {
269
+ return typeof joinItem === 'string' ? q(joinItem) : q(getQueryAs(joinItem));
270
+ };
271
+
278
272
  const pushIn = (
279
273
  ands: string[],
280
274
  prefix: string,