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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pqb",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Postgres query builder",
5
5
  "homepage": "https://porm.netlify.app/guide/query-builder.html",
6
6
  "repository": {
package/src/db.ts CHANGED
@@ -82,6 +82,7 @@ export interface Db<
82
82
  length: number,
83
83
  name: QueryErrorName,
84
84
  ) => QueryError<this>;
85
+ isSubQuery: false;
85
86
  [defaultsKey]: Record<
86
87
  {
87
88
  [K in keyof Shape]: Shape[K]['hasDefault'] extends true ? K : never;
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
- // eslint-disable-next-line @typescript-eslint/ban-types
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,5 +1,7 @@
1
1
  import { Query, SetQueryReturnsRowCount } from '../query';
2
2
 
3
+ export type DeleteMethodsNames = 'del' | '_del' | 'delete' | '_delete';
4
+
3
5
  type DeleteArgs<T extends Query> = T['hasWhere'] extends true
4
6
  ? [forceAll?: boolean]
5
7
  : [true];
@@ -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 { columnTypes, DateColumn } from '../columnSchema';
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 { WhereArg, UpdateData, CreateMethodsNames } from './queryMethods';
3
- import { MaybeArray } from './utils';
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]: string;
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]: string;
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
- Q extends RelationQueryBase = ChainedCreate extends true
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
- ctx.sql.push(`DELETE FROM ${quotedAs}`);
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, q(model.table));
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 { RelationQuery, relationQueryKey } from '../relations';
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]?: string;
59
+ [relationQueryKey]?: RelationQueryData;
56
60
  inTransaction?: boolean;
57
61
  wrapInTransaction?: boolean;
58
62
  throwOnNotFound?: boolean;