Skip to content

Commit 4016952

Browse files
authored
Merge branch 'master' into query-parser-cleanup
2 parents 9b38343 + 197b4d2 commit 4016952

11 files changed

+459
-190
lines changed

package-lock.json

Lines changed: 283 additions & 148 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/PostgrestClient.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,19 @@ export default class PostgrestClient<
5959
from<
6060
TableName extends string & keyof Schema['Tables'],
6161
Table extends Schema['Tables'][TableName]
62-
>(relation: TableName): PostgrestQueryBuilder<Schema, Table>
62+
>(relation: TableName): PostgrestQueryBuilder<Schema, Table, TableName>
6363
from<ViewName extends string & keyof Schema['Views'], View extends Schema['Views'][ViewName]>(
6464
relation: ViewName
65-
): PostgrestQueryBuilder<Schema, View>
66-
from(relation: string): PostgrestQueryBuilder<Schema, any>
65+
): PostgrestQueryBuilder<Schema, View, ViewName>
66+
from(relation: string): PostgrestQueryBuilder<Schema, any, any>
6767
/**
6868
* Perform a query on a table or a view.
6969
*
7070
* @param relation - The table or view name to query
7171
*/
72-
from(relation: string): PostgrestQueryBuilder<Schema, any> {
72+
from(relation: string): PostgrestQueryBuilder<Schema, any, any> {
7373
const url = new URL(`${this.url}/${relation}`)
74-
return new PostgrestQueryBuilder<Schema, any>(url, {
74+
return new PostgrestQueryBuilder(url, {
7575
headers: { ...this.headers },
7676
schema: this.schemaName,
7777
fetch: this.fetch,
@@ -92,11 +92,7 @@ export default class PostgrestClient<
9292
DynamicSchema,
9393
Database[DynamicSchema] extends GenericSchema ? Database[DynamicSchema] : any
9494
> {
95-
return new PostgrestClient<
96-
Database,
97-
DynamicSchema,
98-
Database[DynamicSchema] extends GenericSchema ? Database[DynamicSchema] : any
99-
>(this.url, {
95+
return new PostgrestClient(this.url, {
10096
headers: this.headers,
10197
schema,
10298
fetch: this.fetch,

src/PostgrestFilterBuilder.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ export default class PostgrestFilterBuilder<
2929
Schema extends GenericSchema,
3030
Row extends Record<string, unknown>,
3131
Result,
32+
RelationName = unknown,
3233
Relationships = unknown
33-
> extends PostgrestTransformBuilder<Schema, Row, Result, Relationships> {
34+
> extends PostgrestTransformBuilder<Schema, Row, Result, RelationName, Relationships> {
3435
eq<ColumnName extends string & keyof Row>(
3536
column: ColumnName,
3637
value: NonNullable<Row[ColumnName]>

src/PostgrestQueryBuilder.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Fetch, GenericSchema, GenericTable, GenericView } from './types'
66
export default class PostgrestQueryBuilder<
77
Schema extends GenericSchema,
88
Relation extends GenericTable | GenericView,
9+
RelationName = unknown,
910
Relationships = Relation extends { Relationships: infer R } ? R : unknown
1011
> {
1112
url: URL
@@ -55,7 +56,7 @@ export default class PostgrestQueryBuilder<
5556
*/
5657
select<
5758
Query extends string = '*',
58-
ResultOne = GetResult<Schema, Relation['Row'], Relationships, Query>
59+
ResultOne = GetResult<Schema, Relation['Row'], RelationName, Relationships, Query>
5960
>(
6061
columns?: Query,
6162
{
@@ -65,7 +66,7 @@ export default class PostgrestQueryBuilder<
6566
head?: boolean
6667
count?: 'exact' | 'planned' | 'estimated'
6768
} = {}
68-
): PostgrestFilterBuilder<Schema, Relation['Row'], ResultOne[], Relationships> {
69+
): PostgrestFilterBuilder<Schema, Relation['Row'], ResultOne[], RelationName, Relationships> {
6970
const method = head ? 'HEAD' : 'GET'
7071
// Remove whitespaces except when quoted
7172
let quoted = false
@@ -102,14 +103,14 @@ export default class PostgrestQueryBuilder<
102103
options?: {
103104
count?: 'exact' | 'planned' | 'estimated'
104105
}
105-
): PostgrestFilterBuilder<Schema, Relation['Row'], null, Relationships>
106+
): PostgrestFilterBuilder<Schema, Relation['Row'], null, RelationName, Relationships>
106107
insert<Row extends Relation extends { Insert: unknown } ? Relation['Insert'] : never>(
107108
values: Row[],
108109
options?: {
109110
count?: 'exact' | 'planned' | 'estimated'
110111
defaultToNull?: boolean
111112
}
112-
): PostgrestFilterBuilder<Schema, Relation['Row'], null, Relationships>
113+
): PostgrestFilterBuilder<Schema, Relation['Row'], null, RelationName, Relationships>
113114
/**
114115
* Perform an INSERT into the table or view.
115116
*
@@ -145,7 +146,7 @@ export default class PostgrestQueryBuilder<
145146
count?: 'exact' | 'planned' | 'estimated'
146147
defaultToNull?: boolean
147148
} = {}
148-
): PostgrestFilterBuilder<Schema, Relation['Row'], null, Relationships> {
149+
): PostgrestFilterBuilder<Schema, Relation['Row'], null, RelationName, Relationships> {
149150
const method = 'POST'
150151

151152
const prefersHeaders = []
@@ -187,7 +188,7 @@ export default class PostgrestQueryBuilder<
187188
ignoreDuplicates?: boolean
188189
count?: 'exact' | 'planned' | 'estimated'
189190
}
190-
): PostgrestFilterBuilder<Schema, Relation['Row'], null, Relationships>
191+
): PostgrestFilterBuilder<Schema, Relation['Row'], null, RelationName, Relationships>
191192
upsert<Row extends Relation extends { Insert: unknown } ? Relation['Insert'] : never>(
192193
values: Row[],
193194
options?: {
@@ -196,7 +197,7 @@ export default class PostgrestQueryBuilder<
196197
count?: 'exact' | 'planned' | 'estimated'
197198
defaultToNull?: boolean
198199
}
199-
): PostgrestFilterBuilder<Schema, Relation['Row'], null, Relationships>
200+
): PostgrestFilterBuilder<Schema, Relation['Row'], null, RelationName, Relationships>
200201
/**
201202
* Perform an UPSERT on the table or view. Depending on the column(s) passed
202203
* to `onConflict`, `.upsert()` allows you to perform the equivalent of
@@ -248,7 +249,7 @@ export default class PostgrestQueryBuilder<
248249
count?: 'exact' | 'planned' | 'estimated'
249250
defaultToNull?: boolean
250251
} = {}
251-
): PostgrestFilterBuilder<Schema, Relation['Row'], null, Relationships> {
252+
): PostgrestFilterBuilder<Schema, Relation['Row'], null, RelationName, Relationships> {
252253
const method = 'POST'
253254

254255
const prefersHeaders = [`resolution=${ignoreDuplicates ? 'ignore' : 'merge'}-duplicates`]
@@ -312,7 +313,7 @@ export default class PostgrestQueryBuilder<
312313
}: {
313314
count?: 'exact' | 'planned' | 'estimated'
314315
} = {}
315-
): PostgrestFilterBuilder<Schema, Relation['Row'], null, Relationships> {
316+
): PostgrestFilterBuilder<Schema, Relation['Row'], null, RelationName, Relationships> {
316317
const method = 'PATCH'
317318
const prefersHeaders = []
318319
if (this.headers['Prefer']) {
@@ -357,7 +358,7 @@ export default class PostgrestQueryBuilder<
357358
count,
358359
}: {
359360
count?: 'exact' | 'planned' | 'estimated'
360-
} = {}): PostgrestFilterBuilder<Schema, Relation['Row'], null, Relationships> {
361+
} = {}): PostgrestFilterBuilder<Schema, Relation['Row'], null, RelationName, Relationships> {
361362
const method = 'DELETE'
362363
const prefersHeaders = []
363364
if (count) {

src/PostgrestTransformBuilder.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default class PostgrestTransformBuilder<
66
Schema extends GenericSchema,
77
Row extends Record<string, unknown>,
88
Result,
9+
RelationName = unknown,
910
Relationships = unknown
1011
> extends PostgrestBuilder<Result> {
1112
/**
@@ -17,9 +18,12 @@ export default class PostgrestTransformBuilder<
1718
*
1819
* @param columns - The columns to retrieve, separated by commas
1920
*/
20-
select<Query extends string = '*', NewResultOne = GetResult<Schema, Row, Relationships, Query>>(
21+
select<
22+
Query extends string = '*',
23+
NewResultOne = GetResult<Schema, Row, RelationName, Relationships, Query>
24+
>(
2125
columns?: Query
22-
): PostgrestTransformBuilder<Schema, Row, NewResultOne[], Relationships> {
26+
): PostgrestTransformBuilder<Schema, Row, NewResultOne[], RelationName, Relationships> {
2327
// Remove whitespaces except when quoted
2428
let quoted = false
2529
const cleanedColumns = (columns ?? '*')
@@ -39,7 +43,13 @@ export default class PostgrestTransformBuilder<
3943
this.headers['Prefer'] += ','
4044
}
4145
this.headers['Prefer'] += 'return=representation'
42-
return this as unknown as PostgrestTransformBuilder<Schema, Row, NewResultOne[], Relationships>
46+
return this as unknown as PostgrestTransformBuilder<
47+
Schema,
48+
Row,
49+
NewResultOne[],
50+
RelationName,
51+
Relationships
52+
>
4353
}
4454

4555
order<ColumnName extends string & keyof Row>(
@@ -224,6 +234,10 @@ export default class PostgrestTransformBuilder<
224234
/**
225235
* Return `data` as the EXPLAIN plan for the query.
226236
*
237+
* You need to enable the
238+
* [db_plan_enabled](https://supabase.com/docs/guides/database/debugging-performance#enabling-explain)
239+
* setting before using this method.
240+
*
227241
* @param options - Named parameters
228242
*
229243
* @param options.analyze - If `true`, the query will be executed and the
@@ -267,7 +281,7 @@ export default class PostgrestTransformBuilder<
267281
.filter(Boolean)
268282
.join('|')
269283
// An Accept header can carry multiple media types but postgrest-js always sends one
270-
const forMediatype = this.headers['Accept']
284+
const forMediatype = this.headers['Accept'] ?? 'application/json'
271285
this.headers[
272286
'Accept'
273287
] = `application/vnd.pgrst.plan+${format}; for="${forMediatype}"; options=${options};`
@@ -294,7 +308,19 @@ export default class PostgrestTransformBuilder<
294308
*
295309
* @typeParam NewResult - The new result type to override with
296310
*/
297-
returns<NewResult>(): PostgrestTransformBuilder<Schema, Row, NewResult, Relationships> {
298-
return this as unknown as PostgrestTransformBuilder<Schema, Row, NewResult, Relationships>
311+
returns<NewResult>(): PostgrestTransformBuilder<
312+
Schema,
313+
Row,
314+
NewResult,
315+
RelationName,
316+
Relationships
317+
> {
318+
return this as unknown as PostgrestTransformBuilder<
319+
Schema,
320+
Row,
321+
NewResult,
322+
RelationName,
323+
Relationships
324+
>
299325
}
300326
}

src/select-query-parser.ts

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ type HasFKey<FKeyName, Relationships> = Relationships extends [infer R]
7676
: HasFKey<FKeyName, Rest>
7777
: false
7878

79+
type HasUniqueFKey<FKeyName, Relationships> = Relationships extends [infer R]
80+
? R extends { foreignKeyName: FKeyName; isOneToOne: true }
81+
? true
82+
: false
83+
: Relationships extends [infer R, ...infer Rest]
84+
? HasUniqueFKey<FKeyName, [R]> extends true
85+
? true
86+
: HasUniqueFKey<FKeyName, Rest>
87+
: false
88+
7989
/**
8090
* Returns a boolean representing whether there is a foreign key referencing
8191
* a given relation.
@@ -90,6 +100,16 @@ type HasFKeyToFRel<FRelName, Relationships> = Relationships extends [infer R]
90100
: HasFKeyToFRel<FRelName, Rest>
91101
: false
92102

103+
type HasUniqueFKeyToFRel<FRelName, Relationships> = Relationships extends [infer R]
104+
? R extends { referencedRelation: FRelName; isOneToOne: true }
105+
? true
106+
: false
107+
: Relationships extends [infer R, ...infer Rest]
108+
? HasUniqueFKeyToFRel<FRelName, [R]> extends true
109+
? true
110+
: HasUniqueFKeyToFRel<FRelName, Rest>
111+
: false
112+
93113
/**
94114
* Constructs a type definition for a single field of an object.
95115
*
@@ -101,6 +121,7 @@ type HasFKeyToFRel<FRelName, Relationships> = Relationships extends [infer R]
101121
type ConstructFieldDefinition<
102122
Schema extends GenericSchema,
103123
Row extends Record<string, unknown>,
124+
RelationName,
104125
Relationships,
105126
Field
106127
> = Field extends { star: true }
@@ -110,13 +131,24 @@ type ConstructFieldDefinition<
110131
[_ in Field['name']]: GetResultHelper<
111132
Schema,
112133
(Schema['Tables'] & Schema['Views'])[Field['original']]['Row'],
134+
Field['original'],
113135
(Schema['Tables'] & Schema['Views'])[Field['original']] extends { Relationships: infer R }
114136
? R
115137
: unknown,
116138
Field['children'],
117139
unknown
118140
> extends infer Child
119-
? Relationships extends unknown[]
141+
? // One-to-one relationship - referencing column(s) has unique/pkey constraint.
142+
HasUniqueFKey<
143+
Field['hint'],
144+
(Schema['Tables'] & Schema['Views'])[Field['original']] extends {
145+
Relationships: infer R
146+
}
147+
? R
148+
: unknown
149+
> extends true
150+
? Child | null
151+
: Relationships extends unknown[]
120152
? HasFKey<Field['hint'], Relationships> extends true
121153
? Child | null
122154
: Child[]
@@ -128,13 +160,24 @@ type ConstructFieldDefinition<
128160
[_ in Field['name']]: GetResultHelper<
129161
Schema,
130162
(Schema['Tables'] & Schema['Views'])[Field['original']]['Row'],
163+
Field['original'],
131164
(Schema['Tables'] & Schema['Views'])[Field['original']] extends { Relationships: infer R }
132165
? R
133166
: unknown,
134167
Field['children'],
135168
unknown
136169
> extends infer Child
137-
? Relationships extends unknown[]
170+
? // One-to-one relationship - referencing column(s) has unique/pkey constraint.
171+
HasUniqueFKeyToFRel<
172+
RelationName,
173+
(Schema['Tables'] & Schema['Views'])[Field['original']] extends {
174+
Relationships: infer R
175+
}
176+
? R
177+
: unknown
178+
> extends true
179+
? Child | null
180+
: Relationships extends unknown[]
138181
? HasFKeyToFRel<Field['original'], Relationships> extends true
139182
? Child | null
140183
: Child[]
@@ -372,28 +415,35 @@ type ParseQuery<Query extends string> = string extends Query
372415
type GetResultHelper<
373416
Schema extends GenericSchema,
374417
Row extends Record<string, unknown>,
418+
RelationName,
375419
Relationships,
376420
Fields extends unknown[],
377421
Acc
378422
> = Fields extends [infer R]
379-
? ConstructFieldDefinition<Schema, Row, Relationships, R> extends SelectQueryError<infer E>
423+
? ConstructFieldDefinition<Schema, Row, RelationName, Relationships, R> extends SelectQueryError<
424+
infer E
425+
>
380426
? SelectQueryError<E>
381427
: GetResultHelper<
382428
Schema,
383429
Row,
430+
RelationName,
384431
Relationships,
385432
[],
386-
ConstructFieldDefinition<Schema, Row, Relationships, R> & Acc
433+
ConstructFieldDefinition<Schema, Row, RelationName, Relationships, R> & Acc
387434
>
388435
: Fields extends [infer R, ...infer Rest]
389-
? ConstructFieldDefinition<Schema, Row, Relationships, R> extends SelectQueryError<infer E>
436+
? ConstructFieldDefinition<Schema, Row, RelationName, Relationships, R> extends SelectQueryError<
437+
infer E
438+
>
390439
? SelectQueryError<E>
391440
: GetResultHelper<
392441
Schema,
393442
Row,
443+
RelationName,
394444
Relationships,
395445
Rest,
396-
ConstructFieldDefinition<Schema, Row, Relationships, R> & Acc
446+
ConstructFieldDefinition<Schema, Row, RelationName, Relationships, R> & Acc
397447
>
398448
: Prettify<Acc>
399449

@@ -408,8 +458,9 @@ type GetResultHelper<
408458
export type GetResult<
409459
Schema extends GenericSchema,
410460
Row extends Record<string, unknown>,
461+
RelationName,
411462
Relationships,
412463
Query extends string
413464
> = ParseQuery<Query> extends unknown[]
414-
? GetResultHelper<Schema, Row, Relationships, ParseQuery<Query>, unknown>
465+
? GetResultHelper<Schema, Row, RelationName, Relationships, ParseQuery<Query>, unknown>
415466
: ParseQuery<Query>

test/db/00-schema.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ CREATE TABLE public.channels (
2525
ALTER TABLE public.users REPLICA IDENTITY FULL; -- Send "previous data" to supabase
2626
COMMENT ON COLUMN public.channels.data IS 'For unstructured data and prototyping.';
2727

28+
create table public.channel_details (
29+
id bigint primary key references channels(id),
30+
details text default null
31+
);
32+
2833
-- MESSAGES
2934
CREATE TABLE public.messages (
3035
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,

test/db/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
version: '3'
44
services:
55
rest:
6-
image: postgrest/postgrest:v11.0.0
6+
image: postgrest/postgrest:v11.2.2
77
ports:
88
- '3000:3000'
99
environment:

0 commit comments

Comments
 (0)