Skip to content

Commit

Permalink
feat: add support for type casting (id::text) (#429)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ozanmakes authored Feb 7, 2024
1 parent a79d78c commit 4525dc4
Showing 1 changed file with 81 additions and 3 deletions.
84 changes: 81 additions & 3 deletions src/select-query-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,68 @@ type Letter = Alphabet | Digit | '_'

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

type SingleValuePostgreSQLTypes =
| 'bool'
| 'int2'
| 'int4'
| 'int8'
| 'float4'
| 'float8'
| 'numeric'
| 'bytea'
| 'bpchar'
| 'varchar'
| 'date'
| 'text'
| 'citext'
| 'time'
| 'timetz'
| 'timestamp'
| 'timestamptz'
| 'uuid'
| 'vector'
| 'json'
| 'jsonb'
| 'void'
| 'record'
| string

type ArrayPostgreSQLTypes = `_${SingleValuePostgreSQLTypes}`

type PostgreSQLTypes = SingleValuePostgreSQLTypes | ArrayPostgreSQLTypes

type TypeScriptSingleValueTypes<T extends SingleValuePostgreSQLTypes> = T extends 'bool'
? boolean
: T extends 'int2' | 'int4' | 'int8' | 'float4' | 'float8' | 'numeric'
? number
: T extends
| 'bytea'
| 'bpchar'
| 'varchar'
| 'date'
| 'text'
| 'citext'
| 'time'
| 'timetz'
| 'timestamp'
| 'timestamptz'
| 'uuid'
| 'vector'
? string
: T extends 'json' | 'jsonb'
? Json
: T extends 'void'
? undefined
: T extends 'record'
? Record<string, unknown>
: unknown

type StripUnderscore<T extends string> = T extends `_${infer U}` ? U : T

type TypeScriptTypes<T extends PostgreSQLTypes> = T extends ArrayPostgreSQLTypes
? TypeScriptSingleValueTypes<StripUnderscore<Extract<T, SingleValuePostgreSQLTypes>>>[]
: TypeScriptSingleValueTypes<T>

/**
* Parser errors.
*/
Expand Down Expand Up @@ -243,27 +305,36 @@ type ParseIdentifier<Input extends string> = ReadLetters<Input> extends [
* A node is one of the following:
* - `*`
* - `field`
* - `field::type`
* - `field->json...`
* - `field(nodes)`
* - `field!hint(nodes)`
* - `field!inner(nodes)`
* - `field!hint!inner(nodes)`
* - `renamed_field:field`
* - `renamed_field:field::type`
* - `renamed_field:field->json...`
* - `renamed_field:field(nodes)`
* - `renamed_field:field!hint(nodes)`
* - `renamed_field:field!inner(nodes)`
* - `renamed_field:field!hint!inner(nodes)`
*
* TODO: casting operators `::text`, more support for JSON operators `->`, `->>`.
* TODO: more support for JSON operators `->`, `->>`.
*/
type ParseNode<Input extends string> = Input extends ''
? ParserError<'Empty string'>
: // `*`
Input extends `*${infer Remainder}`
? [{ star: true }, EatWhitespace<Remainder>]
: ParseIdentifier<Input> extends [infer Name, `${infer Remainder}`]
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
? EatWhitespace<Remainder> extends `::${infer Remainder}`
? ParseIdentifier<Remainder> extends [infer CastType, `${infer Remainder}`]
? // `field::type`
CastType extends PostgreSQLTypes
? [{ name: Name; type: TypeScriptTypes<CastType> }, EatWhitespace<Remainder>]
: never
: ParserError<`Unexpected type cast at \`${Input}\``>
: EatWhitespace<Remainder> extends `!inner${infer Remainder}`
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [infer Fields, `${infer Remainder}`]
? // `field!inner(nodes)`
[{ name: Name; original: Name; children: Fields }, EatWhitespace<Remainder>]
Expand Down Expand Up @@ -294,7 +365,14 @@ type ParseNode<Input extends string> = Input extends ''
: ParserError<'Expected identifier after `!`'>
: EatWhitespace<Remainder> extends `:${infer Remainder}`
? ParseIdentifier<EatWhitespace<Remainder>> extends [infer OriginalName, `${infer Remainder}`]
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
? EatWhitespace<Remainder> extends `::${infer Remainder}`
? ParseIdentifier<Remainder> extends [infer CastType, `${infer Remainder}`]
? // `renamed_field:field::type`
CastType extends PostgreSQLTypes
? [{ name: Name; type: TypeScriptTypes<CastType> }, EatWhitespace<Remainder>]
: never
: ParserError<`Unexpected type cast at \`${Input}\``>
: EatWhitespace<Remainder> extends `!inner${infer Remainder}`
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
infer Fields,
`${infer Remainder}`
Expand Down

0 comments on commit 4525dc4

Please sign in to comment.