This guide establishes the standards for SQL in Boundary.
These guidelines are designed to make our SQL more readable, maintainable, and greppable.
- No tabs. 2 spaces per indent.
- No trailing whitespace.
- No more than two blank lines between statements.
- No empty lines in the middle of a single statement.
- Do not SHOUTCASE or "Sentence case" SQL keywords (e.g., use
select
, notSELECT
orSelect
). - Use "river" formatting for DML.
Keep the object of create
, alter
, drop
, and comment on
statements on the
same line as the statement.
create table imaginary_basket (
public_id wt_public_id primary key,
store_id wt_public_id not null
constraint imaginary_store_fkey
references imaginary_store (public_id)
on delete cascade
on update cascade,
basket_name text not null
constraint basket_name_must_not_be_empty
check(
length(trim(basket_name)) > 0
),
basket_type text not null default 'fruit_basket'
constraint basket_type_enm_fkey
references basket_type_enm (name)
on delete restrict
on update cascade,
constraint imaginary_basket_store_id_basket_name_uq
unique(store_id, basket_name)
);
comment on table imaginary_basket is
'imaginary_basket is a table where each row is a resource that represents an imaginary shopping basket.';
For create table
statements:
- Keep the
create table
statement and the name of the table on the same line. - For columns, keep the column name, column type,
not null
(if applicable), and default value (if applicable) on the same line. - Put the
primary key
declaration at the start of thecreate table
statement. - If the
primary key
is a single column, put theprimary key
declaration on the same line as the column name and column type. - Put constraints for a single column on a new line indented under the column declaration.
Constraints:
- Name all constraints.
- Give
check
constraints a name that describes what rule the constraint is enforcing. - The naming pattern for foreign key constraints is
reftable_fkey
wherereftable
is the name of the referenced table. - The naming pattern for unique constraints is
tablename_col1_colx_uq
wheretablename
is the name of the table andcol1_colx
is the name of each column in the unique constraint. - The naming pattern for unique constraint names which would be over 63 characters
in length is to prioritize keeping the full
tablename_uq
pattern intact and abbreviate the names of the columns.
create or replace function colorize_basket(basket_id wt_public_id, basket_color text) returns void
as $$
begin
....
end;
$$ language plpgsql;
comment on function colorize_basket is
'colorize_basket is a function ....';
For create function
and create or replace function
statements:
- Keep the
create function
statement, function name, function parameters andreturns
statement on the same line.
create trigger update_version_column after update of version, termination_reason, key_id, tofu_token, server_id, server_type on session
for each row execute procedure update_version_column();
For create trigger
statements:
- Keep the
create trigger
statement, trigger name,before | after
, event name, andon table_name
statement on the same line. - Put the remaining statements on a new indented line.
create domain wt_private_id as text not null
check(
length(trim(value)) > 10
);
comment on domain wt_private_id is
'Random ID generated with github.com/hashicorp/go-secure-stdlib/base62';
For create domain
statements:
- Keep the
create domain
statement, domain name,as data_type
,not null
(if applicable), and default value (if applicable) on the same line. - Put check constraints on new lines.
For comment on
statements:
- Keep the
comment on
statement, object type, object name, andis
statement on the same line. - Put
comment on
statements directly below the database object the comment is on. - Do not put any blank lines between the
comment on
statement and the database object declaration block that comment is for. - Put the text of the
comment on
a new indented line.
Data manipulation statements should align SQL keywords in a column to form a river between the keywords and the rest of the statement. This makes it easier to scan the statement. It can also help identify keywords in cases where syntax highlighting might not be available, such as when statements are written in go string literals.
select b.public_id,
b.basket_name,
b.basket_type,
s.store_name,
s.store_descrption
from imaginary_basket as b
left join imaginary_store as s
on b.store_id = s.public_id
where basket_type = $1;
update imaginary_basket
set basket_name = basket_name || '(' || basket_type || ')'
where basket_type = $1;
delete
from imaginary_basket
where basket_type = $1;
For insert statements, align the spacing of the column names and values.
insert into imaginary_basket
(public_id, store_id, basket_type, basket_name)
values ('b_123456789', 's_123456789', 'fruit_basket', 'my fruit basket'),
('b_234567891', 's_123456789', 'toy_basket', 'my toy basket');
For more complex queries, there can be multiple rivers, like when a query includes sub-queries, or when using a common table expression (CTE):
with baskets as (
select public_id
from basket
where (create_time, public_id) < ($1, $2)
order by create_time desc,
public_id desc
limit 1000
),
imaginary_baskets as (
select *
from imaginary_basket
where public_id in (select public_id
from baskets)
),
real_baskets as (
select *
from real_basket
where public_id in (select public_id
from baskets)
),
final as (
select public_id,
basket_name,
basket_type,
'imaginary' as type
from imaginary_baskets
union
select public_id,
basket_name,
basket_type,
'real' as type
from real_baskets
)
select *
from final
order by create_time desc,
public_id desc;