pqb 0.4.4 → 0.4.6
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 +18 -7
- package/dist/index.esm.js +15 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/db.ts +1 -0
- package/src/query.ts +2 -2
- package/src/queryMethods/delete.ts +2 -0
- package/src/queryMethods/select.test.ts +1 -205
- package/src/queryMethods/select.ts +2 -0
- package/src/relations.ts +24 -6
- package/src/sql/delete.ts +7 -1
- package/src/sql/select.ts +7 -0
- package/src/sql/toSql.ts +1 -1
- package/src/sql/types.ts +6 -2
package/package.json
CHANGED
package/src/db.ts
CHANGED
package/src/query.ts
CHANGED
|
@@ -70,8 +70,8 @@ export type Query = QueryMethods & {
|
|
|
70
70
|
length: number,
|
|
71
71
|
name: QueryErrorName,
|
|
72
72
|
) => QueryError;
|
|
73
|
-
|
|
74
|
-
[defaultsKey]:
|
|
73
|
+
isSubQuery: boolean;
|
|
74
|
+
[defaultsKey]: EmptyObject;
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
export type Selectable<T extends QueryBase> = StringKey<keyof T['selectable']>;
|
|
@@ -1,25 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertType,
|
|
3
|
-
Chat,
|
|
4
|
-
chatData,
|
|
5
3
|
expectQueryNotMutated,
|
|
6
4
|
expectSql,
|
|
7
|
-
Message,
|
|
8
|
-
messageData,
|
|
9
|
-
MessageRecord,
|
|
10
|
-
now,
|
|
11
5
|
Profile,
|
|
12
6
|
profileData,
|
|
13
|
-
ProfileRecord,
|
|
14
7
|
User,
|
|
15
8
|
userData,
|
|
16
9
|
UserRecord,
|
|
17
10
|
useTestDatabase,
|
|
18
11
|
} from '../test-utils';
|
|
19
12
|
import { raw } from '../common';
|
|
20
|
-
import {
|
|
21
|
-
import { addQueryOn } from './join';
|
|
22
|
-
import { RelationQuery, relationQueryKey } from '../relations';
|
|
13
|
+
import { DateColumn } from '../columnSchema';
|
|
23
14
|
|
|
24
15
|
const insertUserAndProfile = async () => {
|
|
25
16
|
const id = await User.get('id').create(userData);
|
|
@@ -115,201 +106,6 @@ describe('selectMethods', () => {
|
|
|
115
106
|
expectQueryNotMutated(q);
|
|
116
107
|
});
|
|
117
108
|
|
|
118
|
-
describe('select relation', () => {
|
|
119
|
-
const profileQuery = Profile.takeOptional();
|
|
120
|
-
const profileRelationQuery = addQueryOn(
|
|
121
|
-
profileQuery,
|
|
122
|
-
User,
|
|
123
|
-
profileQuery,
|
|
124
|
-
'userId',
|
|
125
|
-
'id',
|
|
126
|
-
);
|
|
127
|
-
profileRelationQuery.query[relationQueryKey] = 'profile';
|
|
128
|
-
|
|
129
|
-
const profileRelation = new Proxy(() => undefined, {
|
|
130
|
-
get(_, key) {
|
|
131
|
-
return (
|
|
132
|
-
profileRelationQuery as unknown as Record<string | symbol, unknown>
|
|
133
|
-
)[key];
|
|
134
|
-
},
|
|
135
|
-
}) as unknown as RelationQuery<
|
|
136
|
-
'profile',
|
|
137
|
-
Record<string, unknown>,
|
|
138
|
-
never,
|
|
139
|
-
typeof profileQuery
|
|
140
|
-
>;
|
|
141
|
-
|
|
142
|
-
it('should select relation which returns one record', () => {
|
|
143
|
-
const q = User.all();
|
|
144
|
-
|
|
145
|
-
const query = q.select('id', {
|
|
146
|
-
profile: () => profileRelation.where({ bio: 'bio' }),
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
assertType<
|
|
150
|
-
Awaited<typeof query>,
|
|
151
|
-
{ id: number; profile: typeof Profile['type'] | null }[]
|
|
152
|
-
>();
|
|
153
|
-
|
|
154
|
-
expectSql(
|
|
155
|
-
query.toSql(),
|
|
156
|
-
`
|
|
157
|
-
SELECT
|
|
158
|
-
"user"."id",
|
|
159
|
-
(
|
|
160
|
-
SELECT row_to_json("t".*)
|
|
161
|
-
FROM (
|
|
162
|
-
SELECT *
|
|
163
|
-
FROM "profile"
|
|
164
|
-
WHERE "profile"."userId" = "user"."id"
|
|
165
|
-
AND "profile"."bio" = $1
|
|
166
|
-
LIMIT $2
|
|
167
|
-
) AS "t"
|
|
168
|
-
) AS "profile"
|
|
169
|
-
FROM "user"
|
|
170
|
-
`,
|
|
171
|
-
['bio', 1],
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
expectQueryNotMutated(q);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('should have proper type for required relation', () => {
|
|
178
|
-
const q = User.all();
|
|
179
|
-
|
|
180
|
-
const query = q.select('id', {
|
|
181
|
-
profile: () =>
|
|
182
|
-
profileRelation as unknown as RelationQuery<
|
|
183
|
-
'profile',
|
|
184
|
-
Record<string, unknown>,
|
|
185
|
-
never,
|
|
186
|
-
typeof profileRelationQuery,
|
|
187
|
-
true
|
|
188
|
-
>,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
assertType<
|
|
192
|
-
Awaited<typeof query>,
|
|
193
|
-
{ id: number; profile: typeof Profile['type'] }[]
|
|
194
|
-
>();
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('should parse columns in single relation record result', async () => {
|
|
198
|
-
const userId = await User.get('id').create(userData);
|
|
199
|
-
const now = new Date();
|
|
200
|
-
await Profile.create({ userId, updatedAt: now, createdAt: now });
|
|
201
|
-
|
|
202
|
-
const [record] = await User.select('id', {
|
|
203
|
-
profile: () => profileRelation,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
assertType<
|
|
207
|
-
typeof record,
|
|
208
|
-
{ id: number; profile: ProfileRecord | null }
|
|
209
|
-
>();
|
|
210
|
-
|
|
211
|
-
expect(record.profile).toMatchObject({
|
|
212
|
-
updatedAt: now,
|
|
213
|
-
createdAt: now,
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const messagesQuery = Message.as('messages');
|
|
218
|
-
const messageRelationQuery = addQueryOn(
|
|
219
|
-
messagesQuery,
|
|
220
|
-
User,
|
|
221
|
-
messagesQuery,
|
|
222
|
-
'authorId',
|
|
223
|
-
'id',
|
|
224
|
-
);
|
|
225
|
-
messageRelationQuery.query[relationQueryKey] = 'messages';
|
|
226
|
-
|
|
227
|
-
const messageRelation = new Proxy(() => undefined, {
|
|
228
|
-
get(_, key) {
|
|
229
|
-
return (
|
|
230
|
-
messageRelationQuery as unknown as Record<string | symbol, unknown>
|
|
231
|
-
)[key];
|
|
232
|
-
},
|
|
233
|
-
}) as unknown as RelationQuery<
|
|
234
|
-
'messages',
|
|
235
|
-
Record<string, unknown>,
|
|
236
|
-
never,
|
|
237
|
-
typeof messageRelationQuery
|
|
238
|
-
>;
|
|
239
|
-
|
|
240
|
-
it('should select relation which returns many records', () => {
|
|
241
|
-
const q = User.all();
|
|
242
|
-
|
|
243
|
-
const query = q.select('id', {
|
|
244
|
-
messages: () => messageRelation.where({ text: 'text' }),
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
assertType<
|
|
248
|
-
Awaited<typeof query>,
|
|
249
|
-
{ id: number; messages: typeof Message['type'][] }[]
|
|
250
|
-
>();
|
|
251
|
-
|
|
252
|
-
expectSql(
|
|
253
|
-
query.toSql(),
|
|
254
|
-
`
|
|
255
|
-
SELECT
|
|
256
|
-
"user"."id",
|
|
257
|
-
(
|
|
258
|
-
SELECT COALESCE(json_agg(row_to_json("t".*)), '[]')
|
|
259
|
-
FROM (
|
|
260
|
-
SELECT *
|
|
261
|
-
FROM "message" AS "messages"
|
|
262
|
-
WHERE "messages"."authorId" = "user"."id"
|
|
263
|
-
AND "messages"."text" = $1
|
|
264
|
-
) AS "t"
|
|
265
|
-
) AS "messages"
|
|
266
|
-
FROM "user"
|
|
267
|
-
`,
|
|
268
|
-
['text'],
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
expectQueryNotMutated(q);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('should parse columns in multiple relation records result', async () => {
|
|
275
|
-
const { id: authorId } = await User.select('id').create(userData);
|
|
276
|
-
const { id: chatId } = await Chat.select('id').create(chatData);
|
|
277
|
-
await Message.create({
|
|
278
|
-
authorId,
|
|
279
|
-
chatId,
|
|
280
|
-
...messageData,
|
|
281
|
-
createdAt: now,
|
|
282
|
-
updatedAt: now,
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
const [record] = await User.select('id', {
|
|
286
|
-
messages: () => messageRelation,
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
assertType<typeof record, { id: number; messages: MessageRecord[] }>();
|
|
290
|
-
|
|
291
|
-
expect(record.messages[0]).toMatchObject({
|
|
292
|
-
updatedAt: now,
|
|
293
|
-
createdAt: now,
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('should have proper type for conditional sub queries', async () => {
|
|
298
|
-
const condition = true;
|
|
299
|
-
|
|
300
|
-
const query = User.select('id', {
|
|
301
|
-
hasProfile: condition
|
|
302
|
-
? () => profileRelation.exists()
|
|
303
|
-
: raw(columnTypes.boolean(), 'true'),
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
assertType<
|
|
307
|
-
Awaited<typeof query>,
|
|
308
|
-
{ id: number; hasProfile: boolean }[]
|
|
309
|
-
>();
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
|
|
313
109
|
describe('parse columns', () => {
|
|
314
110
|
beforeEach(insertUserAndProfile);
|
|
315
111
|
|
|
@@ -106,7 +106,9 @@ export const addParserForSelectItem = <T extends Query>(
|
|
|
106
106
|
addParserForRawExpression(q, key, arg);
|
|
107
107
|
return arg;
|
|
108
108
|
} else if (typeof arg === 'function') {
|
|
109
|
+
q.isSubQuery = true;
|
|
109
110
|
const rel = arg(q);
|
|
111
|
+
q.isSubQuery = false;
|
|
110
112
|
const parsers = getQueryParsers(rel);
|
|
111
113
|
if (parsers) {
|
|
112
114
|
if (queryTypeWithLimitOne[rel.query.returnType]) {
|
package/src/relations.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { defaultsKey, Query, QueryBase, QueryWithTable } from './query';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
WhereArg,
|
|
4
|
+
UpdateData,
|
|
5
|
+
CreateMethodsNames,
|
|
6
|
+
DeleteMethodsNames,
|
|
7
|
+
} from './queryMethods';
|
|
8
|
+
import { EmptyObject, MaybeArray } from './utils';
|
|
4
9
|
|
|
5
10
|
export type NestedInsertOneItem = {
|
|
6
11
|
create?: Record<string, unknown>;
|
|
@@ -179,8 +184,15 @@ export const relationQueryKey = Symbol('relationQuery');
|
|
|
179
184
|
export type isRequiredRelationKey = typeof isRequiredRelationKey;
|
|
180
185
|
export const isRequiredRelationKey = Symbol('isRequiredRelation');
|
|
181
186
|
|
|
187
|
+
export type RelationQueryData = {
|
|
188
|
+
relationName: string;
|
|
189
|
+
sourceQuery: Query;
|
|
190
|
+
relationQuery: Query;
|
|
191
|
+
joinQuery(fromQuery: Query, toQuery: Query): Query;
|
|
192
|
+
};
|
|
193
|
+
|
|
182
194
|
export type RelationQueryBase = Query & {
|
|
183
|
-
[relationQueryKey]:
|
|
195
|
+
[relationQueryKey]: RelationQueryData;
|
|
184
196
|
[isRequiredRelationKey]: boolean;
|
|
185
197
|
};
|
|
186
198
|
|
|
@@ -192,7 +204,7 @@ type PrepareRelationQuery<
|
|
|
192
204
|
> = Omit<T, 'tableAlias'> & {
|
|
193
205
|
tableAlias: RelationName extends string ? RelationName : never;
|
|
194
206
|
[isRequiredRelationKey]: Required;
|
|
195
|
-
[relationQueryKey]:
|
|
207
|
+
[relationQueryKey]: RelationQueryData;
|
|
196
208
|
} & { [defaultsKey]: Record<Populate, true> };
|
|
197
209
|
|
|
198
210
|
export type RelationQuery<
|
|
@@ -202,9 +214,15 @@ export type RelationQuery<
|
|
|
202
214
|
T extends Query = Query,
|
|
203
215
|
Required extends boolean = boolean,
|
|
204
216
|
ChainedCreate extends boolean = false,
|
|
205
|
-
|
|
217
|
+
ChainedDelete extends boolean = false,
|
|
218
|
+
Q extends RelationQueryBase = (ChainedCreate extends true
|
|
206
219
|
? PrepareRelationQuery<T, Name, Required, Populate>
|
|
207
220
|
: PrepareRelationQuery<T, Name, Required, Populate> & {
|
|
208
221
|
[K in CreateMethodsNames]: never;
|
|
209
|
-
}
|
|
222
|
+
}) &
|
|
223
|
+
(ChainedDelete extends true
|
|
224
|
+
? EmptyObject
|
|
225
|
+
: {
|
|
226
|
+
[K in DeleteMethodsNames]: never;
|
|
227
|
+
}),
|
|
210
228
|
> = ((params: Params) => Q) & Q;
|
package/src/sql/delete.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { pushWhereStatementSql } from './where';
|
|
|
4
4
|
import { pushReturningSql } from './insert';
|
|
5
5
|
import { processJoinItem } from './join';
|
|
6
6
|
import { ToSqlCtx } from './toSql';
|
|
7
|
+
import { q } from './common';
|
|
7
8
|
|
|
8
9
|
export const pushDeleteSql = (
|
|
9
10
|
ctx: ToSqlCtx,
|
|
@@ -11,7 +12,12 @@ export const pushDeleteSql = (
|
|
|
11
12
|
query: DeleteQueryData,
|
|
12
13
|
quotedAs: string,
|
|
13
14
|
) => {
|
|
14
|
-
|
|
15
|
+
const from = q(model.table as string);
|
|
16
|
+
ctx.sql.push(`DELETE FROM ${from}`);
|
|
17
|
+
|
|
18
|
+
if (from !== quotedAs) {
|
|
19
|
+
ctx.sql.push(`AS ${quotedAs}`);
|
|
20
|
+
}
|
|
15
21
|
|
|
16
22
|
let conditions: string | undefined;
|
|
17
23
|
if (query.join?.length) {
|
package/src/sql/select.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { PormInternalError, UnhandledTypeError } from '../errors';
|
|
|
12
12
|
import { StringColumn } from '../columnSchema';
|
|
13
13
|
import { quote } from '../quote';
|
|
14
14
|
import { ToSqlCtx } from './toSql';
|
|
15
|
+
import { relationQueryKey } from '../relations';
|
|
15
16
|
|
|
16
17
|
const jsonColumnOrMethodToSql = (
|
|
17
18
|
column: string | JsonItem,
|
|
@@ -143,6 +144,12 @@ const pushSubQuerySql = (
|
|
|
143
144
|
list: string[],
|
|
144
145
|
) => {
|
|
145
146
|
const { returnType = 'all' } = query.query;
|
|
147
|
+
|
|
148
|
+
const rel = query.query[relationQueryKey];
|
|
149
|
+
if (rel) {
|
|
150
|
+
query = rel.joinQuery(rel.sourceQuery, query);
|
|
151
|
+
}
|
|
152
|
+
|
|
146
153
|
switch (returnType) {
|
|
147
154
|
case 'all':
|
|
148
155
|
case 'one':
|
package/src/sql/toSql.ts
CHANGED
|
@@ -84,7 +84,7 @@ const makeSql = (model: Query, { values = [] }: ToSqlOptions = {}): Sql => {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
if (query.type === 'delete') {
|
|
87
|
-
pushDeleteSql(ctx, model, query,
|
|
87
|
+
pushDeleteSql(ctx, model, query, quotedAs);
|
|
88
88
|
return { text: sql.join(' '), values };
|
|
89
89
|
}
|
|
90
90
|
}
|
package/src/sql/types.ts
CHANGED
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
} from '../query';
|
|
9
9
|
import { Expression, RawExpression } from '../common';
|
|
10
10
|
import { ColumnsShape, ColumnType } from '../columnSchema';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
RelationQuery,
|
|
13
|
+
RelationQueryData,
|
|
14
|
+
relationQueryKey,
|
|
15
|
+
} from '../relations';
|
|
12
16
|
import { Adapter, QueryResult } from '../adapter';
|
|
13
17
|
import { MaybeArray } from '../utils';
|
|
14
18
|
import { QueryLogger, QueryLogObject } from '../queryMethods/log';
|
|
@@ -52,7 +56,7 @@ export type CommonQueryData = {
|
|
|
52
56
|
adapter: Adapter;
|
|
53
57
|
handleResult(q: Query, result: QueryResult): Promise<unknown>;
|
|
54
58
|
returnType: QueryReturnType;
|
|
55
|
-
[relationQueryKey]?:
|
|
59
|
+
[relationQueryKey]?: RelationQueryData;
|
|
56
60
|
inTransaction?: boolean;
|
|
57
61
|
wrapInTransaction?: boolean;
|
|
58
62
|
throwOnNotFound?: boolean;
|