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.
- package/dist/index.d.ts +3630 -0
- package/dist/index.esm.js +4587 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +4691 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/rollup.config.js +35 -0
- package/src/adapter.test.ts +10 -0
- package/src/adapter.ts +171 -0
- package/src/columnSchema/array.ts +21 -0
- package/src/columnSchema/boolean.ts +10 -0
- package/src/columnSchema/columnType.test.ts +129 -0
- package/src/columnSchema/columnType.ts +77 -0
- package/src/columnSchema/columnTypes.ts +145 -0
- package/src/columnSchema/columnsSchema.test.ts +32 -0
- package/src/columnSchema/columnsSchema.ts +100 -0
- package/src/columnSchema/commonMethods.ts +130 -0
- package/src/columnSchema/dateTime.ts +104 -0
- package/src/columnSchema/enum.ts +13 -0
- package/src/columnSchema/index.ts +11 -0
- package/src/columnSchema/json/array.ts +55 -0
- package/src/columnSchema/json/discriminatedUnion.ts +91 -0
- package/src/columnSchema/json/enum.ts +29 -0
- package/src/columnSchema/json/instanceOf.ts +16 -0
- package/src/columnSchema/json/intersection.ts +23 -0
- package/src/columnSchema/json/lazy.ts +22 -0
- package/src/columnSchema/json/literal.ts +12 -0
- package/src/columnSchema/json/map.ts +29 -0
- package/src/columnSchema/json/nativeEnum.ts +30 -0
- package/src/columnSchema/json/nullable.ts +33 -0
- package/src/columnSchema/json/nullish.ts +30 -0
- package/src/columnSchema/json/object.ts +206 -0
- package/src/columnSchema/json/optional.ts +28 -0
- package/src/columnSchema/json/record.ts +40 -0
- package/src/columnSchema/json/scalarTypes.ts +117 -0
- package/src/columnSchema/json/set.ts +34 -0
- package/src/columnSchema/json/tuple.ts +40 -0
- package/src/columnSchema/json/typeBase.ts +202 -0
- package/src/columnSchema/json/union.ts +16 -0
- package/src/columnSchema/json.ts +64 -0
- package/src/columnSchema/number.ts +122 -0
- package/src/columnSchema/string.ts +222 -0
- package/src/columnSchema/utils.ts +27 -0
- package/src/common.ts +86 -0
- package/src/db.test.ts +67 -0
- package/src/db.ts +212 -0
- package/src/errors.ts +7 -0
- package/src/index.ts +18 -0
- package/src/operators.test.ts +608 -0
- package/src/operators.ts +177 -0
- package/src/query.ts +292 -0
- package/src/queryDataUtils.ts +50 -0
- package/src/queryMethods/aggregate.test.ts +583 -0
- package/src/queryMethods/aggregate.ts +878 -0
- package/src/queryMethods/callbacks.test.ts +69 -0
- package/src/queryMethods/callbacks.ts +55 -0
- package/src/queryMethods/clear.test.ts +64 -0
- package/src/queryMethods/clear.ts +58 -0
- package/src/queryMethods/columnInfo.test.ts +45 -0
- package/src/queryMethods/columnInfo.ts +67 -0
- package/src/queryMethods/delete.test.ts +135 -0
- package/src/queryMethods/delete.ts +50 -0
- package/src/queryMethods/for.test.ts +57 -0
- package/src/queryMethods/for.ts +99 -0
- package/src/queryMethods/from.test.ts +66 -0
- package/src/queryMethods/from.ts +58 -0
- package/src/queryMethods/get.test.ts +66 -0
- package/src/queryMethods/get.ts +88 -0
- package/src/queryMethods/having.test.ts +247 -0
- package/src/queryMethods/having.ts +99 -0
- package/src/queryMethods/insert.test.ts +555 -0
- package/src/queryMethods/insert.ts +453 -0
- package/src/queryMethods/join.test.ts +150 -0
- package/src/queryMethods/join.ts +508 -0
- package/src/queryMethods/json.test.ts +398 -0
- package/src/queryMethods/json.ts +259 -0
- package/src/queryMethods/log.test.ts +172 -0
- package/src/queryMethods/log.ts +123 -0
- package/src/queryMethods/queryMethods.test.ts +629 -0
- package/src/queryMethods/queryMethods.ts +428 -0
- package/src/queryMethods/select.test.ts +479 -0
- package/src/queryMethods/select.ts +249 -0
- package/src/queryMethods/then.ts +236 -0
- package/src/queryMethods/transaction.test.ts +66 -0
- package/src/queryMethods/transaction.ts +66 -0
- package/src/queryMethods/union.test.ts +59 -0
- package/src/queryMethods/union.ts +89 -0
- package/src/queryMethods/update.test.ts +417 -0
- package/src/queryMethods/update.ts +350 -0
- package/src/queryMethods/upsert.test.ts +56 -0
- package/src/queryMethods/upsert.ts +43 -0
- package/src/queryMethods/where.test.ts +1594 -0
- package/src/queryMethods/where.ts +450 -0
- package/src/queryMethods/window.test.ts +66 -0
- package/src/queryMethods/window.ts +108 -0
- package/src/queryMethods/with.test.ts +191 -0
- package/src/queryMethods/with.ts +92 -0
- package/src/quote.ts +36 -0
- package/src/relations.ts +194 -0
- package/src/sql/aggregate.ts +80 -0
- package/src/sql/columnInfo.ts +22 -0
- package/src/sql/common.ts +42 -0
- package/src/sql/delete.ts +41 -0
- package/src/sql/distinct.ts +19 -0
- package/src/sql/fromAndAs.ts +51 -0
- package/src/sql/having.ts +140 -0
- package/src/sql/index.ts +2 -0
- package/src/sql/insert.ts +102 -0
- package/src/sql/join.ts +242 -0
- package/src/sql/orderBy.ts +41 -0
- package/src/sql/select.ts +153 -0
- package/src/sql/toSql.ts +153 -0
- package/src/sql/truncate.ts +13 -0
- package/src/sql/types.ts +355 -0
- package/src/sql/update.ts +62 -0
- package/src/sql/where.ts +314 -0
- package/src/sql/window.ts +38 -0
- package/src/sql/with.ts +32 -0
- package/src/test-utils.ts +172 -0
- package/src/utils.ts +140 -0
- package/tsconfig.build.json +6 -0
- 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
|
+
});
|