Skip to content

Commit 4525dc4

Browse files
authored
feat: add support for type casting (id::text) (#429)
This change adds support for type casting in select queries. For example an id column with the type bigint can be casted to text by "id::text". Equivalent PostgreSQL type for TypeScript type is based on the type generator pgTypeToTsType function https://github.com/supabase/postgres-meta//blob/80809c71d78d875524d7d9865c2458136207aa10/src/server/templates/typescript.ts#L421
1 parent a79d78c commit 4525dc4

File tree

1 file changed

+81
-3
lines changed

1 file changed

+81
-3
lines changed

src/select-query-parser.ts

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,68 @@ type Letter = Alphabet | Digit | '_'
4040

4141
type Json = string | number | boolean | null | { [key: string]: Json } | Json[]
4242

43+
type SingleValuePostgreSQLTypes =
44+
| 'bool'
45+
| 'int2'
46+
| 'int4'
47+
| 'int8'
48+
| 'float4'
49+
| 'float8'
50+
| 'numeric'
51+
| 'bytea'
52+
| 'bpchar'
53+
| 'varchar'
54+
| 'date'
55+
| 'text'
56+
| 'citext'
57+
| 'time'
58+
| 'timetz'
59+
| 'timestamp'
60+
| 'timestamptz'
61+
| 'uuid'
62+
| 'vector'
63+
| 'json'
64+
| 'jsonb'
65+
| 'void'
66+
| 'record'
67+
| string
68+
69+
type ArrayPostgreSQLTypes = `_${SingleValuePostgreSQLTypes}`
70+
71+
type PostgreSQLTypes = SingleValuePostgreSQLTypes | ArrayPostgreSQLTypes
72+
73+
type TypeScriptSingleValueTypes<T extends SingleValuePostgreSQLTypes> = T extends 'bool'
74+
? boolean
75+
: T extends 'int2' | 'int4' | 'int8' | 'float4' | 'float8' | 'numeric'
76+
? number
77+
: T extends
78+
| 'bytea'
79+
| 'bpchar'
80+
| 'varchar'
81+
| 'date'
82+
| 'text'
83+
| 'citext'
84+
| 'time'
85+
| 'timetz'
86+
| 'timestamp'
87+
| 'timestamptz'
88+
| 'uuid'
89+
| 'vector'
90+
? string
91+
: T extends 'json' | 'jsonb'
92+
? Json
93+
: T extends 'void'
94+
? undefined
95+
: T extends 'record'
96+
? Record<string, unknown>
97+
: unknown
98+
99+
type StripUnderscore<T extends string> = T extends `_${infer U}` ? U : T
100+
101+
type TypeScriptTypes<T extends PostgreSQLTypes> = T extends ArrayPostgreSQLTypes
102+
? TypeScriptSingleValueTypes<StripUnderscore<Extract<T, SingleValuePostgreSQLTypes>>>[]
103+
: TypeScriptSingleValueTypes<T>
104+
43105
/**
44106
* Parser errors.
45107
*/
@@ -243,27 +305,36 @@ type ParseIdentifier<Input extends string> = ReadLetters<Input> extends [
243305
* A node is one of the following:
244306
* - `*`
245307
* - `field`
308+
* - `field::type`
246309
* - `field->json...`
247310
* - `field(nodes)`
248311
* - `field!hint(nodes)`
249312
* - `field!inner(nodes)`
250313
* - `field!hint!inner(nodes)`
251314
* - `renamed_field:field`
315+
* - `renamed_field:field::type`
252316
* - `renamed_field:field->json...`
253317
* - `renamed_field:field(nodes)`
254318
* - `renamed_field:field!hint(nodes)`
255319
* - `renamed_field:field!inner(nodes)`
256320
* - `renamed_field:field!hint!inner(nodes)`
257321
*
258-
* TODO: casting operators `::text`, more support for JSON operators `->`, `->>`.
322+
* TODO: more support for JSON operators `->`, `->>`.
259323
*/
260324
type ParseNode<Input extends string> = Input extends ''
261325
? ParserError<'Empty string'>
262326
: // `*`
263327
Input extends `*${infer Remainder}`
264328
? [{ star: true }, EatWhitespace<Remainder>]
265329
: ParseIdentifier<Input> extends [infer Name, `${infer Remainder}`]
266-
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
330+
? EatWhitespace<Remainder> extends `::${infer Remainder}`
331+
? ParseIdentifier<Remainder> extends [infer CastType, `${infer Remainder}`]
332+
? // `field::type`
333+
CastType extends PostgreSQLTypes
334+
? [{ name: Name; type: TypeScriptTypes<CastType> }, EatWhitespace<Remainder>]
335+
: never
336+
: ParserError<`Unexpected type cast at \`${Input}\``>
337+
: EatWhitespace<Remainder> extends `!inner${infer Remainder}`
267338
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [infer Fields, `${infer Remainder}`]
268339
? // `field!inner(nodes)`
269340
[{ name: Name; original: Name; children: Fields }, EatWhitespace<Remainder>]
@@ -294,7 +365,14 @@ type ParseNode<Input extends string> = Input extends ''
294365
: ParserError<'Expected identifier after `!`'>
295366
: EatWhitespace<Remainder> extends `:${infer Remainder}`
296367
? ParseIdentifier<EatWhitespace<Remainder>> extends [infer OriginalName, `${infer Remainder}`]
297-
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
368+
? EatWhitespace<Remainder> extends `::${infer Remainder}`
369+
? ParseIdentifier<Remainder> extends [infer CastType, `${infer Remainder}`]
370+
? // `renamed_field:field::type`
371+
CastType extends PostgreSQLTypes
372+
? [{ name: Name; type: TypeScriptTypes<CastType> }, EatWhitespace<Remainder>]
373+
: never
374+
: ParserError<`Unexpected type cast at \`${Input}\``>
375+
: EatWhitespace<Remainder> extends `!inner${infer Remainder}`
298376
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
299377
infer Fields,
300378
`${infer Remainder}`

0 commit comments

Comments
 (0)