pqb 0.0.1

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.
Files changed (122) hide show
  1. package/dist/index.d.ts +3630 -0
  2. package/dist/index.esm.js +4587 -0
  3. package/dist/index.esm.js.map +1 -0
  4. package/dist/index.js +4691 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +59 -0
  7. package/rollup.config.js +35 -0
  8. package/src/adapter.test.ts +10 -0
  9. package/src/adapter.ts +171 -0
  10. package/src/columnSchema/array.ts +21 -0
  11. package/src/columnSchema/boolean.ts +10 -0
  12. package/src/columnSchema/columnType.test.ts +129 -0
  13. package/src/columnSchema/columnType.ts +77 -0
  14. package/src/columnSchema/columnTypes.ts +145 -0
  15. package/src/columnSchema/columnsSchema.test.ts +32 -0
  16. package/src/columnSchema/columnsSchema.ts +100 -0
  17. package/src/columnSchema/commonMethods.ts +130 -0
  18. package/src/columnSchema/dateTime.ts +104 -0
  19. package/src/columnSchema/enum.ts +13 -0
  20. package/src/columnSchema/index.ts +11 -0
  21. package/src/columnSchema/json/array.ts +55 -0
  22. package/src/columnSchema/json/discriminatedUnion.ts +91 -0
  23. package/src/columnSchema/json/enum.ts +29 -0
  24. package/src/columnSchema/json/instanceOf.ts +16 -0
  25. package/src/columnSchema/json/intersection.ts +23 -0
  26. package/src/columnSchema/json/lazy.ts +22 -0
  27. package/src/columnSchema/json/literal.ts +12 -0
  28. package/src/columnSchema/json/map.ts +29 -0
  29. package/src/columnSchema/json/nativeEnum.ts +30 -0
  30. package/src/columnSchema/json/nullable.ts +33 -0
  31. package/src/columnSchema/json/nullish.ts +30 -0
  32. package/src/columnSchema/json/object.ts +206 -0
  33. package/src/columnSchema/json/optional.ts +28 -0
  34. package/src/columnSchema/json/record.ts +40 -0
  35. package/src/columnSchema/json/scalarTypes.ts +117 -0
  36. package/src/columnSchema/json/set.ts +34 -0
  37. package/src/columnSchema/json/tuple.ts +40 -0
  38. package/src/columnSchema/json/typeBase.ts +202 -0
  39. package/src/columnSchema/json/union.ts +16 -0
  40. package/src/columnSchema/json.ts +64 -0
  41. package/src/columnSchema/number.ts +122 -0
  42. package/src/columnSchema/string.ts +222 -0
  43. package/src/columnSchema/utils.ts +27 -0
  44. package/src/common.ts +86 -0
  45. package/src/db.test.ts +67 -0
  46. package/src/db.ts +212 -0
  47. package/src/errors.ts +7 -0
  48. package/src/index.ts +18 -0
  49. package/src/operators.test.ts +608 -0
  50. package/src/operators.ts +177 -0
  51. package/src/query.ts +292 -0
  52. package/src/queryDataUtils.ts +50 -0
  53. package/src/queryMethods/aggregate.test.ts +583 -0
  54. package/src/queryMethods/aggregate.ts +878 -0
  55. package/src/queryMethods/callbacks.test.ts +69 -0
  56. package/src/queryMethods/callbacks.ts +55 -0
  57. package/src/queryMethods/clear.test.ts +64 -0
  58. package/src/queryMethods/clear.ts +58 -0
  59. package/src/queryMethods/columnInfo.test.ts +45 -0
  60. package/src/queryMethods/columnInfo.ts +67 -0
  61. package/src/queryMethods/delete.test.ts +135 -0
  62. package/src/queryMethods/delete.ts +50 -0
  63. package/src/queryMethods/for.test.ts +57 -0
  64. package/src/queryMethods/for.ts +99 -0
  65. package/src/queryMethods/from.test.ts +66 -0
  66. package/src/queryMethods/from.ts +58 -0
  67. package/src/queryMethods/get.test.ts +66 -0
  68. package/src/queryMethods/get.ts +88 -0
  69. package/src/queryMethods/having.test.ts +247 -0
  70. package/src/queryMethods/having.ts +99 -0
  71. package/src/queryMethods/insert.test.ts +555 -0
  72. package/src/queryMethods/insert.ts +453 -0
  73. package/src/queryMethods/join.test.ts +150 -0
  74. package/src/queryMethods/join.ts +508 -0
  75. package/src/queryMethods/json.test.ts +398 -0
  76. package/src/queryMethods/json.ts +259 -0
  77. package/src/queryMethods/log.test.ts +172 -0
  78. package/src/queryMethods/log.ts +123 -0
  79. package/src/queryMethods/queryMethods.test.ts +629 -0
  80. package/src/queryMethods/queryMethods.ts +428 -0
  81. package/src/queryMethods/select.test.ts +479 -0
  82. package/src/queryMethods/select.ts +249 -0
  83. package/src/queryMethods/then.ts +236 -0
  84. package/src/queryMethods/transaction.test.ts +66 -0
  85. package/src/queryMethods/transaction.ts +66 -0
  86. package/src/queryMethods/union.test.ts +59 -0
  87. package/src/queryMethods/union.ts +89 -0
  88. package/src/queryMethods/update.test.ts +417 -0
  89. package/src/queryMethods/update.ts +350 -0
  90. package/src/queryMethods/upsert.test.ts +56 -0
  91. package/src/queryMethods/upsert.ts +43 -0
  92. package/src/queryMethods/where.test.ts +1594 -0
  93. package/src/queryMethods/where.ts +450 -0
  94. package/src/queryMethods/window.test.ts +66 -0
  95. package/src/queryMethods/window.ts +108 -0
  96. package/src/queryMethods/with.test.ts +191 -0
  97. package/src/queryMethods/with.ts +92 -0
  98. package/src/quote.ts +36 -0
  99. package/src/relations.ts +194 -0
  100. package/src/sql/aggregate.ts +80 -0
  101. package/src/sql/columnInfo.ts +22 -0
  102. package/src/sql/common.ts +42 -0
  103. package/src/sql/delete.ts +41 -0
  104. package/src/sql/distinct.ts +19 -0
  105. package/src/sql/fromAndAs.ts +51 -0
  106. package/src/sql/having.ts +140 -0
  107. package/src/sql/index.ts +2 -0
  108. package/src/sql/insert.ts +102 -0
  109. package/src/sql/join.ts +242 -0
  110. package/src/sql/orderBy.ts +41 -0
  111. package/src/sql/select.ts +153 -0
  112. package/src/sql/toSql.ts +153 -0
  113. package/src/sql/truncate.ts +13 -0
  114. package/src/sql/types.ts +355 -0
  115. package/src/sql/update.ts +62 -0
  116. package/src/sql/where.ts +314 -0
  117. package/src/sql/window.ts +38 -0
  118. package/src/sql/with.ts +32 -0
  119. package/src/test-utils.ts +172 -0
  120. package/src/utils.ts +140 -0
  121. package/tsconfig.build.json +6 -0
  122. package/tsconfig.json +8 -0
@@ -0,0 +1,249 @@
1
+ import {
2
+ AddQuerySelect,
3
+ ColumnParser,
4
+ Query,
5
+ QueryBase,
6
+ QuerySelectAll,
7
+ } from '../query';
8
+ import {
9
+ ArrayOfColumnsObjects,
10
+ ColumnsObject,
11
+ NullableColumn,
12
+ } from '../columnSchema';
13
+ import { getQueryParsers, isRaw, RawExpression } from '../common';
14
+ import { pushQueryArray } from '../queryDataUtils';
15
+ import { parseRecord } from './then';
16
+ import { QueryData, SelectItem, SelectQueryData } from '../sql';
17
+ import { FilterTuple, getQueryAs, SimpleSpread } from '../utils';
18
+ import {
19
+ isRequiredRelationKey,
20
+ Relation,
21
+ RelationQueryBase,
22
+ relationQueryKey,
23
+ } from '../relations';
24
+
25
+ export type SelectArg<T extends QueryBase> =
26
+ | keyof T['selectable']
27
+ | (T['relations'] extends Record<string, Relation>
28
+ ? keyof T['relations']
29
+ : never)
30
+ | RelationQueryBase
31
+ | SelectAsArg<T>;
32
+
33
+ type SelectAsArg<T extends QueryBase> = Record<
34
+ string,
35
+ keyof T['selectable'] | Query | RawExpression
36
+ >;
37
+
38
+ type SelectResult<
39
+ T extends Query,
40
+ Args extends SelectArg<T>[],
41
+ SelectAsArgs = SimpleSpread<FilterTuple<Args, SelectAsArg<QueryBase>>>,
42
+ > = AddQuerySelect<
43
+ T,
44
+ {
45
+ [Arg in Args[number] as Arg extends keyof T['selectable']
46
+ ? T['selectable'][Arg]['as']
47
+ : Arg extends keyof T['relations']
48
+ ? Arg
49
+ : Arg extends RelationQueryBase
50
+ ? Arg['tableAlias'] extends string
51
+ ? Arg['tableAlias']
52
+ : never
53
+ : never]: Arg extends keyof T['selectable']
54
+ ? T['selectable'][Arg]['column']
55
+ : Arg extends RelationQueryBase
56
+ ? Arg['returnType'] extends 'all'
57
+ ? ArrayOfColumnsObjects<Arg['result']>
58
+ : Arg['returnType'] extends 'valueOrThrow'
59
+ ? Arg['result']['value']
60
+ : Arg[isRequiredRelationKey] extends true
61
+ ? ColumnsObject<Arg['result']>
62
+ : NullableColumn<ColumnsObject<Arg['result']>>
63
+ : T['relations'] extends Record<string, Relation>
64
+ ? Arg extends keyof T['relations']
65
+ ? T['relations'][Arg]['returns'] extends 'many'
66
+ ? ArrayOfColumnsObjects<T['relations'][Arg]['model']['result']>
67
+ : T['relations'][Arg]['options']['required'] extends true
68
+ ? ColumnsObject<T['relations'][Arg]['model']['result']>
69
+ : NullableColumn<
70
+ ColumnsObject<T['relations'][Arg]['model']['result']>
71
+ >
72
+ : never
73
+ : never;
74
+ } & {
75
+ [K in keyof SelectAsArgs]: SelectAsArgs[K] extends keyof T['selectable']
76
+ ? T['selectable'][SelectAsArgs[K]]['column']
77
+ : SelectAsArgs[K] extends RawExpression
78
+ ? SelectAsArgs[K]['__column']
79
+ : SelectAsArgs[K] extends Query
80
+ ? SelectAsArgs[K]['returnType'] extends 'all'
81
+ ? ArrayOfColumnsObjects<SelectAsArgs[K]['result']>
82
+ : ColumnsObject<SelectAsArgs[K]['result']>
83
+ : never;
84
+ }
85
+ >;
86
+
87
+ export const addParserForRawExpression = (
88
+ q: Query,
89
+ key: string,
90
+ raw: RawExpression,
91
+ ) => {
92
+ const parser = raw.__column?.parseFn;
93
+ if (parser) addParserToQuery(q.query, key, parser);
94
+ };
95
+
96
+ export const addParserForSelectItem = <T extends Query>(
97
+ q: T,
98
+ as: string | undefined,
99
+ key: string,
100
+ item: keyof T['selectable'] | Query | RawExpression,
101
+ ) => {
102
+ if (typeof item === 'object') {
103
+ if (isRaw(item)) {
104
+ addParserForRawExpression(q, key, item);
105
+ } else {
106
+ const parsers = getQueryParsers(item);
107
+ if (parsers) {
108
+ if (item.query.take) {
109
+ addParserToQuery(q.query, key, (item) => parseRecord(parsers, item));
110
+ } else {
111
+ addParserToQuery(q.query, key, (items) =>
112
+ (items as unknown[]).map((item) => parseRecord(parsers, item)),
113
+ );
114
+ }
115
+ }
116
+ }
117
+ } else {
118
+ const index = (item as string).indexOf('.');
119
+ if (index !== -1) {
120
+ const table = (item as string).slice(0, index);
121
+ const column = (item as string).slice(index + 1);
122
+
123
+ if (table === as) {
124
+ const parser = q.columnsParsers?.[column];
125
+ if (parser) addParserToQuery(q.query, key, parser);
126
+ } else {
127
+ const parser = (q.query as SelectQueryData).joinedParsers?.[table]?.[
128
+ column
129
+ ];
130
+ if (parser) addParserToQuery(q.query, key, parser);
131
+ }
132
+ } else {
133
+ const parser = q.columnsParsers?.[item as string];
134
+ if (parser) addParserToQuery(q.query, key, parser);
135
+ }
136
+ }
137
+ };
138
+
139
+ export const addParserToQuery = (
140
+ query: QueryData,
141
+ key: string,
142
+ parser: ColumnParser,
143
+ ) => {
144
+ if (query.parsers) query.parsers[key] = parser;
145
+ else query.parsers = { [key]: parser };
146
+ };
147
+
148
+ export const processSelectArg = <T extends Query>(
149
+ q: T,
150
+ as: string | undefined,
151
+ item: SelectArg<T>,
152
+ ): SelectItem => {
153
+ if (typeof item === 'string') {
154
+ if ((q.relations as Record<string, Relation>)[item]) {
155
+ item = (q as unknown as Record<string, RelationQueryBase>)[item];
156
+ } else {
157
+ const index = item.indexOf('.');
158
+ if (index !== -1) {
159
+ const table = item.slice(0, index);
160
+ const column = item.slice(index + 1);
161
+
162
+ if (table === as) {
163
+ const parser = q.columnsParsers?.[column];
164
+ if (parser) addParserToQuery(q.query, column, parser);
165
+ } else {
166
+ const parser = (q.query as SelectQueryData).joinedParsers?.[table]?.[
167
+ column
168
+ ];
169
+ if (parser) addParserToQuery(q.query, column, parser);
170
+ }
171
+ } else {
172
+ const parser = q.columnsParsers?.[item];
173
+ if (parser) addParserToQuery(q.query, item, parser);
174
+ }
175
+ return item;
176
+ }
177
+ }
178
+
179
+ if ((item as { query?: QueryData }).query?.[relationQueryKey]) {
180
+ const relation = item as RelationQueryBase;
181
+ const parsers = relation.query.parsers || relation.columnsParsers;
182
+ if (parsers) {
183
+ addParserToQuery(q.query, getQueryAs(relation), (input) => {
184
+ if (Array.isArray(input)) {
185
+ input.forEach((record: unknown) => {
186
+ for (const key in parsers) {
187
+ const value = (record as Record<string, unknown>)[key];
188
+ if (value !== null) {
189
+ (record as Record<string, unknown>)[key] = parsers[key](value);
190
+ }
191
+ }
192
+ });
193
+ } else {
194
+ for (const key in parsers) {
195
+ const value = (input as Record<string, unknown>)[key];
196
+ if (value !== null) {
197
+ (input as Record<string, unknown>)[key] = parsers[key](value);
198
+ }
199
+ }
200
+ }
201
+ return input;
202
+ });
203
+ }
204
+ } else {
205
+ for (const key in item as SelectAsArg<QueryBase>) {
206
+ addParserForSelectItem(q, as, key, (item as SelectAsArg<QueryBase>)[key]);
207
+ }
208
+
209
+ return { selectAs: item } as SelectItem;
210
+ }
211
+
212
+ return item as SelectItem;
213
+ };
214
+
215
+ export class Select {
216
+ select<T extends Query, K extends SelectArg<T>[]>(
217
+ this: T,
218
+ ...args: K
219
+ ): SelectResult<T, K> {
220
+ return this.clone()._select(...args) as unknown as SelectResult<T, K>;
221
+ }
222
+
223
+ _select<T extends Query, K extends SelectArg<T>[]>(
224
+ this: T,
225
+ ...args: K
226
+ ): SelectResult<T, K> {
227
+ if (!args.length) {
228
+ return this as unknown as SelectResult<T, K>;
229
+ }
230
+
231
+ const as = this.query.as || this.table;
232
+ const selectArgs = args.map((item) => processSelectArg(this, as, item));
233
+
234
+ return pushQueryArray(
235
+ this,
236
+ 'select',
237
+ selectArgs,
238
+ ) as unknown as SelectResult<T, K>;
239
+ }
240
+
241
+ selectAll<T extends Query>(this: T): QuerySelectAll<T> {
242
+ return this.clone()._selectAll();
243
+ }
244
+
245
+ _selectAll<T extends Query>(this: T): QuerySelectAll<T> {
246
+ this.query.select = ['*'];
247
+ return this as unknown as QuerySelectAll<T>;
248
+ }
249
+ }
@@ -0,0 +1,236 @@
1
+ import { ColumnsParsers, Query, QueryReturnType } from '../query';
2
+ import { getQueryParsers } from '../common';
3
+ import { NotFoundError } from '../errors';
4
+ import { QueryArraysResult, QueryResult } from '../adapter';
5
+ import { CommonQueryData, Sql } from '../sql';
6
+ import { AfterCallback, BeforeCallback } from './callbacks';
7
+
8
+ export type ThenResult<Res> = <T extends Query>(
9
+ this: T,
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ resolve?: (value: Res) => any,
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ reject?: (error: any) => any,
14
+ ) => Promise<Res | never>;
15
+
16
+ export const queryMethodByReturnType: Record<
17
+ QueryReturnType,
18
+ 'query' | 'arrays'
19
+ > = {
20
+ all: 'query',
21
+ one: 'query',
22
+ oneOrThrow: 'query',
23
+ rows: 'arrays',
24
+ pluck: 'arrays',
25
+ value: 'arrays',
26
+ valueOrThrow: 'arrays',
27
+ rowCount: 'arrays',
28
+ void: 'arrays',
29
+ };
30
+
31
+ export class Then {
32
+ then(
33
+ this: Query,
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ resolve?: (result: any) => any,
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ reject?: (error: any) => any,
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ ): Promise<any> {
40
+ if (this.query.wrapInTransaction && !this.query.inTransaction) {
41
+ return this.transaction((q) => then(q, resolve, reject));
42
+ } else {
43
+ return then(this, resolve, reject);
44
+ }
45
+ }
46
+ }
47
+
48
+ export const handleResult: CommonQueryData['handleResult'] = async (
49
+ q,
50
+ result: QueryResult,
51
+ ) => {
52
+ return parseResult(q, q.query.returnType, result);
53
+ };
54
+
55
+ const then = async (
56
+ q: Query,
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ resolve?: (result: any) => any,
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ reject?: (error: any) => any,
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ ): Promise<any> => {
63
+ let sql: Sql | undefined;
64
+ let logData: unknown | undefined;
65
+ try {
66
+ let beforeCallbacks: BeforeCallback<Query>[] | undefined;
67
+ let afterCallbacks: AfterCallback<Query>[] | undefined;
68
+ if (q.query.type === 'insert') {
69
+ beforeCallbacks = q.query.beforeInsert;
70
+ afterCallbacks = q.query.afterInsert;
71
+ } else if (q.query.type === 'update') {
72
+ beforeCallbacks = q.query.beforeUpdate;
73
+ afterCallbacks = q.query.afterUpdate;
74
+ }
75
+
76
+ if (beforeCallbacks) {
77
+ await Promise.all(beforeCallbacks.map((cb) => cb(q)));
78
+ }
79
+
80
+ if (q.query.beforeQuery) {
81
+ await Promise.all(q.query.beforeQuery.map((cb) => cb(q)));
82
+ }
83
+
84
+ sql = q.toSql();
85
+
86
+ if (q.query.log) {
87
+ logData = q.query.log?.beforeQuery(q, sql);
88
+ }
89
+
90
+ const queryResult = await q.query.adapter[
91
+ queryMethodByReturnType[q.query.returnType] as 'query'
92
+ ](sql);
93
+
94
+ if (q.query.log) {
95
+ q.query.log?.afterQuery(q, sql, logData);
96
+ // set sql to be undefined to prevent logging on error in case if afterCallbacks throws
97
+ sql = undefined;
98
+ }
99
+
100
+ const result = await q.query.handleResult(q, queryResult);
101
+
102
+ if (afterCallbacks?.length || q.query.afterQuery?.length) {
103
+ if (q.query.afterQuery?.length) {
104
+ await Promise.all(q.query.afterQuery.map((query) => query(q, result)));
105
+ }
106
+
107
+ if (afterCallbacks?.length) {
108
+ await Promise.all(afterCallbacks.map((query) => query(q, result)));
109
+ }
110
+ }
111
+
112
+ resolve?.(result);
113
+ } catch (error) {
114
+ if (q.query.log && sql && logData) {
115
+ q.query.log.onError(error as Error, q, sql, logData);
116
+ }
117
+ reject?.(error);
118
+ }
119
+ };
120
+
121
+ export const parseResult = (
122
+ q: Query,
123
+ returnType: QueryReturnType,
124
+ result: QueryResult,
125
+ ): unknown => {
126
+ switch (returnType) {
127
+ case 'all': {
128
+ if (q.query.throwOnNotFound && result.rows.length === 0)
129
+ throw new NotFoundError();
130
+
131
+ const parsers = getQueryParsers(q);
132
+ return parsers
133
+ ? result.rows.map((row) => parseRecord(parsers, row))
134
+ : result.rows;
135
+ }
136
+ case 'one': {
137
+ const row = result.rows[0];
138
+ if (!row) return;
139
+
140
+ const parsers = getQueryParsers(q);
141
+ return parsers ? parseRecord(parsers, row) : row;
142
+ }
143
+ case 'oneOrThrow': {
144
+ const row = result.rows[0];
145
+ if (!row) throw new NotFoundError();
146
+
147
+ const parsers = getQueryParsers(q);
148
+ return parsers ? parseRecord(parsers, row) : row;
149
+ }
150
+ case 'rows': {
151
+ const parsers = getQueryParsers(q);
152
+ return parsers
153
+ ? parseRows(
154
+ parsers,
155
+ (result as unknown as QueryArraysResult).fields,
156
+ result.rows,
157
+ )
158
+ : result.rows;
159
+ }
160
+ case 'pluck': {
161
+ const parsers = getQueryParsers(q);
162
+ if (parsers?.pluck) {
163
+ return result.rows.map((row) => parsers.pluck(row[0]));
164
+ }
165
+ return result.rows.map((row) => row[0]);
166
+ }
167
+ case 'value': {
168
+ const value = result.rows[0]?.[0];
169
+ return value !== undefined
170
+ ? parseValue(value, (result as unknown as QueryArraysResult).fields, q)
171
+ : undefined;
172
+ }
173
+ case 'valueOrThrow': {
174
+ const value = result.rows[0]?.[0];
175
+ if (value === undefined) throw new NotFoundError();
176
+
177
+ return parseValue(
178
+ value,
179
+ (result as unknown as QueryArraysResult).fields,
180
+ q,
181
+ );
182
+ }
183
+ case 'rowCount': {
184
+ if (q.query.throwOnNotFound && result.rowCount === 0) {
185
+ throw new NotFoundError();
186
+ }
187
+ return result.rowCount;
188
+ }
189
+ case 'void': {
190
+ return;
191
+ }
192
+ }
193
+ };
194
+
195
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
+ export const parseRecord = (parsers: ColumnsParsers, row: any) => {
197
+ for (const key in parsers) {
198
+ if (row[key] !== null && row[key] !== undefined) {
199
+ row[key] = parsers[key](row[key]);
200
+ }
201
+ }
202
+ return row;
203
+ };
204
+
205
+ const parseRows = (
206
+ parsers: ColumnsParsers,
207
+ fields: { name: string }[],
208
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
209
+ rows: any[],
210
+ ) => {
211
+ fields.forEach((field, i) => {
212
+ const parser = parsers[field.name];
213
+ if (parser) {
214
+ rows.forEach((row) => {
215
+ row[i] = parser(row[i]);
216
+ });
217
+ }
218
+ });
219
+ return rows;
220
+ };
221
+
222
+ const parseValue = (
223
+ value: unknown,
224
+ fields: { name: string }[],
225
+ query: Query,
226
+ ) => {
227
+ const field = fields[0];
228
+ if (value !== null) {
229
+ const parsers = getQueryParsers(query);
230
+ const parser = parsers?.[field.name];
231
+ if (parser) {
232
+ return parser(value);
233
+ }
234
+ }
235
+ return value;
236
+ };
@@ -0,0 +1,66 @@
1
+ import { db, User } from '../test-utils';
2
+ import { Client } from 'pg';
3
+
4
+ describe('transaction', () => {
5
+ beforeEach(() => jest.clearAllMocks());
6
+
7
+ it('should start and commit transaction', async () => {
8
+ const spy = jest.spyOn(Client.prototype, 'query');
9
+
10
+ const result = await db.transaction(async (db) => {
11
+ expect(db.query.inTransaction).toBe(true);
12
+
13
+ const {
14
+ rows: [{ a }],
15
+ } = await db.adapter.query('SELECT 1 AS a');
16
+ const {
17
+ rows: [{ b }],
18
+ } = await db.adapter.query('SELECT 2 AS b');
19
+ return a + b;
20
+ });
21
+
22
+ expect(result).toBe(3);
23
+
24
+ expect(
25
+ spy.mock.calls.map(
26
+ (call) => (call[0] as unknown as { text: string }).text,
27
+ ),
28
+ ).toEqual(['BEGIN', 'SELECT 1 AS a', 'SELECT 2 AS b', 'COMMIT']);
29
+ });
30
+
31
+ it('should rollback if error happens', async () => {
32
+ const spy = jest.spyOn(Client.prototype, 'query');
33
+
34
+ let error: Error | undefined;
35
+
36
+ await db
37
+ .transaction(async () => {
38
+ throw new Error('error');
39
+ })
40
+ .catch((err) => (error = err));
41
+
42
+ expect(error?.message).toBe('error');
43
+
44
+ expect(
45
+ spy.mock.calls.map(
46
+ (call) => (call[0] as unknown as { text: string }).text,
47
+ ),
48
+ ).toEqual(['BEGIN', 'ROLLBACK']);
49
+ });
50
+
51
+ describe('transacting', () => {
52
+ it('should use provided adapter to perform queries', async () => {
53
+ const spy = jest.spyOn(Client.prototype, 'query');
54
+
55
+ await db.transaction(async (trx) => {
56
+ return User.transacting(trx).all();
57
+ });
58
+
59
+ expect(
60
+ spy.mock.calls.map(
61
+ (call) => (call[0] as unknown as { text: string }).text,
62
+ ),
63
+ ).toEqual(['BEGIN', 'SELECT * FROM "user"', 'COMMIT']);
64
+ });
65
+ });
66
+ });
@@ -0,0 +1,66 @@
1
+ import { Query } from '../query';
2
+
3
+ const beginSql = {
4
+ text: 'BEGIN',
5
+ values: [],
6
+ };
7
+
8
+ const commitSql = {
9
+ text: 'COMMIT',
10
+ values: [],
11
+ };
12
+
13
+ const rollbackSql = {
14
+ text: 'ROLLBACK',
15
+ values: [],
16
+ };
17
+
18
+ export class Transaction {
19
+ async transaction<T extends Query, Result>(
20
+ this: T,
21
+ cb: (query: T) => Promise<Result>,
22
+ ): Promise<Result> {
23
+ const log = this.query.log;
24
+ let logData: unknown | undefined;
25
+ if (log) {
26
+ logData = log.beforeQuery(this, beginSql);
27
+ }
28
+ const t = this.query.adapter.transaction((adapter) => {
29
+ if (log) {
30
+ log.afterQuery(this, beginSql, logData);
31
+ }
32
+
33
+ const q = this.clone();
34
+ q.query.adapter = adapter;
35
+ q.query.inTransaction = true;
36
+
37
+ if (log) {
38
+ logData = log.beforeQuery(this, commitSql);
39
+ }
40
+ return cb(q);
41
+ });
42
+
43
+ if (log) {
44
+ t.then(
45
+ () => {
46
+ log.afterQuery(this, commitSql, logData);
47
+ },
48
+ () => {
49
+ log.afterQuery(this, rollbackSql, logData);
50
+ },
51
+ );
52
+ }
53
+
54
+ return t;
55
+ }
56
+
57
+ transacting<T extends Query>(this: T, query: Query): T {
58
+ return this.clone()._transacting(query);
59
+ }
60
+
61
+ _transacting<T extends Query>(this: T, query: Query): T {
62
+ this.query.adapter = query.query.adapter;
63
+ this.query.inTransaction = true;
64
+ return this;
65
+ }
66
+ }
@@ -0,0 +1,59 @@
1
+ import { Chat, expectQueryNotMutated, expectSql, User } from '../test-utils';
2
+ import { raw } from '../common';
3
+
4
+ ['union', 'intersect', 'except'].forEach((what) => {
5
+ const upper = what.toUpperCase();
6
+ describe(what, () => {
7
+ it(`adds ${what}`, () => {
8
+ const q = User.all();
9
+ let query = q.select('id');
10
+ query = query[what as 'union']([Chat.select('id'), raw('SELECT 1')]);
11
+ query = query[
12
+ (what + 'All') as 'unionAll' | 'intersectAll' | 'exceptAll'
13
+ ]([raw('SELECT 2')], true);
14
+
15
+ const wrapped = query.wrap(User.select('id'));
16
+
17
+ expectSql(
18
+ wrapped.toSql(),
19
+ `
20
+ SELECT "t"."id" FROM (
21
+ SELECT "user"."id" FROM "user"
22
+ ${upper}
23
+ SELECT "chat"."id" FROM "chat"
24
+ ${upper}
25
+ SELECT 1
26
+ ${upper} ALL
27
+ (SELECT 2)
28
+ ) AS "t"
29
+ `,
30
+ );
31
+
32
+ expectQueryNotMutated(q);
33
+ });
34
+
35
+ it('has modifier', () => {
36
+ const q = User.select('id');
37
+ q[`_${what}` as '_union']([raw('SELECT 1')]);
38
+ expectSql(
39
+ q.toSql(),
40
+ `
41
+ SELECT "user"."id" FROM "user"
42
+ ${upper}
43
+ SELECT 1
44
+ `,
45
+ );
46
+ q[`_${what}All` as '_unionAll']([raw('SELECT 2')], true);
47
+ expectSql(
48
+ q.toSql(),
49
+ `
50
+ SELECT "user"."id" FROM "user"
51
+ ${upper}
52
+ SELECT 1
53
+ ${upper} ALL
54
+ (SELECT 2)
55
+ `,
56
+ );
57
+ });
58
+ });
59
+ });