Annotated SQL files
PgTyped supports parsing queries from SQL files, allowing developers to write DB queries in their favourite database IDE. To help PgTyped generate executable queries from these SQL files, they need to be annotated with special comments.
/* @name getAllComments */
SELECT * FROM book_comments WHERE id = :commentId;
/*
@name selectSomeUsers
@param ages -> (...)
*/
SELECT FROM users WHERE age in :ages;
Annotation format
PgTyped has a number of requirements for SQL file contents:
- Each query must be preceded with an annotation (comment).
- An annotation must specify the query name using the
@name
tag. - Each query must be a single SQL statement that ends with a semicolon.
- Queries can contain parameters. Parameters should start with a colon, ex.
:paramName
. - Annotations can include param expansions if needed using the
@param
tag. - Parameters can be forced to be not nullable using an exclamation mark
:paramName!
. - Nullability on output columns for output columns can be specified using column aliases such as
AS "name?"
orAS "name!"
.
Parameter expansions
Parameter expansions allow the user to pass arrays and objects as query parameters. This allows to build more complicated queries, which would be impossible or would look too big if they used only scalar parameters.
For example, with parameter expansions a typical insert query looks like this:
/*
@name InsertComment
@param comments -> ((userId, commentBody)...)
*/
INSERT INTO book_comments (user_id, body)
VALUES :comments;
Here comments -> ((userId, commentBody)...)
is a parameter expansion that instructs pgtyped to expand comments
into an array of objects with each object having a userId
and commentBody
field.
A query can also contain multiple expansions if needed:
/*
@name selectSomeUsers
@param ages -> (...)
@param names -> (...)
*/
SELECT FROM users WHERE age in :ages or name in :names;
At the moment, PgTyped supports four expansion types:
Array spread
The array spread expansion allows to pass an array of scalars as parameter.
Syntax:
@param paramName -> (...)
Example:
/*
@name selectSomeUsers
@param ages -> (...)
*/
SELECT FROM users WHERE age in :ages;
const parameters = { ages: [25, 30, 35] };
selectSomeUsers.run(parameters, connection);
-- Parameters: [25, 30, 35]
SELECT FROM users WHERE age in ($1, $2, $3);
Array literal
The array literal expansion allows to pass an array of scalars as parameter.
Syntax:
@param paramName -> ARRAY[...]
Example:
/*
@name selectFunctionResult
@param values -> ARRAY[...]
*/
SELECT FROM my_function(:values);
const parameters = { values: ['a', 'b', 'c'] };
selectSomeUsers.run(parameters, connection);
-- Parameters: ['a', 'b', 'c']
SELECT FROM my_function(ARRAY[$1, $2, $3]);
Object pick
The object pick expansion allows to pass an object as a parameter.
Syntax:
@param paramName -> (name, age)
Example:
/*
@name insertUsers
@param user -> (name, age)
*/
INSERT INTO users (name, age) VALUES :user RETURNING id;
const parameters = { user: { name: 'Rob', age: 56 } };
insertUsers.run(parameters, connection);
-- Bindings: ['Rob', 56]
INSERT INTO users (name, age) VALUES ($1, $2) RETURNING id;
Array spread and pick
The array spread-and-pick expansion allows to pass an array of objects as a parameter.
Syntax:
@param paramName -> ((name, age)...)
Example:
/*
@name insertUsers
@param users -> ((name, age)...)
*/
INSERT INTO users (name, age) VALUES :users RETURNING id;`;
const parameters = {
users: [
{ name: 'Rob', age: 56 },
{ name: 'Tom', age: 45 },
],
};
insertUsers.run(parameters, connection);
-- Bindings: ['Rob', 56, 'Tom', 45]
INSERT INTO users (name, age) VALUES ($1, $2), ($3, $4) RETURNING id;
Enforcing non-nullability for parameters
Sometimes you might want to force pgTyped to use a non-nullable type for a nullable parameter.
This can be done using the exclamation mark modifier :paramName!
.
Example:
/* @name GetAllComments */
SELECT * FROM book_comments WHERE id = :id OR user_id = :id;
/* @name GetAllCommentsStrict */
SELECT * FROM book_comments WHERE id = :id! OR user_id = :id;
export interface IGetAllCommentsParams {
id: number | null | void;
}
export interface IGetAllCommentsStrictParams {
id: number;
}
Enforcing (non)-nullability on output columns
Sometimes you might want to force pgTyped to use a (non-)nullable type for an output column as Postgres limits how much
information can be automatically discovered. This can be the case with materialized views and function calls for
example.
You can enforce nullability on output columns by aliasing the column by using AS "name?"
to make it nullable or
AS "name!"
to enforce non-nullability.
Example:
/* @name GetTotalUserScores */
SELECT coalesce(sum(score), 0) AS "total_score!" FROM users WHERE id = :id!;
/* @name GetUsersAndTheirNames */
SELECT id, name AS "name?" FROM users LEFT JOIN names USING (id);
export interface IGetTotalUserScoresResult {
total_score: number;
}
export interface IGetUsersAndTheirNamesResult {
id: number;
name: string | null;
}
We will be adding more annotation tags and expansion types in the future.
If you have an idea for a new expansion type, or a new annotation tag, please submit an issue for that so we can consider it.