Skip to content

Commit c9cebf8

Browse files
authored
refactor: query parser cleanup (#496)
* Improve comments * Add parser error helper * Add `ParseField` helper This removes the duplication of parsing required for renamed fields and non-renamed fields.
1 parent 4525dc4 commit c9cebf8

File tree

1 file changed

+81
-112
lines changed

1 file changed

+81
-112
lines changed

src/select-query-parser.ts

Lines changed: 81 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ type ParserError<Message extends string> = { error: true } & Message
109109
type GenericStringError = ParserError<'Received a generic string'>
110110
export type SelectQueryError<Message extends string> = { error: true } & Message
111111

112+
/**
113+
* Creates a new {@link ParserError} if the given input is not already a parser error.
114+
*/
115+
type CreateParserErrorIfRequired<Input, Message extends string> = Input extends ParserError<string>
116+
? Input
117+
: ParserError<Message>
118+
112119
/**
113120
* Trims whitespace from the left of the input.
114121
*/
@@ -118,6 +125,9 @@ type EatWhitespace<Input extends string> = string extends Input
118125
? EatWhitespace<Remainder>
119126
: Input
120127

128+
/**
129+
* Returns a boolean representing whether there is a foreign key with the given name.
130+
*/
121131
type HasFKey<FKeyName, Relationships> = Relationships extends [infer R]
122132
? R extends { foreignKeyName: FKeyName }
123133
? true
@@ -128,6 +138,9 @@ type HasFKey<FKeyName, Relationships> = Relationships extends [infer R]
128138
: HasFKey<FKeyName, Rest>
129139
: false
130140

141+
/**
142+
* Returns a boolean representing whether there the foreign key has a unique constraint.
143+
*/
131144
type HasUniqueFKey<FKeyName, Relationships> = Relationships extends [infer R]
132145
? R extends { foreignKeyName: FKeyName; isOneToOne: true }
133146
? true
@@ -138,6 +151,10 @@ type HasUniqueFKey<FKeyName, Relationships> = Relationships extends [infer R]
138151
: HasUniqueFKey<FKeyName, Rest>
139152
: false
140153

154+
/**
155+
* Returns a boolean representing whether there is a foreign key referencing
156+
* a given relation.
157+
*/
141158
type HasFKeyToFRel<FRelName, Relationships> = Relationships extends [infer R]
142159
? R extends { referencedRelation: FRelName }
143160
? true
@@ -161,8 +178,9 @@ type HasUniqueFKeyToFRel<FRelName, Relationships> = Relationships extends [infer
161178
/**
162179
* Constructs a type definition for a single field of an object.
163180
*
164-
* @param Definitions Record of definitions, possibly generated from PostgREST's OpenAPI spec.
165-
* @param Name Name of the table being queried.
181+
* @param Schema Database schema.
182+
* @param Row Type of a row in the given table.
183+
* @param Relationships Relationships between different tables in the database.
166184
* @param Field Single field parsed by `ParseQuery`.
167185
*/
168186
type ConstructFieldDefinition<
@@ -231,12 +249,12 @@ type ConstructFieldDefinition<
231249
: Child[]
232250
: never
233251
}
252+
: Field extends { name: string; type: infer T }
253+
? { [K in Field['name']]: T }
234254
: Field extends { name: string; original: string }
235255
? Field['original'] extends keyof Row
236256
? { [K in Field['name']]: Row[Field['original']] }
237257
: SelectQueryError<`Referencing missing column \`${Field['original']}\``>
238-
: Field extends { name: string; type: infer T }
239-
? { [K in Field['name']]: T }
240258
: Record<string, unknown>
241259

242260
/**
@@ -246,8 +264,7 @@ type ConstructFieldDefinition<
246264
*/
247265

248266
/**
249-
* Reads a consecutive sequence of more than 1 letter,
250-
* where letters are `[0-9a-zA-Z_]`.
267+
* Reads a consecutive sequence of 1 or more letter, where letters are `[0-9a-zA-Z_]`.
251268
*/
252269
type ReadLetters<Input extends string> = string extends Input
253270
? GenericStringError
@@ -266,7 +283,7 @@ type ReadLettersHelper<Input extends string, Acc extends string> = string extend
266283
: [Acc, '']
267284

268285
/**
269-
* Reads a consecutive sequence of more than 1 double-quoted letters,
286+
* Reads a consecutive sequence of 1 or more double-quoted letters,
270287
* where letters are `[^"]`.
271288
*/
272289
type ReadQuotedLetters<Input extends string> = string extends Input
@@ -289,7 +306,7 @@ type ReadQuotedLettersHelper<Input extends string, Acc extends string> = string
289306

290307
/**
291308
* Parses a (possibly double-quoted) identifier.
292-
* For now, identifiers are just sequences of more than 1 letter.
309+
* Identifiers are sequences of 1 or more letters.
293310
*/
294311
type ParseIdentifier<Input extends string> = ReadLetters<Input> extends [
295312
infer Name,
@@ -301,46 +318,29 @@ type ParseIdentifier<Input extends string> = ReadLetters<Input> extends [
301318
: ParserError<`No (possibly double-quoted) identifier at \`${Input}\``>
302319

303320
/**
304-
* Parses a node.
305-
* A node is one of the following:
306-
* - `*`
321+
* Parses a field without preceding field renaming.
322+
* A field is one of the following:
307323
* - `field`
308324
* - `field::type`
309325
* - `field->json...`
310326
* - `field(nodes)`
311327
* - `field!hint(nodes)`
312328
* - `field!inner(nodes)`
313329
* - `field!hint!inner(nodes)`
314-
* - `renamed_field:field`
315-
* - `renamed_field:field::type`
316-
* - `renamed_field:field->json...`
317-
* - `renamed_field:field(nodes)`
318-
* - `renamed_field:field!hint(nodes)`
319-
* - `renamed_field:field!inner(nodes)`
320-
* - `renamed_field:field!hint!inner(nodes)`
321330
*
322-
* TODO: more support for JSON operators `->`, `->>`.
331+
* TODO: support type casting of JSON operators `a->b::type`, `a->>b::type`.
323332
*/
324-
type ParseNode<Input extends string> = Input extends ''
333+
type ParseField<Input extends string> = Input extends ''
325334
? ParserError<'Empty string'>
326-
: // `*`
327-
Input extends `*${infer Remainder}`
328-
? [{ star: true }, EatWhitespace<Remainder>]
329335
: ParseIdentifier<Input> extends [infer Name, `${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}`
336+
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
338337
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [infer Fields, `${infer Remainder}`]
339338
? // `field!inner(nodes)`
340339
[{ name: Name; original: Name; children: Fields }, EatWhitespace<Remainder>]
341-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends ParserError<string>
342-
? ParseEmbeddedResource<EatWhitespace<Remainder>>
343-
: ParserError<'Expected embedded resource after `!inner`'>
340+
: CreateParserErrorIfRequired<
341+
ParseEmbeddedResource<EatWhitespace<Remainder>>,
342+
'Expected embedded resource after `!inner`'
343+
>
344344
: EatWhitespace<Remainder> extends `!${infer Remainder}`
345345
? ParseIdentifier<EatWhitespace<Remainder>> extends [infer Hint, `${infer Remainder}`]
346346
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
@@ -350,89 +350,21 @@ type ParseNode<Input extends string> = Input extends ''
350350
]
351351
? // `field!hint!inner(nodes)`
352352
[{ name: Name; original: Name; hint: Hint; children: Fields }, EatWhitespace<Remainder>]
353-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends ParserError<string>
354-
? ParseEmbeddedResource<EatWhitespace<Remainder>>
355-
: ParserError<'Expected embedded resource after `!inner`'>
353+
: CreateParserErrorIfRequired<
354+
ParseEmbeddedResource<EatWhitespace<Remainder>>,
355+
'Expected embedded resource after `!inner`'
356+
>
356357
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
357358
infer Fields,
358359
`${infer Remainder}`
359360
]
360361
? // `field!hint(nodes)`
361362
[{ name: Name; original: Name; hint: Hint; children: Fields }, EatWhitespace<Remainder>]
362-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends ParserError<string>
363-
? ParseEmbeddedResource<EatWhitespace<Remainder>>
364-
: ParserError<'Expected embedded resource after `!hint`'>
363+
: CreateParserErrorIfRequired<
364+
ParseEmbeddedResource<EatWhitespace<Remainder>>,
365+
'Expected embedded resource after `!hint`'
366+
>
365367
: ParserError<'Expected identifier after `!`'>
366-
: EatWhitespace<Remainder> extends `:${infer Remainder}`
367-
? ParseIdentifier<EatWhitespace<Remainder>> extends [infer OriginalName, `${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}`
376-
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
377-
infer Fields,
378-
`${infer Remainder}`
379-
]
380-
? // `renamed_field:field!inner(nodes)`
381-
[{ name: Name; original: OriginalName; children: Fields }, EatWhitespace<Remainder>]
382-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends ParserError<string>
383-
? ParseEmbeddedResource<EatWhitespace<Remainder>>
384-
: ParserError<'Expected embedded resource after `!inner`'>
385-
: EatWhitespace<Remainder> extends `!${infer Remainder}`
386-
? ParseIdentifier<EatWhitespace<Remainder>> extends [infer Hint, `${infer Remainder}`]
387-
? EatWhitespace<Remainder> extends `!inner${infer Remainder}`
388-
? ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
389-
infer Fields,
390-
`${infer Remainder}`
391-
]
392-
? // `renamed_field:field!hint!inner(nodes)`
393-
[
394-
{ name: Name; original: OriginalName; hint: Hint; children: Fields },
395-
EatWhitespace<Remainder>
396-
]
397-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends ParserError<string>
398-
? ParseEmbeddedResource<EatWhitespace<Remainder>>
399-
: ParserError<'Expected embedded resource after `!inner`'>
400-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
401-
infer Fields,
402-
`${infer Remainder}`
403-
]
404-
? // `renamed_field:field!hint(nodes)`
405-
[
406-
{
407-
name: Name
408-
original: OriginalName
409-
hint: Hint
410-
children: Fields
411-
},
412-
EatWhitespace<Remainder>
413-
]
414-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends ParserError<string>
415-
? ParseEmbeddedResource<EatWhitespace<Remainder>>
416-
: ParserError<'Expected embedded resource after `!hint`'>
417-
: ParserError<'Expected identifier after `!`'>
418-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends [
419-
infer Fields,
420-
`${infer Remainder}`
421-
]
422-
? // `renamed_field:field(nodes)`
423-
[{ name: Name; original: OriginalName; children: Fields }, EatWhitespace<Remainder>]
424-
: ParseJsonAccessor<EatWhitespace<Remainder>> extends [
425-
infer _PropertyName,
426-
infer PropertyType,
427-
`${infer Remainder}`
428-
]
429-
? // `renamed_field:field->json...`
430-
[{ name: Name; type: PropertyType }, EatWhitespace<Remainder>]
431-
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends ParserError<string>
432-
? ParseEmbeddedResource<EatWhitespace<Remainder>>
433-
: // `renamed_field:field`
434-
[{ name: Name; original: OriginalName }, EatWhitespace<Remainder>]
435-
: ParseIdentifier<EatWhitespace<Remainder>>
436368
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends [infer Fields, `${infer Remainder}`]
437369
? // `field(nodes)`
438370
[{ name: Name; original: Name; children: Fields }, EatWhitespace<Remainder>]
@@ -442,13 +374,48 @@ type ParseNode<Input extends string> = Input extends ''
442374
`${infer Remainder}`
443375
]
444376
? // `field->json...`
445-
[{ name: PropertyName; type: PropertyType }, EatWhitespace<Remainder>]
377+
[{ name: PropertyName; original: PropertyName; type: PropertyType }, EatWhitespace<Remainder>]
446378
: ParseEmbeddedResource<EatWhitespace<Remainder>> extends ParserError<string>
447379
? ParseEmbeddedResource<EatWhitespace<Remainder>>
380+
: EatWhitespace<Remainder> extends `::${infer Remainder}`
381+
? ParseIdentifier<Remainder> extends [`${infer CastType}`, `${infer Remainder}`]
382+
? // `field::type`
383+
CastType extends PostgreSQLTypes
384+
? [{ name: Name; type: TypeScriptTypes<CastType> }, EatWhitespace<Remainder>]
385+
: ParserError<`Invalid type for \`::\` operator \`${CastType}\``>
386+
: ParserError<`Invalid type for \`::\` operator at \`${Remainder}\``>
448387
: // `field`
449388
[{ name: Name; original: Name }, EatWhitespace<Remainder>]
450389
: ParserError<`Expected identifier at \`${Input}\``>
451390

391+
/**
392+
* Parses a node.
393+
* A node is one of the following:
394+
* - `*`
395+
* - a field, as defined above
396+
* - a renamed field, `renamed_field:field`
397+
*/
398+
type ParseNode<Input extends string> = Input extends ''
399+
? ParserError<'Empty string'>
400+
: // `*`
401+
Input extends `*${infer Remainder}`
402+
? [{ star: true }, EatWhitespace<Remainder>]
403+
: ParseIdentifier<Input> extends [infer Name, `${infer Remainder}`]
404+
? EatWhitespace<Remainder> extends `::${infer _Remainder}`
405+
? // `field::`
406+
// Special case to detect type-casting before renaming.
407+
ParseField<Input>
408+
: EatWhitespace<Remainder> extends `:${infer Remainder}`
409+
? // `renamed_field:`
410+
ParseField<EatWhitespace<Remainder>> extends [infer Field, `${infer Remainder}`]
411+
? Field extends { name: string }
412+
? [Prettify<Omit<Field, 'name'> & { name: Name }>, EatWhitespace<Remainder>]
413+
: ParserError<`Unable to parse renamed field`>
414+
: ParserError<`Unable to parse renamed field`>
415+
: // Otherwise, just parse it as a field without renaming.
416+
ParseField<Input>
417+
: ParserError<`Expected identifier at \`${Input}\``>
418+
452419
/**
453420
* Parses a JSON property accessor of the shape `->a->b->c`. The last accessor in
454421
* the series may convert to text by using the ->> operator instead of ->.
@@ -560,7 +527,9 @@ type GetResultHelper<
560527
/**
561528
* Constructs a type definition for an object based on a given PostgREST query.
562529
*
563-
* @param Row Record<string, unknown>.
530+
* @param Schema Database schema.
531+
* @param Row Type of a row in the given table.
532+
* @param Relationships Relationships between different tables in the database.
564533
* @param Query Select query string literal to parse.
565534
*/
566535
export type GetResult<

0 commit comments

Comments
 (0)