Skip to content

Commit 80461a0

Browse files
committed
Merge branch 'master' into feat/add-embeded-functions-type-inference
2 parents 87557ef + c0d25d3 commit 80461a0

24 files changed

+3203
-3318
lines changed

jest.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@ module.exports = {
22
preset: 'ts-jest',
33
testEnvironment: 'node',
44
collectCoverageFrom: ['src/**/*', '!src/types.ts'],
5+
// Run tests sequentially to prevent database side effects
6+
maxWorkers: 1,
7+
// Ensure deterministic test order
8+
testSequencer: '<rootDir>/test/testSequencer.js',
59
}

src/select-query-parser/result.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from './types'
1313
import {
1414
CheckDuplicateEmbededReference,
15+
GetComputedFields,
1516
GetFieldNodeResultName,
1617
IsAny,
1718
IsRelationNullable,
@@ -234,7 +235,12 @@ export type ProcessNode<
234235
> =
235236
// TODO: figure out why comparing the `type` property is necessary vs. `NodeType extends Ast.StarNode`
236237
NodeType['type'] extends Ast.StarNode['type'] // If the selection is *
237-
? Row
238+
? // If the row has computed field, postgrest will omit them from star selection per default
239+
GetComputedFields<Schema, RelationName> extends never
240+
? // If no computed fields are detected on the row, we can return it as is
241+
Row
242+
: // otherwise we omit all the computed field from the star result return
243+
Omit<Row, GetComputedFields<Schema, RelationName>>
238244
: NodeType['type'] extends Ast.SpreadNode['type'] // If the selection is a ...spread
239245
? ProcessSpreadNode<
240246
ClientOptions,

src/select-query-parser/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,3 +663,25 @@ type FindMatchingFunctionBySetofFrom<
663663
> = FnUnion extends infer Fn extends GenericFunction
664664
? MatchingFunctionBySetofFrom<Fn, TableName>
665665
: false
666+
667+
type ComputedField<
668+
Schema extends GenericSchema,
669+
RelationName extends keyof TablesAndViews<Schema>,
670+
FieldName extends keyof TablesAndViews<Schema>[RelationName]['Row']
671+
> = FieldName extends keyof Schema['Functions']
672+
? Schema['Functions'][FieldName] extends {
673+
Args: { '': TablesAndViews<Schema>[RelationName]['Row'] }
674+
Returns: any
675+
}
676+
? FieldName
677+
: never
678+
: never
679+
680+
// Given a relation name (Table or View) extract all the "computed fields" based on the Row
681+
// object, and the schema functions definitions
682+
export type GetComputedFields<
683+
Schema extends GenericSchema,
684+
RelationName extends keyof TablesAndViews<Schema>
685+
> = {
686+
[K in keyof TablesAndViews<Schema>[RelationName]['Row']]: ComputedField<Schema, RelationName, K>
687+
}[keyof TablesAndViews<Schema>[RelationName]['Row']]
File renamed without changes.

test/db/00-schema.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ $$ language sql immutable;
162162
create function public.function_with_array_param(param uuid[])
163163
returns void as '' language sql immutable;
164164

165-
166165
create table public.cornercase (
167166
id int primary key,
168167
"column whitespace" text,
@@ -311,6 +310,7 @@ create or replace function public.polymorphic_function_with_unnamed_default_over
311310
create or replace function public.polymorphic_function_with_unnamed_default_overload(text default 'default') returns text language sql as $$ SELECT 'foo' $$;
312311
create or replace function public.polymorphic_function_with_unnamed_default_overload(bool default true) returns int language sql as 'SELECT 3';
313312

313+
-- Function creating a computed field
314314
create function public.blurb_message(public.messages) returns character varying as
315315
$$
316316
select substring($1.message, 1, 3);
File renamed without changes.

test/index.test.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

test/max-affected.ts renamed to test/max-affected.test.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,38 @@ import { Database } from './types.override'
33
import { Database as DatabasePostgrest13 } from './types.override-with-options-postgrest13'
44
import { expectType } from 'tsd'
55
import { InvalidMethodError } from '../src/PostgrestFilterBuilder'
6+
import { Json } from './types.generated'
67

78
const REST_URL_13 = 'http://localhost:3001'
89
const postgrest13 = new PostgrestClient<DatabasePostgrest13>(REST_URL_13)
910
const postgrest12 = new PostgrestClient<Database>(REST_URL_13)
1011

1112
describe('maxAffected', () => {
12-
// Type checking tests
13-
test('maxAffected should show type warning on postgrest 12 clients', async () => {
13+
test('types: maxAffected should show type warning on postgrest 12 clients', async () => {
1414
const resUpdate = await postgrest12
1515
.from('messages')
1616
.update({ channel_id: 2 })
1717
.eq('message', 'foo')
1818
.maxAffected(1)
1919
expectType<InvalidMethodError<'maxAffected method only available on postgrest 13+'>>(resUpdate)
2020
})
21-
test('maxAffected should show type warning on non update / delete', async () => {
22-
const resSelect = await postgrest13.from('messages').select('*').maxAffected(10)
23-
const resInsert = await postgrest13
21+
test('types: maxAffected should show type warning on non update / delete', async () => {
22+
const resSelect = postgrest13.from('messages').select('*').maxAffected(10)
23+
const resInsert = postgrest13
2424
.from('messages')
2525
.insert({ message: 'foo', username: 'supabot', channel_id: 1 })
2626
.maxAffected(10)
27-
const resUpsert = await postgrest13
27+
const resUpsert = postgrest13
2828
.from('messages')
2929
.upsert({ id: 3, message: 'foo', username: 'supabot', channel_id: 2 })
3030
.maxAffected(10)
31-
const resUpdate = await postgrest13
31+
const resUpdate = postgrest13
3232
.from('messages')
3333
.update({ channel_id: 2 })
3434
.eq('message', 'foo')
3535
.maxAffected(1)
3636
.select()
37-
const resDelete = await postgrest13
37+
const resDelete = postgrest13
3838
.from('messages')
3939
.delete()
4040
.eq('message', 'foo')
@@ -59,7 +59,6 @@ describe('maxAffected', () => {
5959
)
6060
})
6161

62-
// Runtime behavior tests
6362
test('update should fail when maxAffected is exceeded', async () => {
6463
// First create multiple rows
6564
await postgrest13.from('messages').insert([
@@ -74,9 +73,13 @@ describe('maxAffected', () => {
7473
.update({ message: 'updated' })
7574
.eq('message', 'test1')
7675
.maxAffected(2)
76+
7777
const { error } = result
7878
expect(error).toBeDefined()
7979
expect(error?.code).toBe('PGRST124')
80+
81+
// cleanup
82+
await postgrest13.from('messages').delete().eq('message', 'test1')
8083
})
8184

8285
test('update should succeed when within maxAffected limit', async () => {
@@ -92,10 +95,22 @@ describe('maxAffected', () => {
9295
.eq('message', 'test2')
9396
.maxAffected(2)
9497
.select()
95-
98+
expectType<
99+
| {
100+
channel_id: number
101+
data: Json | null
102+
id: number
103+
message: string | null
104+
username: string
105+
}[]
106+
| null
107+
>(data)
96108
expect(error).toBeNull()
97109
expect(data).toHaveLength(1)
98110
expect(data?.[0].message).toBe('updated')
111+
112+
// cleanup
113+
await postgrest13.from('messages').delete().eq('message', 'updated')
99114
})
100115

101116
test('delete should fail when maxAffected is exceeded', async () => {
@@ -113,9 +128,11 @@ describe('maxAffected', () => {
113128
.eq('message', 'test3')
114129
.maxAffected(2)
115130
.select()
116-
117131
expect(error).toBeDefined()
118132
expect(error?.code).toBe('PGRST124')
133+
134+
// cleanup
135+
await postgrest13.from('messages').delete().eq('message', 'test3')
119136
})
120137

121138
test('delete should succeed when within maxAffected limit', async () => {
@@ -157,6 +174,9 @@ describe('maxAffected', () => {
157174
catchphrase: null,
158175
},
159176
])
177+
178+
// cleanup
179+
await postgrest13.from('users').delete().eq('username', 'testuser')
160180
})
161181

162182
test('should fail when rpc returns more results than maxAffected', async () => {
@@ -176,5 +196,8 @@ describe('maxAffected', () => {
176196
expect(error).toBeDefined()
177197
expect(error?.code).toBe('PGRST124')
178198
expect(data).toBeNull()
199+
200+
// cleanup
201+
await postgrest13.from('users').delete().in('username', ['testuser1', 'testuser2', 'testuser3'])
179202
})
180203
})

0 commit comments

Comments
 (0)