PostgreSQL, and therefore YSQL, natively support both language sql and language plpgsql functions and procedures. But the implementation of a do statement can only be language plpgsql. PL/pgSQL source text is governed by the plpgsql_block_stmt rule. See these sections:
The syntax diagrams in these three sections show that the PL/pgSQL source text must be enquoted. Yugabyte recommends that, for consistency, you use dollar quoting around the source text and that you spell this as $body$. Notice that PL/pgSQL's dynamic SQL feature lets you write a user-defined procedure that will create a user-defined subprogram. If you take advantage of this, then you'll have to use different enquoting syntax around the source text of the to-be-created subprogram.
This section, and its subsections, specify:
- the grammar of the plpgsql_block_stmt rule
 - its decomposition down to terminal rules
 - the associated semantics.
 
plpgsql_block_stmt ::= [ << label >> ]  
                       [ plpgsql_declaration_section ]  
                       plpgsql_executable_section  
                       [ plpgsql_exception_section ] END [ label ] ;
plpgsql_declaration_section ::= DECLARE 
                                [ plpgsql_declaration [ ... ] ]
plpgsql_executable_section ::= BEGIN 
                               [ plpgsql_executable_stmt [ ... ] ]
plpgsql_exception_section ::= EXCEPTION { plpgsql_handler [ ... ] }
The minimal PL/pgSQL source text
The executable section can include a block statement—and this implies the possibility of an arbitrarily deep nesting. It's this that underpins this characterization of PL/pgSQL at the start of this overall section on language plpgsql subprograms:
PL/pgSQL is a conventional, block-structured, imperative programming language [whose] basic syntax conventions and repertoire of simple and compound statements seem to be inspired by Ada.
The executable section is mandatory. This, therefore, is the minimal form of a PL/pgSQL source text:
$body$
begin
end;
$body$;
It's useful to know this because each of create function and create procedure, when it completes without error, inevitably creates a subprogram upon which the execute privilege has already been granted to public. See these tips in the sections that describe these two create statements:
Each tip recommends that you always revoke this privilege immediately after creating a subprogram. However, even this might expose a momentary security risk. Here is the watertight secure approach:
create schema s;
create procedure s.p()
  language plpgsql
as $body$
begin
  null; -- Implementation to follow.
end;
$body$;
revoke execute on procedure s.p() from public;
-- "create or replace" leaves the extant privileges on "s.p" unchanged.
create or replace procedure s.p()
  set search_path = pg_catalog, pg_temp
  security definer
  language plpgsql
as $body$
declare
  -- (Optionally) the intended declarations.
  -- ...
begin
  -- The intended implementation.
  -- ...
exception
  -- (Optionally) the intended handlers.
  -- ...
end;
$body$;
Notice that null; is a legal PL/pgSQL executable statement. Of course, it does nothing at all. You might prefer to write null; explicitly to emphasize your intention. Now you can grant execute on s.p to the role(s) that you intend.
Each section is described in a dedicated subsection: