diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index da881a7737..3f02202caf 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5241,7 +5241,7 @@ SCRAM-SHA-256$<iteration count>:&l
prorettypeoidpg_type.oid
- Data type of the return value
+ Data type of the return value, or null for a procedure
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index e6f50ec819..9f583266de 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3947,7 +3947,7 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- Functions and operators
+ Functions, procedures, and operators
diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml
index d1872c1a5c..5a8d1f1b95 100644
--- a/doc/src/sgml/ecpg.sgml
+++ b/doc/src/sgml/ecpg.sgml
@@ -4778,7 +4778,9 @@ EXEC SQL WHENEVER conditionactionDO name (args)
- Call the specified C functions with the specified arguments.
+ Call the specified C functions with the specified arguments. (This
+ use is different from the meaning of CALL
+ and DO in the normal PostgreSQL grammar.)
diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
index 99b0ea8519..0faa72f1d3 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -3972,8 +3972,8 @@ ORDER BY c.ordinal_position;
routines
- The view routines contains all functions in the
- current database. Only those functions are shown that the current
+ The view routines contains all functions and procedures in the
+ current database. Only those functions and procedures are shown that the current
user has access to (by way of being the owner or having some
privilege).
@@ -4037,8 +4037,8 @@ ORDER BY c.ordinal_position;
routine_typecharacter_data
- Always FUNCTION (In the future there might
- be other types of routines.)
+ FUNCTION for a
+ function, PROCEDURE for a procedure
@@ -4087,7 +4087,7 @@ ORDER BY c.ordinal_position;
the view element_types), else
USER-DEFINED (in that case, the type is
identified in type_udt_name and associated
- columns).
+ columns). Null for a procedure.
@@ -4180,7 +4180,7 @@ ORDER BY c.ordinal_position;
sql_identifier
Name of the database that the return data type of the function
- is defined in (always the current database)
+ is defined in (always the current database). Null for a procedure.
@@ -4189,7 +4189,7 @@ ORDER BY c.ordinal_position;
sql_identifier
Name of the schema that the return data type of the function is
- defined in
+ defined in. Null for a procedure.
@@ -4197,7 +4197,7 @@ ORDER BY c.ordinal_position;
type_udt_namesql_identifier
- Name of the return data type of the function
+ Name of the return data type of the function. Null for a procedure.
@@ -4314,7 +4314,7 @@ ORDER BY c.ordinal_position;
If the function automatically returns null if any of its
arguments are null, then YES, else
- NO.
+ NO. Null for a procedure.
diff --git a/doc/src/sgml/plperl.sgml b/doc/src/sgml/plperl.sgml
index 33e39d85e4..100162dead 100644
--- a/doc/src/sgml/plperl.sgml
+++ b/doc/src/sgml/plperl.sgml
@@ -67,6 +67,10 @@ $$ LANGUAGE plperl;
as discussed below.
+
+ In a PL/Perl procedure, any return value from the Perl code is ignored.
+
+
PL/Perl also supports anonymous code blocks called with the
statement:
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 6d14b34448..7d23ed437e 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -156,7 +156,8 @@
Finally, a PL/pgSQL function can be declared to return
- void if it has no useful return value.
+ void if it has no useful return value. (Alternatively, it
+ could be written as a procedure in that case.)
@@ -1865,6 +1866,18 @@ SELECT * FROM get_available_flightid(CURRENT_DATE);
+
+ Returning From a Procedure
+
+
+ A procedure does not have a return value. A procedure can therefore end
+ without a RETURN statement. If
+ a RETURN statement is desired to exit the code early,
+ then NULL must be returned. Returning any other value
+ will result in an error.
+
+
+
Conditionals
@@ -5244,7 +5257,7 @@ show errors;
Here is how this function would end up in PostgreSQL:
-CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc() RETURNS void AS $func$
+CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc() AS $func$
DECLARE
referrer_keys CURSOR IS
SELECT * FROM cs_referrer_keys
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index ec5f671632..0dbeee1fa2 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -207,7 +207,11 @@ $$ LANGUAGE plpythonu;
yield (in case of a result-set statement). If
you do not provide a return value, Python returns the default
None. PL/Python translates
- Python's None into the SQL null value.
+ Python's None into the SQL null value. In a procedure,
+ the result from the Python code must be None (typically
+ achieved by ending the procedure without a return
+ statement or by using a return statement without
+ argument); otherwise, an error will be raised.
diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml
index 0646a8ba0b..8018783b0a 100644
--- a/doc/src/sgml/pltcl.sgml
+++ b/doc/src/sgml/pltcl.sgml
@@ -97,7 +97,8 @@ $$ LANGUAGE pltcl;
Tcl script as variables named 1
... n. The result is
returned from the Tcl code in the usual way, with
- a return statement.
+ a return statement. In a procedure, the return value
+ from the Tcl code is ignored.
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 01acc2ef9d..22e6893211 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -26,8 +26,10 @@ Complete list of usable sgml source files in this directory.
+
+
@@ -48,6 +50,7 @@ Complete list of usable sgml source files in this directory.
+
@@ -75,6 +78,7 @@ Complete list of usable sgml source files in this directory.
+
@@ -122,8 +126,10 @@ Complete list of usable sgml source files in this directory.
+
+
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index ab2c35b4dd..0c09f1db5c 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -39,7 +39,7 @@ GRANT { { USAGE | SELECT | UPDATE }
TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ]
GRANT { EXECUTE | ALL [ PRIVILEGES ] }
- ON FUNCTIONS
+ ON { FUNCTIONS | ROUTINES }
TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ]
GRANT { USAGE | ALL [ PRIVILEGES ] }
@@ -66,7 +66,7 @@ REVOKE [ GRANT OPTION FOR ]
REVOKE [ GRANT OPTION FOR ]
{ EXECUTE | ALL [ PRIVILEGES ] }
- ON FUNCTIONS
+ ON { FUNCTIONS | ROUTINES }
FROM { [ GROUP ] role_name | PUBLIC } [, ...]
[ CASCADE | RESTRICT ]
@@ -93,7 +93,13 @@ REVOKE [ GRANT OPTION FOR ]
affect privileges assigned to already-existing objects.) Currently,
only the privileges for schemas, tables (including views and foreign
tables), sequences, functions, and types (including domains) can be
- altered.
+ altered. For this command, functions include aggregates and procedures.
+ The words FUNCTIONS and ROUTINES are
+ equivalent in this command. (ROUTINES is preferred
+ going forward as the standard term for functions and procedures taken
+ together. In earlier PostgreSQL releases, only the
+ word FUNCTIONS was allowed. It is not possible to set
+ default privileges for functions and procedures separately.)
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index e54925507e..a2d405d6cd 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -45,6 +45,8 @@ ALTER EXTENSION name DROP object_name USING index_method |
OPERATOR FAMILY object_name USING index_method |
[ PROCEDURAL ] LANGUAGE object_name |
+ PROCEDURE procedure_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] |
+ ROUTINE routine_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] |
SCHEMA object_name |
SEQUENCE object_name |
SERVER object_name |
@@ -170,12 +172,14 @@ ALTER EXTENSION name DROP aggregate_namefunction_nameoperator_name
+ procedure_name
+ routine_name
The name of an object to be added to or removed from the extension.
Names of tables,
aggregates, domains, foreign tables, functions, operators,
- operator classes, operator families, sequences, text search objects,
+ operator classes, operator families, procedures, routines, sequences, text search objects,
types, and views can be schema-qualified.
@@ -204,7 +208,7 @@ ALTER EXTENSION name DROP
- The mode of a function or aggregate
+ The mode of a function, procedure, or aggregate
argument: IN, OUT,
INOUT, or VARIADIC.
If omitted, the default is IN.
@@ -222,7 +226,7 @@ ALTER EXTENSION name DROP
- The name of a function or aggregate argument.
+ The name of a function, procedure, or aggregate argument.
Note that ALTER EXTENSION does not actually pay
any attention to argument names, since only the argument data
types are needed to determine the function's identity.
@@ -235,7 +239,7 @@ ALTER EXTENSION name DROP
- The data type of a function or aggregate argument.
+ The data type of a function, procedure, or aggregate argument.
diff --git a/doc/src/sgml/ref/alter_function.sgml b/doc/src/sgml/ref/alter_function.sgml
index 196d2dde0c..d8747e0748 100644
--- a/doc/src/sgml/ref/alter_function.sgml
+++ b/doc/src/sgml/ref/alter_function.sgml
@@ -359,6 +359,8 @@ ALTER FUNCTION check_password(text) RESET search_path;
+
+
diff --git a/doc/src/sgml/ref/alter_procedure.sgml b/doc/src/sgml/ref/alter_procedure.sgml
new file mode 100644
index 0000000000..dae80076d9
--- /dev/null
+++ b/doc/src/sgml/ref/alter_procedure.sgml
@@ -0,0 +1,281 @@
+
+
+
+
+ ALTER PROCEDURE
+
+
+
+ ALTER PROCEDURE
+ 7
+ SQL - Language Statements
+
+
+
+ ALTER PROCEDURE
+ change the definition of a procedure
+
+
+
+
+ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ action [ ... ] [ RESTRICT ]
+ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ RENAME TO new_name
+ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
+ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ SET SCHEMA new_schema
+ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ DEPENDS ON EXTENSION extension_name
+
+where action is one of:
+
+ [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
+ SET configuration_parameter { TO | = } { value | DEFAULT }
+ SET configuration_parameter FROM CURRENT
+ RESET configuration_parameter
+ RESET ALL
+
+
+
+
+ Description
+
+
+ ALTER PROCEDURE changes the definition of a
+ procedure.
+
+
+
+ You must own the procedure to use ALTER PROCEDURE.
+ To change a procedure's schema, you must also have CREATE
+ privilege on the new schema.
+ To alter the owner, you must also be a direct or indirect member of the new
+ owning role, and that role must have CREATE privilege on
+ the procedure's schema. (These restrictions enforce that altering the owner
+ doesn't do anything you couldn't do by dropping and recreating the procedure.
+ However, a superuser can alter ownership of any procedure anyway.)
+
+
+
+
+ Parameters
+
+
+
+ name
+
+
+ The name (optionally schema-qualified) of an existing procedure. If no
+ argument list is specified, the name must be unique in its schema.
+
+
+
+
+
+ argmode
+
+
+
+ The mode of an argument: IN or VARIADIC.
+ If omitted, the default is IN.
+
+
+
+
+
+ argname
+
+
+
+ The name of an argument.
+ Note that ALTER PROCEDURE does not actually pay
+ any attention to argument names, since only the argument data
+ types are needed to determine the procedure's identity.
+
+
+
+
+
+ argtype
+
+
+
+ The data type(s) of the procedure's arguments (optionally
+ schema-qualified), if any.
+
+
+
+
+
+ new_name
+
+
+ The new name of the procedure.
+
+
+
+
+
+ new_owner
+
+
+ The new owner of the procedure. Note that if the procedure is
+ marked SECURITY DEFINER, it will subsequently
+ execute as the new owner.
+
+
+
+
+
+ new_schema
+
+
+ The new schema for the procedure.
+
+
+
+
+
+ extension_name
+
+
+ The name of the extension that the procedure is to depend on.
+
+
+
+
+
+ EXTERNAL SECURITY INVOKER
+ EXTERNAL SECURITY DEFINER
+
+
+
+ Change whether the procedure is a security definer or not. The
+ key word EXTERNAL is ignored for SQL
+ conformance. See for more information about
+ this capability.
+
+
+
+
+
+ configuration_parameter
+ value
+
+
+ Add or change the assignment to be made to a configuration parameter
+ when the procedure is called. If
+ value is DEFAULT
+ or, equivalently, RESET is used, the procedure-local
+ setting is removed, so that the procedure executes with the value
+ present in its environment. Use RESET
+ ALL to clear all procedure-local settings.
+ SET FROM CURRENT saves the value of the parameter that
+ is current when ALTER PROCEDURE is executed as the value
+ to be applied when the procedure is entered.
+
+
+
+ See and
+
+ for more information about allowed parameter names and values.
+
+
+
+
+
+ RESTRICT
+
+
+
+ Ignored for conformance with the SQL standard.
+
+
+
+
+
+
+
+ Examples
+
+
+ To rename the procedure insert_data with two arguments
+ of type integer to insert_record:
+
+ALTER PROCEDURE insert_data(integer, integer) RENAME TO insert_record;
+
+
+
+
+ To change the owner of the procedure insert_data with
+ two arguments of type integer to joe:
+
+ALTER PROCEDURE insert_data(integer, integer) OWNER TO joe;
+
+
+
+
+ To change the schema of the procedure insert_data with
+ two arguments of type integer
+ to accounting:
+
+ALTER PROCEDURE insert_data(integer, integer) SET SCHEMA accounting;
+
+
+
+
+ To mark the procedure insert_data(integer, integer) as
+ being dependent on the extension myext:
+
+ALTER PROCEDURE insert_data(integer, integer) DEPENDS ON EXTENSION myext;
+
+
+
+
+ To adjust the search path that is automatically set for a procedure:
+
+ALTER PROCEDURE check_password(text) SET search_path = admin, pg_temp;
+
+
+
+
+ To disable automatic setting of search_path for a procedure:
+
+ALTER PROCEDURE check_password(text) RESET search_path;
+
+ The procedure will now execute with whatever search path is used by its
+ caller.
+
+
+
+
+ Compatibility
+
+
+ This statement is partially compatible with the ALTER
+ PROCEDURE statement in the SQL standard. The standard allows more
+ properties of a procedure to be modified, but does not provide the
+ ability to rename a procedure, make a procedure a security definer,
+ attach configuration parameter values to a procedure,
+ or change the owner, schema, or volatility of a procedure. The standard also
+ requires the RESTRICT key word, which is optional in
+ PostgreSQL.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/alter_routine.sgml b/doc/src/sgml/ref/alter_routine.sgml
new file mode 100644
index 0000000000..d1699691e1
--- /dev/null
+++ b/doc/src/sgml/ref/alter_routine.sgml
@@ -0,0 +1,102 @@
+
+
+
+
+ ALTER ROUTINE
+
+
+
+ ALTER ROUTINE
+ 7
+ SQL - Language Statements
+
+
+
+ ALTER ROUTINE
+ change the definition of a routine
+
+
+
+
+ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ action [ ... ] [ RESTRICT ]
+ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ RENAME TO new_name
+ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
+ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ SET SCHEMA new_schema
+ALTER ROUTINE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ]
+ DEPENDS ON EXTENSION extension_name
+
+where action is one of:
+
+ IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF
+ [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
+ COST execution_cost
+ ROWS result_rows
+ SET configuration_parameter { TO | = } { value | DEFAULT }
+ SET configuration_parameter FROM CURRENT
+ RESET configuration_parameter
+ RESET ALL
+
+
+
+
+ Description
+
+
+ ALTER ROUTINE changes the definition of a routine, which
+ can be an aggregate function, a normal function, or a procedure. See
+ under , ,
+ and for the description of the
+ parameters, more examples, and further details.
+
+
+
+
+ Examples
+
+
+ To rename the routine foo for type
+ integer to foobar:
+
+ALTER ROUTINE foo(integer) RENAME TO foobar;
+
+ This command will work independent of whether foo is an
+ aggregate, function, or procedure.
+
+
+
+
+ Compatibility
+
+
+ This statement is partially compatible with the ALTER
+ ROUTINE statement in the SQL standard. See
+ under
+ and for more details. Allowing
+ routine names to refer to aggregate functions is
+ a PostgreSQL extension.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+ Note that there is no CREATE ROUTINE command.
+
+
+
diff --git a/doc/src/sgml/ref/call.sgml b/doc/src/sgml/ref/call.sgml
new file mode 100644
index 0000000000..2741d8d15e
--- /dev/null
+++ b/doc/src/sgml/ref/call.sgml
@@ -0,0 +1,97 @@
+
+
+
+
+ CALL
+
+
+
+ CALL
+ 7
+ SQL - Language Statements
+
+
+
+ CALL
+ invoke a procedure
+
+
+
+
+CALL name ( [ argument ] [ , ...] )
+
+
+
+
+ Description
+
+
+ CALL executes a procedure.
+
+
+
+
+ Parameters
+
+
+
+ name
+
+
+ The name (optionally schema-qualified) of the procedure.
+
+
+
+
+
+ argument
+
+
+ An argument for the procedure call.
+ See for the full details on
+ function and procedure call syntax, including use of named parameters.
+
+
+
+
+
+
+
+ Notes
+
+
+ The user must have EXECUTE privilege on the procedure in
+ order to be allowed to invoke it.
+
+
+
+ To call a function (not a procedure), use SELECT instead.
+
+
+
+
+ Examples
+
+CALL do_db_maintenance();
+
+
+
+
+ Compatibility
+
+
+ CALL conforms to the SQL standard.
+
+
+
+
+ See Also
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 7d66c1a34c..965c5a40ad 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -46,8 +46,10 @@ COMMENT ON
OPERATOR FAMILY object_name USING index_method |
POLICY policy_name ON table_name |
[ PROCEDURAL ] LANGUAGE object_name |
+ PROCEDURE procedure_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] |
PUBLICATION object_name |
ROLE object_name |
+ ROUTINE routine_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] |
RULE rule_name ON table_name |
SCHEMA object_name |
SEQUENCE object_name |
@@ -121,13 +123,15 @@ COMMENT ON
function_nameoperator_namepolicy_name
+ procedure_name
+ routine_namerule_nametrigger_name
The name of the object to be commented. Names of tables,
aggregates, collations, conversions, domains, foreign tables, functions,
- indexes, operators, operator classes, operator families, sequences,
+ indexes, operators, operator classes, operator families, procedures, routines, sequences,
statistics, text search objects, types, and views can be
schema-qualified. When commenting on a column,
relation_name must refer
@@ -170,7 +174,7 @@ COMMENT ON
argmode
- The mode of a function or aggregate
+ The mode of a function, procedure, or aggregate
argument: IN, OUT,
INOUT, or VARIADIC.
If omitted, the default is IN.
@@ -187,7 +191,7 @@ COMMENT ON
argname
- The name of a function or aggregate argument.
+ The name of a function, procedure, or aggregate argument.
Note that COMMENT does not actually pay
any attention to argument names, since only the argument data
types are needed to determine the function's identity.
@@ -199,7 +203,7 @@ COMMENT ON
argtype
- The data type of a function or aggregate argument.
+ The data type of a function, procedure, or aggregate argument.
@@ -325,6 +329,7 @@ COMMENT ON OPERATOR - (NONE, integer) IS 'Unary minus';
COMMENT ON OPERATOR CLASS int4ops USING btree IS '4 byte integer operators for btrees';
COMMENT ON OPERATOR FAMILY integer_ops USING btree IS 'all integer operators for btrees';
COMMENT ON POLICY my_policy ON mytable IS 'Filter rows by users';
+COMMENT ON PROCEDURE my_proc (integer, integer) IS 'Runs a report';
COMMENT ON ROLE my_role IS 'Administration group for finance tables';
COMMENT ON RULE my_rule ON my_table IS 'Logs updates of employee records';
COMMENT ON SCHEMA my_schema IS 'Departmental data';
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 75331165fe..fd229d1193 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -55,9 +55,9 @@ CREATE [ OR REPLACE ] FUNCTION
If a schema name is included, then the function is created in the
specified schema. Otherwise it is created in the current schema.
- The name of the new function must not match any existing function
+ The name of the new function must not match any existing function or procedure
with the same input argument types in the same schema. However,
- functions of different argument types can share a name (this is
+ functions and procedures of different argument types can share a name (this is
called overloading).
@@ -450,7 +450,7 @@ CREATE [ OR REPLACE ] FUNCTION
- execution_cost
+ COSTexecution_cost
@@ -466,7 +466,7 @@ CREATE [ OR REPLACE ] FUNCTION
- result_rows
+ ROWSresult_rows
@@ -818,7 +818,7 @@ COMMIT;
Compatibility
- A CREATE FUNCTION command is defined in SQL:1999 and later.
+ A CREATE FUNCTION command is defined in the SQL standard.
The PostgreSQL version is similar but
not fully compatible. The attributes are not portable, neither are the
different available languages.
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
new file mode 100644
index 0000000000..d712043824
--- /dev/null
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -0,0 +1,341 @@
+
+
+
+
+ CREATE PROCEDURE
+
+
+
+ CREATE PROCEDURE
+ 7
+ SQL - Language Statements
+
+
+
+ CREATE PROCEDURE
+ define a new procedure
+
+
+
+
+CREATE [ OR REPLACE ] PROCEDURE
+ name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
+ { LANGUAGE lang_name
+ | TRANSFORM { FOR TYPE type_name } [, ... ]
+ | [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
+ | SET configuration_parameter { TO value | = value | FROM CURRENT }
+ | AS 'definition'
+ | AS 'obj_file', 'link_symbol'
+ } ...
+
+
+
+
+ Description
+
+
+ CREATE PROCEDURE defines a new procedure.
+ CREATE OR REPLACE PROCEDURE will either create a
+ new procedure, or replace an existing definition.
+ To be able to define a procedure, the user must have the
+ USAGE privilege on the language.
+
+
+
+ If a schema name is included, then the procedure is created in the
+ specified schema. Otherwise it is created in the current schema.
+ The name of the new procedure must not match any existing procedure or function
+ with the same input argument types in the same schema. However,
+ procedures and functions of different argument types can share a name (this is
+ called overloading).
+
+
+
+ To replace the current definition of an existing procedure, use
+ CREATE OR REPLACE PROCEDURE. It is not possible
+ to change the name or argument types of a procedure this way (if you
+ tried, you would actually be creating a new, distinct procedure).
+
+
+
+ When CREATE OR REPLACE PROCEDURE is used to replace an
+ existing procedure, the ownership and permissions of the procedure
+ do not change. All other procedure properties are assigned the
+ values specified or implied in the command. You must own the procedure
+ to replace it (this includes being a member of the owning role).
+
+
+
+ The user that creates the procedure becomes the owner of the procedure.
+
+
+
+ To be able to create a procedure, you must have USAGE
+ privilege on the argument types.
+
+
+
+
+ Parameters
+
+
+
+ name
+
+
+
+ The name (optionally schema-qualified) of the procedure to create.
+
+
+
+
+
+ argmode
+
+
+
+ The mode of an argument: IN or VARIADIC.
+ If omitted, the default is IN.
+
+
+
+
+
+ argname
+
+
+
+ The name of an argument.
+
+
+
+
+
+ argtype
+
+
+
+ The data type(s) of the procedure's arguments (optionally
+ schema-qualified), if any. The argument types can be base, composite,
+ or domain types, or can reference the type of a table column.
+
+
+ Depending on the implementation language it might also be allowed
+ to specify pseudo-types such as cstring.
+ Pseudo-types indicate that the actual argument type is either
+ incompletely specified, or outside the set of ordinary SQL data types.
+
+
+ The type of a column is referenced by writing
+ table_name.column_name%TYPE.
+ Using this feature can sometimes help make a procedure independent of
+ changes to the definition of a table.
+
+
+
+
+
+ default_expr
+
+
+
+ An expression to be used as default value if the parameter is
+ not specified. The expression has to be coercible to the
+ argument type of the parameter.
+ All input parameters following a
+ parameter with a default value must have default values as well.
+
+
+
+
+
+ lang_name
+
+
+
+ The name of the language that the procedure is implemented in.
+ It can be sql, c,
+ internal, or the name of a user-defined
+ procedural language, e.g. plpgsql. Enclosing the
+ name in single quotes is deprecated and requires matching case.
+
+
+
+
+
+ TRANSFORM { FOR TYPE type_name } [, ... ] }
+
+
+
+ Lists which transforms a call to the procedure should apply. Transforms
+ convert between SQL types and language-specific data types;
+ see . Procedural language
+ implementations usually have hardcoded knowledge of the built-in types,
+ so those don't need to be listed here. If a procedural language
+ implementation does not know how to handle a type and no transform is
+ supplied, it will fall back to a default behavior for converting data
+ types, but this depends on the implementation.
+
+
+
+
+
+ EXTERNAL SECURITY INVOKER
+ EXTERNAL SECURITY DEFINER
+
+
+ SECURITY INVOKER indicates that the procedure
+ is to be executed with the privileges of the user that calls it.
+ That is the default. SECURITY DEFINER
+ specifies that the procedure is to be executed with the
+ privileges of the user that owns it.
+
+
+
+ The key word EXTERNAL is allowed for SQL
+ conformance, but it is optional since, unlike in SQL, this feature
+ applies to all procedures not only external ones.
+
+
+
+
+
+ configuration_parameter
+ value
+
+
+ The SET clause causes the specified configuration
+ parameter to be set to the specified value when the procedure is
+ entered, and then restored to its prior value when the procedure exits.
+ SET FROM CURRENT saves the value of the parameter that
+ is current when CREATE PROCEDURE is executed as the value
+ to be applied when the procedure is entered.
+
+
+
+ If a SET clause is attached to a procedure, then
+ the effects of a SET LOCAL command executed inside the
+ procedure for the same variable are restricted to the procedure: the
+ configuration parameter's prior value is still restored at procedure exit.
+ However, an ordinary
+ SET command (without LOCAL) overrides the
+ SET clause, much as it would do for a previous SET
+ LOCAL command: the effects of such a command will persist after
+ procedure exit, unless the current transaction is rolled back.
+
+
+
+ See and
+
+ for more information about allowed parameter names and values.
+
+
+
+
+
+ definition
+
+
+
+ A string constant defining the procedure; the meaning depends on the
+ language. It can be an internal procedure name, the path to an
+ object file, an SQL command, or text in a procedural language.
+
+
+
+ It is often helpful to use dollar quoting (see ) to write the procedure definition
+ string, rather than the normal single quote syntax. Without dollar
+ quoting, any single quotes or backslashes in the procedure definition must
+ be escaped by doubling them.
+
+
+
+
+
+
+ obj_file, link_symbol
+
+
+
+ This form of the AS clause is used for
+ dynamically loadable C language procedures when the procedure name
+ in the C language source code is not the same as the name of
+ the SQL procedure. The string obj_file is the name of the shared
+ library file containing the compiled C procedure, and is interpreted
+ as for the command. The string
+ link_symbol is the
+ procedure's link symbol, that is, the name of the procedure in the C
+ language source code. If the link symbol is omitted, it is assumed
+ to be the same as the name of the SQL procedure being defined.
+
+
+
+ When repeated CREATE PROCEDURE calls refer to
+ the same object file, the file is only loaded once per session.
+ To unload and
+ reload the file (perhaps during development), start a new session.
+
+
+
+
+
+
+
+
+ Notes
+
+
+ See for more details on function
+ creation that also apply to procedures.
+
+
+
+ Use to execute a procedure.
+
+
+
+
+ Examples
+
+
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+AS $$
+INSERT INTO tbl VALUES (a);
+INSERT INTO tbl VALUES (b);
+$$;
+
+CALL insert_data(1, 2);
+
+
+
+
+ Compatibility
+
+
+ A CREATE PROCEDURE command is defined in the SQL
+ standard. The PostgreSQL version is similar but
+ not fully compatible. For details see
+ also .
+
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/drop_function.sgml b/doc/src/sgml/ref/drop_function.sgml
index eda1a59c84..127fdfe419 100644
--- a/doc/src/sgml/ref/drop_function.sgml
+++ b/doc/src/sgml/ref/drop_function.sgml
@@ -185,6 +185,8 @@ DROP FUNCTION update_employee_salaries();
+
+
diff --git a/doc/src/sgml/ref/drop_procedure.sgml b/doc/src/sgml/ref/drop_procedure.sgml
new file mode 100644
index 0000000000..fef61b66ac
--- /dev/null
+++ b/doc/src/sgml/ref/drop_procedure.sgml
@@ -0,0 +1,162 @@
+
+
+
+
+ DROP PROCEDURE
+
+
+
+ DROP PROCEDURE
+ 7
+ SQL - Language Statements
+
+
+
+ DROP PROCEDURE
+ remove a procedure
+
+
+
+
+DROP PROCEDURE [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...]
+ [ CASCADE | RESTRICT ]
+
+
+
+
+ Description
+
+
+ DROP PROCEDURE removes the definition of an existing
+ procedure. To execute this command the user must be the
+ owner of the procedure. The argument types to the
+ procedure must be specified, since several different procedures
+ can exist with the same name and different argument lists.
+
+
+
+
+ Parameters
+
+
+
+ IF EXISTS
+
+
+ Do not throw an error if the procedure does not exist. A notice is issued
+ in this case.
+
+
+
+
+
+ name
+
+
+ The name (optionally schema-qualified) of an existing procedure. If no
+ argument list is specified, the name must be unique in its schema.
+
+
+
+
+
+ argmode
+
+
+
+ The mode of an argument: IN or VARIADIC.
+ If omitted, the default is IN.
+
+
+
+
+
+ argname
+
+
+
+ The name of an argument.
+ Note that DROP PROCEDURE does not actually pay
+ any attention to argument names, since only the argument data
+ types are needed to determine the procedure's identity.
+
+
+
+
+
+ argtype
+
+
+
+ The data type(s) of the procedure's arguments (optionally
+ schema-qualified), if any.
+
+
+
+
+
+ CASCADE
+
+
+ Automatically drop objects that depend on the procedure,
+ and in turn all objects that depend on those objects
+ (see ).
+
+
+
+
+
+ RESTRICT
+
+
+ Refuse to drop the procedure if any objects depend on it. This
+ is the default.
+
+
+
+
+
+
+
+ Examples
+
+
+DROP PROCEDURE do_db_maintenance();
+
+
+
+
+ Compatibility
+
+
+ This command conforms to the SQL standard, with
+ these PostgreSQL extensions:
+
+
+ The standard only allows one procedure to be dropped per command.
+
+
+ The IF EXISTS option
+
+
+ The ability to specify argument modes and names
+
+
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/drop_routine.sgml b/doc/src/sgml/ref/drop_routine.sgml
new file mode 100644
index 0000000000..5cd1a0f11e
--- /dev/null
+++ b/doc/src/sgml/ref/drop_routine.sgml
@@ -0,0 +1,94 @@
+
+
+
+
+ DROP ROUTINE
+
+
+
+ DROP ROUTINE
+ 7
+ SQL - Language Statements
+
+
+
+ DROP ROUTINE
+ remove a routine
+
+
+
+
+DROP ROUTINE [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...]
+ [ CASCADE | RESTRICT ]
+
+
+
+
+ Description
+
+
+ DROP ROUTINE removes the definition of an existing
+ routine, which can be an aggregate function, a normal function, or a
+ procedure. See
+ under , ,
+ and for the description of the
+ parameters, more examples, and further details.
+
+
+
+
+ Examples
+
+
+ To drop the routine foo for type
+ integer:
+
+DROP ROUTINE foo(integer);
+
+ This command will work independent of whether foo is an
+ aggregate, function, or procedure.
+
+
+
+
+ Compatibility
+
+
+ This command conforms to the SQL standard, with
+ these PostgreSQL extensions:
+
+
+ The standard only allows one routine to be dropped per command.
+
+
+ The IF EXISTS option
+
+
+ The ability to specify argument modes and names
+
+
+ Aggregate functions are an extension.
+
+
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+ Note that there is no CREATE ROUTINE command.
+
+
+
+
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index a5e895d09d..ff64c7a3ba 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -55,8 +55,8 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
TO role_specification [, ...] [ WITH GRANT OPTION ]
GRANT { EXECUTE | ALL [ PRIVILEGES ] }
- ON { FUNCTION function_name [ ( [ [ argmode ] [ arg_name ] arg_type [, ...] ] ) ] [, ...]
- | ALL FUNCTIONS IN SCHEMA schema_name [, ...] }
+ ON { { FUNCTION | PROCEDURE | ROUTINE } routine_name [ ( [ [ argmode ] [ arg_name ] arg_type [, ...] ] ) ] [, ...]
+ | ALL { FUNCTIONS | PROCEDURES | ROUTINES } IN SCHEMA schema_name [, ...] }
TO role_specification [, ...] [ WITH GRANT OPTION ]
GRANT { USAGE | ALL [ PRIVILEGES ] }
@@ -96,7 +96,7 @@ GRANT role_name [, ...] TO
The GRANT command has two basic variants: one
that grants privileges on a database object (table, column, view, foreign
- table, sequence, database, foreign-data wrapper, foreign server, function,
+ table, sequence, database, foreign-data wrapper, foreign server, function, procedure,
procedural language, schema, or tablespace), and one that grants
membership in a role. These variants are similar in many ways, but
they are different enough to be described separately.
@@ -115,8 +115,11 @@ GRANT role_name [, ...] TO
There is also an option to grant privileges on all objects of the same
type within one or more schemas. This functionality is currently supported
- only for tables, sequences, and functions (but note that ALL
- TABLES is considered to include views and foreign tables).
+ only for tables, sequences, functions, and procedures. ALL
+ TABLES also affects views and foreign tables, just like the
+ specific-object GRANT command. ALL
+ FUNCTIONS also affects aggregate functions, but not procedures,
+ again just like the specific-object GRANT command.
@@ -169,7 +172,7 @@ GRANT role_name [, ...] TO PUBLIC are as follows:
CONNECT and TEMPORARY (create
temporary tables) privileges for databases;
- EXECUTE privilege for functions; and
+ EXECUTE privilege for functions and procedures; and
USAGE privilege for languages and data types
(including domains).
The object owner can, of course, REVOKE
@@ -329,10 +332,12 @@ GRANT role_name [, ...] TO EXECUTE
- Allows the use of the specified function and the use of any
- operators that are implemented on top of the function. This is
- the only type of privilege that is applicable to functions.
- (This syntax works for aggregate functions, as well.)
+ Allows the use of the specified function or procedure and the use of
+ any operators that are implemented on top of the function. This is the
+ only type of privilege that is applicable to functions and procedures.
+ The FUNCTION syntax also works for aggregate
+ functions. Alternatively, use ROUTINE to refer to a function,
+ aggregate function, or procedure regardless of what it is.
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 4d133a782b..7018202f14 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -70,8 +70,8 @@ REVOKE [ GRANT OPTION FOR ]
REVOKE [ GRANT OPTION FOR ]
{ EXECUTE | ALL [ PRIVILEGES ] }
- ON { FUNCTION function_name [ ( [ [ argmode ] [ arg_name ] arg_type [, ...] ] ) ] [, ...]
- | ALL FUNCTIONS IN SCHEMA schema_name [, ...] }
+ ON { { FUNCTION | PROCEDURE | ROUTINE } function_name [ ( [ [ argmode ] [ arg_name ] arg_type [, ...] ] ) ] [, ...]
+ | ALL { FUNCTIONS | PROCEDURES | ROUTINES } IN SCHEMA schema_name [, ...] }
FROM { [ GROUP ] role_name | PUBLIC } [, ...]
[ CASCADE | RESTRICT ]
diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml
index d52113e035..e9cfdec9f9 100644
--- a/doc/src/sgml/ref/security_label.sgml
+++ b/doc/src/sgml/ref/security_label.sgml
@@ -34,8 +34,10 @@ SECURITY LABEL [ FOR provider ] ON
LARGE OBJECT large_object_oid |
MATERIALIZED VIEW object_name |
[ PROCEDURAL ] LANGUAGE object_name |
+ PROCEDURE procedure_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] |
PUBLICATION object_name |
ROLE object_name |
+ ROUTINE routine_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] |
SCHEMA object_name |
SEQUENCE object_name |
SUBSCRIPTION object_name |
@@ -93,10 +95,12 @@ SECURITY LABEL [ FOR provider ] ON
table_name.column_nameaggregate_namefunction_name
+ procedure_name
+ routine_name
The name of the object to be labeled. Names of tables,
- aggregates, domains, foreign tables, functions, sequences, types, and
+ aggregates, domains, foreign tables, functions, procedures, routines, sequences, types, and
views can be schema-qualified.
@@ -119,7 +123,7 @@ SECURITY LABEL [ FOR provider ] ON
- The mode of a function or aggregate
+ The mode of a function, procedure, or aggregate
argument: IN, OUT,
INOUT, or VARIADIC.
If omitted, the default is IN.
@@ -137,7 +141,7 @@ SECURITY LABEL [ FOR provider ] ON
- The name of a function or aggregate argument.
+ The name of a function, procedure, or aggregate argument.
Note that SECURITY LABEL does not actually
pay any attention to argument names, since only the argument data
types are needed to determine the function's identity.
@@ -150,7 +154,7 @@ SECURITY LABEL [ FOR provider ] ON
- The data type of a function or aggregate argument.
+ The data type of a function, procedure, or aggregate argument.
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index d20eaa87e7..d27fb414f7 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -54,8 +54,10 @@
&alterOperatorClass;
&alterOperatorFamily;
&alterPolicy;
+ &alterProcedure;
&alterPublication;
&alterRole;
+ &alterRoutine;
&alterRule;
&alterSchema;
&alterSequence;
@@ -76,6 +78,7 @@
&alterView;
&analyze;
&begin;
+ &call;
&checkpoint;
&close;
&cluster;
@@ -103,6 +106,7 @@
&createOperatorClass;
&createOperatorFamily;
&createPolicy;
+ &createProcedure;
&createPublication;
&createRole;
&createRule;
@@ -150,8 +154,10 @@
&dropOperatorFamily;
&dropOwned;
&dropPolicy;
+ &dropProcedure;
&dropPublication;
&dropRole;
+ &dropRoutine;
&dropRule;
&dropSchema;
&dropSequence;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 508ee7a96c..bbc3766cc2 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -72,6 +72,39 @@
+
+ User-defined Procedures
+
+
+ procedure
+ user-defined
+
+
+
+ A procedure is a database object similar to a function. The difference is
+ that a procedure does not return a value, so there is no return type
+ declaration. While a function is called as part of a query or DML
+ command, a procedure is called explicitly using
+ the statement.
+
+
+
+ The explanations on how to define user-defined functions in the rest of
+ this chapter apply to procedures as well, except that
+ the command is used instead, there is
+ no return type, and some other features such as strictness don't apply.
+
+
+
+ Collectively, functions and procedures are also known
+ as routinesroutine.
+ There are commands such as
+ and that can operate on functions and
+ procedures without having to know which kind it is. Note, however, that
+ there is no CREATE ROUTINE command.
+
+
+
Query Language (SQL) Functions
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index ccde66a7dd..e481cf3d11 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -482,6 +482,14 @@ ExecuteGrantStmt(GrantStmt *stmt)
all_privileges = ACL_ALL_RIGHTS_NAMESPACE;
errormsg = gettext_noop("invalid privilege type %s for schema");
break;
+ case ACL_OBJECT_PROCEDURE:
+ all_privileges = ACL_ALL_RIGHTS_FUNCTION;
+ errormsg = gettext_noop("invalid privilege type %s for procedure");
+ break;
+ case ACL_OBJECT_ROUTINE:
+ all_privileges = ACL_ALL_RIGHTS_FUNCTION;
+ errormsg = gettext_noop("invalid privilege type %s for routine");
+ break;
case ACL_OBJECT_TABLESPACE:
all_privileges = ACL_ALL_RIGHTS_TABLESPACE;
errormsg = gettext_noop("invalid privilege type %s for tablespace");
@@ -584,6 +592,8 @@ ExecGrantStmt_oids(InternalGrant *istmt)
ExecGrant_ForeignServer(istmt);
break;
case ACL_OBJECT_FUNCTION:
+ case ACL_OBJECT_PROCEDURE:
+ case ACL_OBJECT_ROUTINE:
ExecGrant_Function(istmt);
break;
case ACL_OBJECT_LANGUAGE:
@@ -671,7 +681,7 @@ objectNamesToOids(GrantObjectType objtype, List *objnames)
ObjectWithArgs *func = (ObjectWithArgs *) lfirst(cell);
Oid funcid;
- funcid = LookupFuncWithArgs(func, false);
+ funcid = LookupFuncWithArgs(OBJECT_FUNCTION, func, false);
objects = lappend_oid(objects, funcid);
}
break;
@@ -709,6 +719,26 @@ objectNamesToOids(GrantObjectType objtype, List *objnames)
objects = lappend_oid(objects, oid);
}
break;
+ case ACL_OBJECT_PROCEDURE:
+ foreach(cell, objnames)
+ {
+ ObjectWithArgs *func = (ObjectWithArgs *) lfirst(cell);
+ Oid procid;
+
+ procid = LookupFuncWithArgs(OBJECT_PROCEDURE, func, false);
+ objects = lappend_oid(objects, procid);
+ }
+ break;
+ case ACL_OBJECT_ROUTINE:
+ foreach(cell, objnames)
+ {
+ ObjectWithArgs *func = (ObjectWithArgs *) lfirst(cell);
+ Oid routid;
+
+ routid = LookupFuncWithArgs(OBJECT_ROUTINE, func, false);
+ objects = lappend_oid(objects, routid);
+ }
+ break;
case ACL_OBJECT_TABLESPACE:
foreach(cell, objnames)
{
@@ -785,19 +815,39 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
objects = list_concat(objects, objs);
break;
case ACL_OBJECT_FUNCTION:
+ case ACL_OBJECT_PROCEDURE:
+ case ACL_OBJECT_ROUTINE:
{
- ScanKeyData key[1];
+ ScanKeyData key[2];
+ int keycount;
Relation rel;
HeapScanDesc scan;
HeapTuple tuple;
- ScanKeyInit(&key[0],
+ keycount = 0;
+ ScanKeyInit(&key[keycount++],
Anum_pg_proc_pronamespace,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(namespaceId));
+ /*
+ * When looking for functions, check for return type <>0.
+ * When looking for procedures, check for return type ==0.
+ * When looking for routines, don't check the return type.
+ */
+ if (objtype == ACL_OBJECT_FUNCTION)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_proc_prorettype,
+ BTEqualStrategyNumber, F_OIDNE,
+ InvalidOid);
+ else if (objtype == ACL_OBJECT_PROCEDURE)
+ ScanKeyInit(&key[keycount++],
+ Anum_pg_proc_prorettype,
+ BTEqualStrategyNumber, F_OIDEQ,
+ InvalidOid);
+
rel = heap_open(ProcedureRelationId, AccessShareLock);
- scan = heap_beginscan_catalog(rel, 1, key);
+ scan = heap_beginscan_catalog(rel, keycount, key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
@@ -955,6 +1005,14 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
all_privileges = ACL_ALL_RIGHTS_FUNCTION;
errormsg = gettext_noop("invalid privilege type %s for function");
break;
+ case ACL_OBJECT_PROCEDURE:
+ all_privileges = ACL_ALL_RIGHTS_FUNCTION;
+ errormsg = gettext_noop("invalid privilege type %s for procedure");
+ break;
+ case ACL_OBJECT_ROUTINE:
+ all_privileges = ACL_ALL_RIGHTS_FUNCTION;
+ errormsg = gettext_noop("invalid privilege type %s for routine");
+ break;
case ACL_OBJECT_TYPE:
all_privileges = ACL_ALL_RIGHTS_TYPE;
errormsg = gettext_noop("invalid privilege type %s for type");
@@ -1423,7 +1481,7 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
istmt.objtype = ACL_OBJECT_TYPE;
break;
case ProcedureRelationId:
- istmt.objtype = ACL_OBJECT_FUNCTION;
+ istmt.objtype = ACL_OBJECT_ROUTINE;
break;
case LanguageRelationId:
istmt.objtype = ACL_OBJECT_LANGUAGE;
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 236f6be37e..360725d59a 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -1413,7 +1413,8 @@ CREATE VIEW routines AS
CAST(current_database() AS sql_identifier) AS routine_catalog,
CAST(n.nspname AS sql_identifier) AS routine_schema,
CAST(p.proname AS sql_identifier) AS routine_name,
- CAST('FUNCTION' AS character_data) AS routine_type,
+ CAST(CASE WHEN p.prorettype <> 0 THEN 'FUNCTION' ELSE 'PROCEDURE' END
+ AS character_data) AS routine_type,
CAST(null AS sql_identifier) AS module_catalog,
CAST(null AS sql_identifier) AS module_schema,
CAST(null AS sql_identifier) AS module_name,
@@ -1422,7 +1423,8 @@ CREATE VIEW routines AS
CAST(null AS sql_identifier) AS udt_name,
CAST(
- CASE WHEN t.typelem <> 0 AND t.typlen = -1 THEN 'ARRAY'
+ CASE WHEN p.prorettype = 0 THEN NULL
+ WHEN t.typelem <> 0 AND t.typlen = -1 THEN 'ARRAY'
WHEN nt.nspname = 'pg_catalog' THEN format_type(t.oid, null)
ELSE 'USER-DEFINED' END AS character_data)
AS data_type,
@@ -1440,7 +1442,7 @@ CREATE VIEW routines AS
CAST(null AS cardinal_number) AS datetime_precision,
CAST(null AS character_data) AS interval_type,
CAST(null AS cardinal_number) AS interval_precision,
- CAST(current_database() AS sql_identifier) AS type_udt_catalog,
+ CAST(CASE WHEN p.prorettype <> 0 THEN current_database() END AS sql_identifier) AS type_udt_catalog,
CAST(nt.nspname AS sql_identifier) AS type_udt_schema,
CAST(t.typname AS sql_identifier) AS type_udt_name,
CAST(null AS sql_identifier) AS scope_catalog,
@@ -1462,7 +1464,8 @@ CREATE VIEW routines AS
CAST('GENERAL' AS character_data) AS parameter_style,
CAST(CASE WHEN p.provolatile = 'i' THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_deterministic,
CAST('MODIFIES' AS character_data) AS sql_data_access,
- CAST(CASE WHEN p.proisstrict THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_null_call,
+ CAST(CASE WHEN p.prorettype <> 0 THEN
+ CASE WHEN p.proisstrict THEN 'YES' ELSE 'NO' END END AS yes_or_no) AS is_null_call,
CAST(null AS character_data) AS sql_path,
CAST('YES' AS yes_or_no) AS schema_level_routine,
CAST(0 AS cardinal_number) AS max_dynamic_result_sets,
@@ -1503,13 +1506,15 @@ CREATE VIEW routines AS
CAST(null AS cardinal_number) AS result_cast_maximum_cardinality,
CAST(null AS sql_identifier) AS result_cast_dtd_identifier
- FROM pg_namespace n, pg_proc p, pg_language l,
- pg_type t, pg_namespace nt
+ FROM (pg_namespace n
+ JOIN pg_proc p ON n.oid = p.pronamespace
+ JOIN pg_language l ON p.prolang = l.oid)
+ LEFT JOIN
+ (pg_type t JOIN pg_namespace nt ON t.typnamespace = nt.oid)
+ ON p.prorettype = t.oid
- WHERE n.oid = p.pronamespace AND p.prolang = l.oid
- AND p.prorettype = t.oid AND t.typnamespace = nt.oid
- AND (pg_has_role(p.proowner, 'USAGE')
- OR has_function_privilege(p.oid, 'EXECUTE'));
+ WHERE (pg_has_role(p.proowner, 'USAGE')
+ OR has_function_privilege(p.oid, 'EXECUTE'));
GRANT SELECT ON routines TO PUBLIC;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8d55c76fc4..9553675975 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -566,6 +566,9 @@ static const struct object_type_map
{
"function", OBJECT_FUNCTION
},
+ {
+ "procedure", OBJECT_PROCEDURE
+ },
/* OCLASS_TYPE */
{
"type", OBJECT_TYPE
@@ -884,13 +887,11 @@ get_object_address(ObjectType objtype, Node *object,
address = get_object_address_type(objtype, castNode(TypeName, object), missing_ok);
break;
case OBJECT_AGGREGATE:
- address.classId = ProcedureRelationId;
- address.objectId = LookupAggWithArgs(castNode(ObjectWithArgs, object), missing_ok);
- address.objectSubId = 0;
- break;
case OBJECT_FUNCTION:
+ case OBJECT_PROCEDURE:
+ case OBJECT_ROUTINE:
address.classId = ProcedureRelationId;
- address.objectId = LookupFuncWithArgs(castNode(ObjectWithArgs, object), missing_ok);
+ address.objectId = LookupFuncWithArgs(objtype, castNode(ObjectWithArgs, object), missing_ok);
address.objectSubId = 0;
break;
case OBJECT_OPERATOR:
@@ -2025,6 +2026,8 @@ pg_get_object_address(PG_FUNCTION_ARGS)
*/
if (type == OBJECT_AGGREGATE ||
type == OBJECT_FUNCTION ||
+ type == OBJECT_PROCEDURE ||
+ type == OBJECT_ROUTINE ||
type == OBJECT_OPERATOR ||
type == OBJECT_CAST ||
type == OBJECT_AMOP ||
@@ -2168,6 +2171,8 @@ pg_get_object_address(PG_FUNCTION_ARGS)
objnode = (Node *) list_make2(name, args);
break;
case OBJECT_FUNCTION:
+ case OBJECT_PROCEDURE:
+ case OBJECT_ROUTINE:
case OBJECT_AGGREGATE:
case OBJECT_OPERATOR:
{
@@ -2253,6 +2258,8 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
break;
case OBJECT_AGGREGATE:
case OBJECT_FUNCTION:
+ case OBJECT_PROCEDURE:
+ case OBJECT_ROUTINE:
if (!pg_proc_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
NameListToString((castNode(ObjectWithArgs, object))->objname));
@@ -4026,6 +4033,8 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
if (procForm->proisagg)
appendStringInfoString(buffer, "aggregate");
+ else if (procForm->prorettype == InvalidOid)
+ appendStringInfoString(buffer, "procedure");
else
appendStringInfoString(buffer, "function");
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 47916cfb54..7d05e4bdb2 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -857,7 +857,8 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Disallow pseudotype result */
/* except for RECORD, VOID, or polymorphic */
- if (get_typtype(proc->prorettype) == TYPTYPE_PSEUDO &&
+ if (proc->prorettype &&
+ get_typtype(proc->prorettype) == TYPTYPE_PSEUDO &&
proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID &&
!IsPolymorphicType(proc->prorettype))
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index adc9877e79..2e2ee883e2 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -307,7 +307,7 @@ DefineAggregate(ParseState *pstate, List *name, List *args, bool oldstyle, List
interpret_function_parameter_list(pstate,
args,
InvalidOid,
- true, /* is an aggregate */
+ OBJECT_AGGREGATE,
¶meterTypes,
&allParameterTypes,
¶meterModes,
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 4f8147907c..21e3f1efe1 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -378,6 +378,8 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_OPCLASS:
case OBJECT_OPFAMILY:
case OBJECT_LANGUAGE:
+ case OBJECT_PROCEDURE:
+ case OBJECT_ROUTINE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TSCONFIGURATION:
case OBJECT_TSDICTIONARY:
@@ -495,6 +497,8 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
case OBJECT_OPERATOR:
case OBJECT_OPCLASS:
case OBJECT_OPFAMILY:
+ case OBJECT_PROCEDURE:
+ case OBJECT_ROUTINE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TSCONFIGURATION:
case OBJECT_TSDICTIONARY:
@@ -842,6 +846,8 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
case OBJECT_OPERATOR:
case OBJECT_OPCLASS:
case OBJECT_OPFAMILY:
+ case OBJECT_PROCEDURE:
+ case OBJECT_ROUTINE:
case OBJECT_STATISTIC_EXT:
case OBJECT_TABLESPACE:
case OBJECT_TSDICTIONARY:
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 2b30677d6f..7e6baa1928 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -26,6 +26,7 @@
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -91,21 +92,12 @@ RemoveObjects(DropStmt *stmt)
*/
if (stmt->removeType == OBJECT_FUNCTION)
{
- Oid funcOid = address.objectId;
- HeapTuple tup;
-
- tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
- if (!HeapTupleIsValid(tup)) /* should not happen */
- elog(ERROR, "cache lookup failed for function %u", funcOid);
-
- if (((Form_pg_proc) GETSTRUCT(tup))->proisagg)
+ if (get_func_isagg(address.objectId))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an aggregate function",
NameListToString(castNode(ObjectWithArgs, object)->objname)),
errhint("Use DROP AGGREGATE to drop aggregate functions.")));
-
- ReleaseSysCache(tup);
}
/* Check permissions. */
@@ -338,6 +330,32 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
}
break;
}
+ case OBJECT_PROCEDURE:
+ {
+ ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
+
+ if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
+ !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
+ {
+ msg = gettext_noop("procedure %s(%s) does not exist, skipping");
+ name = NameListToString(owa->objname);
+ args = TypeNameListToString(owa->objargs);
+ }
+ break;
+ }
+ case OBJECT_ROUTINE:
+ {
+ ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
+
+ if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
+ !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
+ {
+ msg = gettext_noop("routine %s(%s) does not exist, skipping");
+ name = NameListToString(owa->objname);
+ args = TypeNameListToString(owa->objargs);
+ }
+ break;
+ }
case OBJECT_AGGREGATE:
{
ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index fa7d0d015a..a602c20b41 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -106,8 +106,10 @@ static event_trigger_support_data event_trigger_support[] = {
{"OPERATOR CLASS", true},
{"OPERATOR FAMILY", true},
{"POLICY", true},
+ {"PROCEDURE", true},
{"PUBLICATION", true},
{"ROLE", false},
+ {"ROUTINE", true},
{"RULE", true},
{"SCHEMA", true},
{"SEQUENCE", true},
@@ -1103,8 +1105,10 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
+ case OBJECT_PROCEDURE:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
+ case OBJECT_ROUTINE:
case OBJECT_RULE:
case OBJECT_SCHEMA:
case OBJECT_SEQUENCE:
@@ -1215,6 +1219,8 @@ EventTriggerSupportsGrantObjectType(GrantObjectType objtype)
case ACL_OBJECT_LANGUAGE:
case ACL_OBJECT_LARGEOBJECT:
case ACL_OBJECT_NAMESPACE:
+ case ACL_OBJECT_PROCEDURE:
+ case ACL_OBJECT_ROUTINE:
case ACL_OBJECT_TYPE:
return true;
@@ -2243,6 +2249,10 @@ stringify_grantobjtype(GrantObjectType objtype)
return "LARGE OBJECT";
case ACL_OBJECT_NAMESPACE:
return "SCHEMA";
+ case ACL_OBJECT_PROCEDURE:
+ return "PROCEDURE";
+ case ACL_OBJECT_ROUTINE:
+ return "ROUTINE";
case ACL_OBJECT_TABLESPACE:
return "TABLESPACE";
case ACL_OBJECT_TYPE:
@@ -2285,6 +2295,10 @@ stringify_adefprivs_objtype(GrantObjectType objtype)
return "LARGE OBJECTS";
case ACL_OBJECT_NAMESPACE:
return "SCHEMAS";
+ case ACL_OBJECT_PROCEDURE:
+ return "PROCEDURES";
+ case ACL_OBJECT_ROUTINE:
+ return "ROUTINES";
case ACL_OBJECT_TABLESPACE:
return "TABLESPACES";
case ACL_OBJECT_TYPE:
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7de844b2ca..2a9c90133d 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -51,6 +51,8 @@
#include "commands/alter.h"
#include "commands/defrem.h"
#include "commands/proclang.h"
+#include "executor/execdesc.h"
+#include "executor/executor.h"
#include "miscadmin.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
@@ -179,7 +181,7 @@ void
interpret_function_parameter_list(ParseState *pstate,
List *parameters,
Oid languageOid,
- bool is_aggregate,
+ ObjectType objtype,
oidvector **parameterTypes,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
@@ -233,7 +235,7 @@ interpret_function_parameter_list(ParseState *pstate,
errmsg("SQL function cannot accept shell type %s",
TypeNameToString(t))));
/* We don't allow creating aggregates on shell types either */
- else if (is_aggregate)
+ else if (objtype == OBJECT_AGGREGATE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("aggregate cannot accept shell type %s",
@@ -262,16 +264,28 @@ interpret_function_parameter_list(ParseState *pstate,
if (t->setof)
{
- if (is_aggregate)
+ if (objtype == OBJECT_AGGREGATE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("aggregates cannot accept set arguments")));
+ else if (objtype == OBJECT_PROCEDURE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("procedures cannot accept set arguments")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("functions cannot accept set arguments")));
}
+ if (objtype == OBJECT_PROCEDURE)
+ {
+ if (fp->mode == FUNC_PARAM_OUT || fp->mode == FUNC_PARAM_INOUT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ (errmsg("procedures cannot have OUT parameters"))));
+ }
+
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
{
@@ -451,6 +465,7 @@ interpret_function_parameter_list(ParseState *pstate,
*/
static bool
compute_common_attribute(ParseState *pstate,
+ bool is_procedure,
DefElem *defel,
DefElem **volatility_item,
DefElem **strict_item,
@@ -463,6 +478,8 @@ compute_common_attribute(ParseState *pstate,
{
if (strcmp(defel->defname, "volatility") == 0)
{
+ if (is_procedure)
+ goto procedure_error;
if (*volatility_item)
goto duplicate_error;
@@ -470,6 +487,8 @@ compute_common_attribute(ParseState *pstate,
}
else if (strcmp(defel->defname, "strict") == 0)
{
+ if (is_procedure)
+ goto procedure_error;
if (*strict_item)
goto duplicate_error;
@@ -484,6 +503,8 @@ compute_common_attribute(ParseState *pstate,
}
else if (strcmp(defel->defname, "leakproof") == 0)
{
+ if (is_procedure)
+ goto procedure_error;
if (*leakproof_item)
goto duplicate_error;
@@ -495,6 +516,8 @@ compute_common_attribute(ParseState *pstate,
}
else if (strcmp(defel->defname, "cost") == 0)
{
+ if (is_procedure)
+ goto procedure_error;
if (*cost_item)
goto duplicate_error;
@@ -502,6 +525,8 @@ compute_common_attribute(ParseState *pstate,
}
else if (strcmp(defel->defname, "rows") == 0)
{
+ if (is_procedure)
+ goto procedure_error;
if (*rows_item)
goto duplicate_error;
@@ -509,6 +534,8 @@ compute_common_attribute(ParseState *pstate,
}
else if (strcmp(defel->defname, "parallel") == 0)
{
+ if (is_procedure)
+ goto procedure_error;
if (*parallel_item)
goto duplicate_error;
@@ -526,6 +553,13 @@ duplicate_error:
errmsg("conflicting or redundant options"),
parser_errposition(pstate, defel->location)));
return false; /* keep compiler quiet */
+
+procedure_error:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid attribute in procedure definition"),
+ parser_errposition(pstate, defel->location)));
+ return false;
}
static char
@@ -603,6 +637,7 @@ update_proconfig_value(ArrayType *a, List *set_items)
*/
static void
compute_attributes_sql_style(ParseState *pstate,
+ bool is_procedure,
List *options,
List **as,
char **language,
@@ -669,9 +704,15 @@ compute_attributes_sql_style(ParseState *pstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
parser_errposition(pstate, defel->location)));
+ if (is_procedure)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid attribute in procedure definition"),
+ parser_errposition(pstate, defel->location)));
windowfunc_item = defel;
}
else if (compute_common_attribute(pstate,
+ is_procedure,
defel,
&volatility_item,
&strict_item,
@@ -762,7 +803,7 @@ compute_attributes_sql_style(ParseState *pstate,
*------------
*/
static void
-compute_attributes_with_style(ParseState *pstate, List *parameters, bool *isStrict_p, char *volatility_p)
+compute_attributes_with_style(ParseState *pstate, bool is_procedure, List *parameters, bool *isStrict_p, char *volatility_p)
{
ListCell *pl;
@@ -771,10 +812,22 @@ compute_attributes_with_style(ParseState *pstate, List *parameters, bool *isStri
DefElem *param = (DefElem *) lfirst(pl);
if (pg_strcasecmp(param->defname, "isstrict") == 0)
+ {
+ if (is_procedure)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid attribute in procedure definition"),
+ parser_errposition(pstate, param->location)));
*isStrict_p = defGetBoolean(param);
+ }
else if (pg_strcasecmp(param->defname, "iscachable") == 0)
{
/* obsolete spelling of isImmutable */
+ if (is_procedure)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid attribute in procedure definition"),
+ parser_errposition(pstate, param->location)));
if (defGetBoolean(param))
*volatility_p = PROVOLATILE_IMMUTABLE;
}
@@ -916,6 +969,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
/* override attributes from explicit list */
compute_attributes_sql_style(pstate,
+ stmt->is_procedure,
stmt->options,
&as_clause, &language, &transformDefElem,
&isWindowFunc, &volatility,
@@ -990,7 +1044,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
interpret_function_parameter_list(pstate,
stmt->parameters,
languageOid,
- false, /* not an aggregate */
+ stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
&allParameterTypes,
¶meterModes,
@@ -999,7 +1053,14 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
&variadicArgType,
&requiredResultType);
- if (stmt->returnType)
+ if (stmt->is_procedure)
+ {
+ Assert(!stmt->returnType);
+
+ prorettype = InvalidOid;
+ returnsSet = false;
+ }
+ else if (stmt->returnType)
{
/* explicit RETURNS clause */
compute_return_type(stmt->returnType, languageOid,
@@ -1045,7 +1106,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- compute_attributes_with_style(pstate, stmt->withClause, &isStrict, &volatility);
+ compute_attributes_with_style(pstate, stmt->is_procedure, stmt->withClause, &isStrict, &volatility);
interpret_AS_clause(languageOid, language, funcname, as_clause,
&prosrc_str, &probin_str);
@@ -1168,6 +1229,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
HeapTuple tup;
Oid funcOid;
Form_pg_proc procForm;
+ bool is_procedure;
Relation rel;
ListCell *l;
DefElem *volatility_item = NULL;
@@ -1182,7 +1244,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
rel = heap_open(ProcedureRelationId, RowExclusiveLock);
- funcOid = LookupFuncWithArgs(stmt->func, false);
+ funcOid = LookupFuncWithArgs(stmt->objtype, stmt->func, false);
tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
if (!HeapTupleIsValid(tup)) /* should not happen */
@@ -1201,12 +1263,15 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
errmsg("\"%s\" is an aggregate function",
NameListToString(stmt->func->objname))));
+ is_procedure = (procForm->prorettype == InvalidOid);
+
/* Examine requested actions. */
foreach(l, stmt->actions)
{
DefElem *defel = (DefElem *) lfirst(l);
if (compute_common_attribute(pstate,
+ is_procedure,
defel,
&volatility_item,
&strict_item,
@@ -1472,7 +1537,7 @@ CreateCast(CreateCastStmt *stmt)
{
Form_pg_proc procstruct;
- funcid = LookupFuncWithArgs(stmt->func, false);
+ funcid = LookupFuncWithArgs(OBJECT_FUNCTION, stmt->func, false);
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(tuple))
@@ -1853,7 +1918,7 @@ CreateTransform(CreateTransformStmt *stmt)
*/
if (stmt->fromsql)
{
- fromsqlfuncid = LookupFuncWithArgs(stmt->fromsql, false);
+ fromsqlfuncid = LookupFuncWithArgs(OBJECT_FUNCTION, stmt->fromsql, false);
if (!pg_proc_ownercheck(fromsqlfuncid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->fromsql->objname));
@@ -1879,7 +1944,7 @@ CreateTransform(CreateTransformStmt *stmt)
if (stmt->tosql)
{
- tosqlfuncid = LookupFuncWithArgs(stmt->tosql, false);
+ tosqlfuncid = LookupFuncWithArgs(OBJECT_FUNCTION, stmt->tosql, false);
if (!pg_proc_ownercheck(tosqlfuncid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->tosql->objname));
@@ -2168,3 +2233,80 @@ ExecuteDoStmt(DoStmt *stmt)
/* execute the inline handler */
OidFunctionCall1(laninline, PointerGetDatum(codeblock));
}
+
+/*
+ * Execute CALL statement
+ */
+void
+ExecuteCallStmt(ParseState *pstate, CallStmt *stmt)
+{
+ List *targs;
+ ListCell *lc;
+ Node *node;
+ FuncExpr *fexpr;
+ int nargs;
+ int i;
+ AclResult aclresult;
+ FmgrInfo flinfo;
+ FunctionCallInfoData fcinfo;
+
+ targs = NIL;
+ foreach(lc, stmt->funccall->args)
+ {
+ targs = lappend(targs, transformExpr(pstate,
+ (Node *) lfirst(lc),
+ EXPR_KIND_CALL));
+ }
+
+ node = ParseFuncOrColumn(pstate,
+ stmt->funccall->funcname,
+ targs,
+ pstate->p_last_srf,
+ stmt->funccall,
+ true,
+ stmt->funccall->location);
+
+ fexpr = castNode(FuncExpr, node);
+
+ aclresult = pg_proc_aclcheck(fexpr->funcid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(fexpr->funcid));
+ InvokeFunctionExecuteHook(fexpr->funcid);
+
+ nargs = list_length(fexpr->args);
+
+ /* safety check; see ExecInitFunc() */
+ if (nargs > FUNC_MAX_ARGS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("cannot pass more than %d argument to a procedure",
+ "cannot pass more than %d arguments to a procedure",
+ FUNC_MAX_ARGS,
+ FUNC_MAX_ARGS)));
+
+ fmgr_info(fexpr->funcid, &flinfo);
+ InitFunctionCallInfoData(fcinfo, &flinfo, nargs, fexpr->inputcollid, NULL, NULL);
+
+ i = 0;
+ foreach (lc, fexpr->args)
+ {
+ EState *estate;
+ ExprState *exprstate;
+ ExprContext *econtext;
+ Datum val;
+ bool isnull;
+
+ estate = CreateExecutorState();
+ exprstate = ExecPrepareExpr(lfirst(lc), estate);
+ econtext = CreateStandaloneExprContext();
+ val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull);
+ FreeExecutorState(estate);
+
+ fcinfo.arg[i] = val;
+ fcinfo.argnull[i] = isnull;
+
+ i++;
+ }
+
+ FunctionCallInvoke(&fcinfo);
+}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 1641e68abe..35c7c67bf5 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -520,7 +520,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
errmsg("invalid procedure number %d,"
" must be between 1 and %d",
item->number, maxProcNumber)));
- funcOid = LookupFuncWithArgs(item->name, false);
+ funcOid = LookupFuncWithArgs(OBJECT_FUNCTION, item->name, false);
#ifdef NOT_USED
/* XXX this is unnecessary given the superuser check above */
/* Caller must own function */
@@ -894,7 +894,7 @@ AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid,
errmsg("invalid procedure number %d,"
" must be between 1 and %d",
item->number, maxProcNumber)));
- funcOid = LookupFuncWithArgs(item->name, false);
+ funcOid = LookupFuncWithArgs(OBJECT_FUNCTION, item->name, false);
#ifdef NOT_USED
/* XXX this is unnecessary given the superuser check above */
/* Caller must own function */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 98eb777421..3caa343723 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -390,6 +390,7 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(param),
pstate->p_last_srf,
NULL,
+ false,
cref->location);
}
@@ -658,7 +659,8 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
fcache->rettype = rettype;
/* Fetch the typlen and byval info for the result type */
- get_typlenbyval(rettype, &fcache->typlen, &fcache->typbyval);
+ if (rettype)
+ get_typlenbyval(rettype, &fcache->typlen, &fcache->typbyval);
/* Remember whether we're returning setof something */
fcache->returnsSet = procedureStruct->proretset;
@@ -1321,8 +1323,8 @@ fmgr_sql(PG_FUNCTION_ARGS)
}
else
{
- /* Should only get here for VOID functions */
- Assert(fcache->rettype == VOIDOID);
+ /* Should only get here for procedures and VOID functions */
+ Assert(fcache->rettype == InvalidOid || fcache->rettype == VOIDOID);
fcinfo->isnull = true;
result = (Datum) 0;
}
@@ -1546,7 +1548,10 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
if (modifyTargetList)
*modifyTargetList = false; /* initialize for no change */
if (junkFilter)
- *junkFilter = NULL; /* initialize in case of VOID result */
+ *junkFilter = NULL; /* initialize in case of procedure/VOID result */
+
+ if (!rettype)
+ return false;
/*
* Find the last canSetTag query in the list. This isn't necessarily the
@@ -1591,7 +1596,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
else
{
/* Empty function body, or last statement is a utility command */
- if (rettype != VOIDOID)
+ if (rettype && rettype != VOIDOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d9ff8a7e51..aff9a62106 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3210,6 +3210,16 @@ _copyClosePortalStmt(const ClosePortalStmt *from)
return newnode;
}
+static CallStmt *
+_copyCallStmt(const CallStmt *from)
+{
+ CallStmt *newnode = makeNode(CallStmt);
+
+ COPY_NODE_FIELD(funccall);
+
+ return newnode;
+}
+
static ClusterStmt *
_copyClusterStmt(const ClusterStmt *from)
{
@@ -3411,6 +3421,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(funcname);
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
+ COPY_SCALAR_FIELD(is_procedure);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(withClause);
@@ -3435,6 +3446,7 @@ _copyAlterFunctionStmt(const AlterFunctionStmt *from)
{
AlterFunctionStmt *newnode = makeNode(AlterFunctionStmt);
+ COPY_SCALAR_FIELD(objtype);
COPY_NODE_FIELD(func);
COPY_NODE_FIELD(actions);
@@ -5104,6 +5116,9 @@ copyObjectImpl(const void *from)
case T_ClosePortalStmt:
retval = _copyClosePortalStmt(from);
break;
+ case T_CallStmt:
+ retval = _copyCallStmt(from);
+ break;
case T_ClusterStmt:
retval = _copyClusterStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2866fd7b4a..2e869a9d5d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1201,6 +1201,14 @@ _equalClosePortalStmt(const ClosePortalStmt *a, const ClosePortalStmt *b)
return true;
}
+static bool
+_equalCallStmt(const CallStmt *a, const CallStmt *b)
+{
+ COMPARE_NODE_FIELD(funccall);
+
+ return true;
+}
+
static bool
_equalClusterStmt(const ClusterStmt *a, const ClusterStmt *b)
{
@@ -1364,6 +1372,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(funcname);
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
+ COMPARE_SCALAR_FIELD(is_procedure);
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(withClause);
@@ -1384,6 +1393,7 @@ _equalFunctionParameter(const FunctionParameter *a, const FunctionParameter *b)
static bool
_equalAlterFunctionStmt(const AlterFunctionStmt *a, const AlterFunctionStmt *b)
{
+ COMPARE_SCALAR_FIELD(objtype);
COMPARE_NODE_FIELD(func);
COMPARE_NODE_FIELD(actions);
@@ -3246,6 +3256,9 @@ equal(const void *a, const void *b)
case T_ClosePortalStmt:
retval = _equalClosePortalStmt(a, b);
break;
+ case T_CallStmt:
+ retval = _equalCallStmt(a, b);
+ break;
case T_ClusterStmt:
retval = _equalClusterStmt(a, b);
break;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e5e2956564..6a2d5ad760 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4401,6 +4401,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
if (funcform->prolang != SQLlanguageId ||
funcform->prosecdef ||
funcform->proretset ||
+ funcform->prorettype == InvalidOid ||
funcform->prorettype == RECORDOID ||
!heap_attisnull(func_tuple, Anum_pg_proc_proconfig) ||
funcform->pronargs != list_length(args))
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c301ca465d..ebfc94f896 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -253,7 +253,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
AlterCompositeTypeStmt AlterUserMappingStmt
AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt
AlterDefaultPrivilegesStmt DefACLAction
- AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
+ AnalyzeStmt CallStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
@@ -611,7 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
- CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
+ CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
@@ -660,14 +660,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
- PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM PUBLICATION
+ PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
QUOTE
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
- ROW ROWS RULE
+ ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
@@ -845,6 +845,7 @@ stmt :
| AlterTSDictionaryStmt
| AlterUserMappingStmt
| AnalyzeStmt
+ | CallStmt
| CheckPointStmt
| ClosePortalStmt
| ClusterStmt
@@ -940,6 +941,20 @@ stmt :
{ $$ = NULL; }
;
+/*****************************************************************************
+ *
+ * CALL statement
+ *
+ *****************************************************************************/
+
+CallStmt: CALL func_application
+ {
+ CallStmt *n = makeNode(CallStmt);
+ n->funccall = castNode(FuncCall, $2);
+ $$ = (Node *)n;
+ }
+ ;
+
/*****************************************************************************
*
* Create a new Postgres DBMS role
@@ -4554,6 +4569,24 @@ AlterExtensionContentsStmt:
n->object = (Node *) lcons(makeString($9), $7);
$$ = (Node *)n;
}
+ | ALTER EXTENSION name add_drop PROCEDURE function_with_argtypes
+ {
+ AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
+ n->extname = $3;
+ n->action = $4;
+ n->objtype = OBJECT_PROCEDURE;
+ n->object = (Node *) $6;
+ $$ = (Node *)n;
+ }
+ | ALTER EXTENSION name add_drop ROUTINE function_with_argtypes
+ {
+ AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
+ n->extname = $3;
+ n->action = $4;
+ n->objtype = OBJECT_ROUTINE;
+ n->object = (Node *) $6;
+ $$ = (Node *)n;
+ }
| ALTER EXTENSION name add_drop SCHEMA name
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
@@ -6436,6 +6469,22 @@ CommentStmt:
n->comment = $8;
$$ = (Node *) n;
}
+ | COMMENT ON PROCEDURE function_with_argtypes IS comment_text
+ {
+ CommentStmt *n = makeNode(CommentStmt);
+ n->objtype = OBJECT_PROCEDURE;
+ n->object = (Node *) $4;
+ n->comment = $6;
+ $$ = (Node *) n;
+ }
+ | COMMENT ON ROUTINE function_with_argtypes IS comment_text
+ {
+ CommentStmt *n = makeNode(CommentStmt);
+ n->objtype = OBJECT_ROUTINE;
+ n->object = (Node *) $4;
+ n->comment = $6;
+ $$ = (Node *) n;
+ }
| COMMENT ON RULE name ON any_name IS comment_text
{
CommentStmt *n = makeNode(CommentStmt);
@@ -6614,6 +6663,26 @@ SecLabelStmt:
n->label = $9;
$$ = (Node *) n;
}
+ | SECURITY LABEL opt_provider ON PROCEDURE function_with_argtypes
+ IS security_label
+ {
+ SecLabelStmt *n = makeNode(SecLabelStmt);
+ n->provider = $3;
+ n->objtype = OBJECT_PROCEDURE;
+ n->object = (Node *) $6;
+ n->label = $8;
+ $$ = (Node *) n;
+ }
+ | SECURITY LABEL opt_provider ON ROUTINE function_with_argtypes
+ IS security_label
+ {
+ SecLabelStmt *n = makeNode(SecLabelStmt);
+ n->provider = $3;
+ n->objtype = OBJECT_ROUTINE;
+ n->object = (Node *) $6;
+ n->label = $8;
+ $$ = (Node *) n;
+ }
;
opt_provider: FOR NonReservedWord_or_Sconst { $$ = $2; }
@@ -6977,6 +7046,22 @@ privilege_target:
n->objs = $2;
$$ = n;
}
+ | PROCEDURE function_with_argtypes_list
+ {
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+ n->targtype = ACL_TARGET_OBJECT;
+ n->objtype = ACL_OBJECT_PROCEDURE;
+ n->objs = $2;
+ $$ = n;
+ }
+ | ROUTINE function_with_argtypes_list
+ {
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+ n->targtype = ACL_TARGET_OBJECT;
+ n->objtype = ACL_OBJECT_ROUTINE;
+ n->objs = $2;
+ $$ = n;
+ }
| DATABASE name_list
{
PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7057,6 +7142,22 @@ privilege_target:
n->objs = $5;
$$ = n;
}
+ | ALL PROCEDURES IN_P SCHEMA name_list
+ {
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+ n->targtype = ACL_TARGET_ALL_IN_SCHEMA;
+ n->objtype = ACL_OBJECT_PROCEDURE;
+ n->objs = $5;
+ $$ = n;
+ }
+ | ALL ROUTINES IN_P SCHEMA name_list
+ {
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+ n->targtype = ACL_TARGET_ALL_IN_SCHEMA;
+ n->objtype = ACL_OBJECT_ROUTINE;
+ n->objs = $5;
+ $$ = n;
+ }
;
@@ -7213,6 +7314,7 @@ DefACLAction:
defacl_privilege_target:
TABLES { $$ = ACL_OBJECT_RELATION; }
| FUNCTIONS { $$ = ACL_OBJECT_FUNCTION; }
+ | ROUTINES { $$ = ACL_OBJECT_FUNCTION; }
| SEQUENCES { $$ = ACL_OBJECT_SEQUENCE; }
| TYPES_P { $$ = ACL_OBJECT_TYPE; }
| SCHEMAS { $$ = ACL_OBJECT_NAMESPACE; }
@@ -7413,6 +7515,18 @@ CreateFunctionStmt:
n->withClause = $7;
$$ = (Node *)n;
}
+ | CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
+ createfunc_opt_list
+ {
+ CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
+ n->replace = $2;
+ n->funcname = $4;
+ n->parameters = $5;
+ n->returnType = NULL;
+ n->is_procedure = true;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
;
opt_or_replace:
@@ -7830,7 +7944,7 @@ table_func_column_list:
;
/*****************************************************************************
- * ALTER FUNCTION
+ * ALTER FUNCTION / ALTER PROCEDURE / ALTER ROUTINE
*
* RENAME and OWNER subcommands are already provided by the generic
* ALTER infrastructure, here we just specify alterations that can
@@ -7841,6 +7955,23 @@ AlterFunctionStmt:
ALTER FUNCTION function_with_argtypes alterfunc_opt_list opt_restrict
{
AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
+ n->objtype = OBJECT_FUNCTION;
+ n->func = $3;
+ n->actions = $4;
+ $$ = (Node *) n;
+ }
+ | ALTER PROCEDURE function_with_argtypes alterfunc_opt_list opt_restrict
+ {
+ AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
+ n->objtype = OBJECT_PROCEDURE;
+ n->func = $3;
+ n->actions = $4;
+ $$ = (Node *) n;
+ }
+ | ALTER ROUTINE function_with_argtypes alterfunc_opt_list opt_restrict
+ {
+ AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
+ n->objtype = OBJECT_ROUTINE;
n->func = $3;
n->actions = $4;
$$ = (Node *) n;
@@ -7865,6 +7996,8 @@ opt_restrict:
* QUERY:
*
* DROP FUNCTION funcname (arg1, arg2, ...) [ RESTRICT | CASCADE ]
+ * DROP PROCEDURE procname (arg1, arg2, ...) [ RESTRICT | CASCADE ]
+ * DROP ROUTINE routname (arg1, arg2, ...) [ RESTRICT | CASCADE ]
* DROP AGGREGATE aggname (arg1, ...) [ RESTRICT | CASCADE ]
* DROP OPERATOR opname (leftoperand_typ, rightoperand_typ) [ RESTRICT | CASCADE ]
*
@@ -7891,6 +8024,46 @@ RemoveFuncStmt:
n->concurrent = false;
$$ = (Node *)n;
}
+ | DROP PROCEDURE function_with_argtypes_list opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_PROCEDURE;
+ n->objects = $3;
+ n->behavior = $4;
+ n->missing_ok = false;
+ n->concurrent = false;
+ $$ = (Node *)n;
+ }
+ | DROP PROCEDURE IF_P EXISTS function_with_argtypes_list opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_PROCEDURE;
+ n->objects = $5;
+ n->behavior = $6;
+ n->missing_ok = true;
+ n->concurrent = false;
+ $$ = (Node *)n;
+ }
+ | DROP ROUTINE function_with_argtypes_list opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_ROUTINE;
+ n->objects = $3;
+ n->behavior = $4;
+ n->missing_ok = false;
+ n->concurrent = false;
+ $$ = (Node *)n;
+ }
+ | DROP ROUTINE IF_P EXISTS function_with_argtypes_list opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_ROUTINE;
+ n->objects = $5;
+ n->behavior = $6;
+ n->missing_ok = true;
+ n->concurrent = false;
+ $$ = (Node *)n;
+ }
;
RemoveAggrStmt:
@@ -8348,6 +8521,15 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER PROCEDURE function_with_argtypes RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_PROCEDURE;
+ n->object = (Node *) $3;
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
| ALTER PUBLICATION name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -8357,6 +8539,15 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER ROUTINE function_with_argtypes RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_ROUTINE;
+ n->object = (Node *) $3;
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
| ALTER SCHEMA name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -8736,6 +8927,22 @@ AlterObjectDependsStmt:
n->extname = makeString($7);
$$ = (Node *)n;
}
+ | ALTER PROCEDURE function_with_argtypes DEPENDS ON EXTENSION name
+ {
+ AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
+ n->objectType = OBJECT_PROCEDURE;
+ n->object = (Node *) $3;
+ n->extname = makeString($7);
+ $$ = (Node *)n;
+ }
+ | ALTER ROUTINE function_with_argtypes DEPENDS ON EXTENSION name
+ {
+ AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
+ n->objectType = OBJECT_ROUTINE;
+ n->object = (Node *) $3;
+ n->extname = makeString($7);
+ $$ = (Node *)n;
+ }
| ALTER TRIGGER name ON qualified_name DEPENDS ON EXTENSION name
{
AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
@@ -8851,6 +9058,24 @@ AlterObjectSchemaStmt:
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER PROCEDURE function_with_argtypes SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_PROCEDURE;
+ n->object = (Node *) $3;
+ n->newschema = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER ROUTINE function_with_argtypes SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_ROUTINE;
+ n->object = (Node *) $3;
+ n->newschema = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
| ALTER TABLE relation_expr SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
@@ -9126,6 +9351,22 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
n->newowner = $9;
$$ = (Node *)n;
}
+ | ALTER PROCEDURE function_with_argtypes OWNER TO RoleSpec
+ {
+ AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+ n->objectType = OBJECT_PROCEDURE;
+ n->object = (Node *) $3;
+ n->newowner = $6;
+ $$ = (Node *)n;
+ }
+ | ALTER ROUTINE function_with_argtypes OWNER TO RoleSpec
+ {
+ AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+ n->objectType = OBJECT_ROUTINE;
+ n->object = (Node *) $3;
+ n->newowner = $6;
+ $$ = (Node *)n;
+ }
| ALTER SCHEMA name OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
@@ -14689,6 +14930,7 @@ unreserved_keyword:
| BEGIN_P
| BY
| CACHE
+ | CALL
| CALLED
| CASCADE
| CASCADED
@@ -14848,6 +15090,7 @@ unreserved_keyword:
| PRIVILEGES
| PROCEDURAL
| PROCEDURE
+ | PROCEDURES
| PROGRAM
| PUBLICATION
| QUOTE
@@ -14874,6 +15117,8 @@ unreserved_keyword:
| ROLE
| ROLLBACK
| ROLLUP
+ | ROUTINE
+ | ROUTINES
| ROWS
| RULE
| SAVEPOINT
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 64111f315e..4c4f4cdc3d 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -508,6 +508,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
+ case EXPR_KIND_CALL:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in CALL arguments");
+ else
+ err = _("grouping operations are not allowed in CALL arguments");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
@@ -883,6 +891,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_EXPRESSION:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_CALL:
+ err = _("window functions are not allowed in CALL arguments");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 86d1da0677..29f9da796f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -480,6 +480,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
list_make1(result),
last_srf,
NULL,
+ false,
location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
@@ -629,6 +630,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(node),
pstate->p_last_srf,
NULL,
+ false,
cref->location);
}
break;
@@ -676,6 +678,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(node),
pstate->p_last_srf,
NULL,
+ false,
cref->location);
}
break;
@@ -736,6 +739,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(node),
pstate->p_last_srf,
NULL,
+ false,
cref->location);
}
break;
@@ -1477,6 +1481,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
targs,
last_srf,
fn,
+ false,
fn->location);
}
@@ -1812,6 +1817,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
case EXPR_KIND_VALUES_SINGLE:
+ case EXPR_KIND_CALL:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3462,6 +3468,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_EXPRESSION:
return "PARTITION BY";
+ case EXPR_KIND_CALL:
+ return "CALL";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index a11843332b..2f20516e76 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -71,7 +71,7 @@ static Node *ParseComplexProjection(ParseState *pstate, const char *funcname,
*/
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
- Node *last_srf, FuncCall *fn, int location)
+ Node *last_srf, FuncCall *fn, bool proc_call, int location)
{
bool is_column = (fn == NULL);
List *agg_order = (fn ? fn->agg_order : NIL);
@@ -263,7 +263,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
actual_arg_types[0], rettype, -1,
COERCION_EXPLICIT, COERCE_EXPLICIT_CALL, location);
}
- else if (fdresult == FUNCDETAIL_NORMAL)
+ else if (fdresult == FUNCDETAIL_NORMAL || fdresult == FUNCDETAIL_PROCEDURE)
{
/*
* Normal function found; was there anything indicating it must be an
@@ -306,6 +306,26 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("OVER specified, but %s is not a window function nor an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+
+ if (fdresult == FUNCDETAIL_NORMAL && proc_call)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("%s is not a procedure",
+ func_signature_string(funcname, nargs,
+ argnames,
+ actual_arg_types)),
+ errhint("To call a function, use SELECT."),
+ parser_errposition(pstate, location)));
+
+ if (fdresult == FUNCDETAIL_PROCEDURE && !proc_call)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("%s is a procedure",
+ func_signature_string(funcname, nargs,
+ argnames,
+ actual_arg_types)),
+ errhint("To call a procedure, use CALL."),
+ parser_errposition(pstate, location)));
}
else if (fdresult == FUNCDETAIL_AGGREGATE)
{
@@ -635,7 +655,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
check_srf_call_placement(pstate, last_srf, location);
/* build the appropriate output structure */
- if (fdresult == FUNCDETAIL_NORMAL)
+ if (fdresult == FUNCDETAIL_NORMAL || fdresult == FUNCDETAIL_PROCEDURE)
{
FuncExpr *funcexpr = makeNode(FuncExpr);
@@ -1589,6 +1609,8 @@ func_get_detail(List *funcname,
result = FUNCDETAIL_AGGREGATE;
else if (pform->proiswindow)
result = FUNCDETAIL_WINDOWFUNC;
+ else if (pform->prorettype == InvalidOid)
+ result = FUNCDETAIL_PROCEDURE;
else
result = FUNCDETAIL_NORMAL;
ReleaseSysCache(ftup);
@@ -1984,16 +2006,28 @@ LookupFuncName(List *funcname, int nargs, const Oid *argtypes, bool noError)
/*
* LookupFuncWithArgs
- * Like LookupFuncName, but the argument types are specified by a
- * ObjectWithArgs node.
+ *
+ * Like LookupFuncName, but the argument types are specified by a
+ * ObjectWithArgs node. Also, this function can check whether the result is a
+ * function, procedure, or aggregate, based on the objtype argument. Pass
+ * OBJECT_ROUTINE to accept any of them.
+ *
+ * For historical reasons, we also accept aggregates when looking for a
+ * function.
*/
Oid
-LookupFuncWithArgs(ObjectWithArgs *func, bool noError)
+LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, bool noError)
{
Oid argoids[FUNC_MAX_ARGS];
int argcount;
int i;
ListCell *args_item;
+ Oid oid;
+
+ Assert(objtype == OBJECT_AGGREGATE ||
+ objtype == OBJECT_FUNCTION ||
+ objtype == OBJECT_PROCEDURE ||
+ objtype == OBJECT_ROUTINE);
argcount = list_length(func->objargs);
if (argcount > FUNC_MAX_ARGS)
@@ -2013,90 +2047,100 @@ LookupFuncWithArgs(ObjectWithArgs *func, bool noError)
args_item = lnext(args_item);
}
- return LookupFuncName(func->objname, func->args_unspecified ? -1 : argcount, argoids, noError);
-}
+ /*
+ * When looking for a function or routine, we pass noError through to
+ * LookupFuncName and let it make any error messages. Otherwise, we make
+ * our own errors for the aggregate and procedure cases.
+ */
+ oid = LookupFuncName(func->objname, func->args_unspecified ? -1 : argcount, argoids,
+ (objtype == OBJECT_FUNCTION || objtype == OBJECT_ROUTINE) ? noError : true);
-/*
- * LookupAggWithArgs
- * Find an aggregate function from a given ObjectWithArgs node.
- *
- * This is almost like LookupFuncWithArgs, but the error messages refer
- * to aggregates rather than plain functions, and we verify that the found
- * function really is an aggregate.
- */
-Oid
-LookupAggWithArgs(ObjectWithArgs *agg, bool noError)
-{
- Oid argoids[FUNC_MAX_ARGS];
- int argcount;
- int i;
- ListCell *lc;
- Oid oid;
- HeapTuple ftup;
- Form_pg_proc pform;
-
- argcount = list_length(agg->objargs);
- if (argcount > FUNC_MAX_ARGS)
- ereport(ERROR,
- (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
- errmsg_plural("functions cannot have more than %d argument",
- "functions cannot have more than %d arguments",
- FUNC_MAX_ARGS,
- FUNC_MAX_ARGS)));
-
- i = 0;
- foreach(lc, agg->objargs)
+ if (objtype == OBJECT_FUNCTION)
{
- TypeName *t = (TypeName *) lfirst(lc);
-
- argoids[i] = LookupTypeNameOid(NULL, t, noError);
- i++;
- }
-
- oid = LookupFuncName(agg->objname, argcount, argoids, true);
-
- if (!OidIsValid(oid))
- {
- if (noError)
- return InvalidOid;
- if (argcount == 0)
+ /* Make sure it's a function, not a procedure */
+ if (oid && get_func_rettype(oid) == InvalidOid)
+ {
+ if (noError)
+ return InvalidOid;
ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_FUNCTION),
- errmsg("aggregate %s(*) does not exist",
- NameListToString(agg->objname))));
- else
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_FUNCTION),
- errmsg("aggregate %s does not exist",
- func_signature_string(agg->objname, argcount,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is not a function",
+ func_signature_string(func->objname, argcount,
NIL, argoids))));
+ }
}
-
- /* Make sure it's an aggregate */
- ftup = SearchSysCache1(PROCOID, ObjectIdGetDatum(oid));
- if (!HeapTupleIsValid(ftup)) /* should not happen */
- elog(ERROR, "cache lookup failed for function %u", oid);
- pform = (Form_pg_proc) GETSTRUCT(ftup);
-
- if (!pform->proisagg)
+ else if (objtype == OBJECT_PROCEDURE)
{
- ReleaseSysCache(ftup);
- if (noError)
- return InvalidOid;
- /* we do not use the (*) notation for functions... */
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("function %s is not an aggregate",
- func_signature_string(agg->objname, argcount,
- NIL, argoids))));
- }
+ if (!OidIsValid(oid))
+ {
+ if (noError)
+ return InvalidOid;
+ else if (func->args_unspecified)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not find a procedure named \"%s\"",
+ NameListToString(func->objname))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("procedure %s does not exist",
+ func_signature_string(func->objname, argcount,
+ NIL, argoids))));
+ }
- ReleaseSysCache(ftup);
+ /* Make sure it's a procedure */
+ if (get_func_rettype(oid) != InvalidOid)
+ {
+ if (noError)
+ return InvalidOid;
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is not a procedure",
+ func_signature_string(func->objname, argcount,
+ NIL, argoids))));
+ }
+ }
+ else if (objtype == OBJECT_AGGREGATE)
+ {
+ if (!OidIsValid(oid))
+ {
+ if (noError)
+ return InvalidOid;
+ else if (func->args_unspecified)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not find a aggregate named \"%s\"",
+ NameListToString(func->objname))));
+ else if (argcount == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("aggregate %s(*) does not exist",
+ NameListToString(func->objname))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("aggregate %s does not exist",
+ func_signature_string(func->objname, argcount,
+ NIL, argoids))));
+ }
+
+ /* Make sure it's an aggregate */
+ if (!get_func_isagg(oid))
+ {
+ if (noError)
+ return InvalidOid;
+ /* we do not use the (*) notation for functions... */
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("function %s is not an aggregate",
+ func_signature_string(func->objname, argcount,
+ NIL, argoids))));
+ }
+ }
return oid;
}
-
/*
* check_srf_call_placement
* Verify that a set-returning function is called in a valid place,
@@ -2236,6 +2280,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_PARTITION_EXPRESSION:
err = _("set-returning functions are not allowed in partition key expressions");
break;
+ case EXPR_KIND_CALL:
+ err = _("set-returning functions are not allowed in CALL arguments");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 82a707af7b..4da1f8f643 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -657,6 +657,10 @@ standard_ProcessUtility(PlannedStmt *pstmt,
}
break;
+ case T_CallStmt:
+ ExecuteCallStmt(pstate, castNode(CallStmt, parsetree));
+ break;
+
case T_ClusterStmt:
/* we choose to allow this during "read only" transactions */
PreventCommandDuringRecovery("CLUSTER");
@@ -1957,9 +1961,15 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_POLICY:
tag = "ALTER POLICY";
break;
+ case OBJECT_PROCEDURE:
+ tag = "ALTER PROCEDURE";
+ break;
case OBJECT_ROLE:
tag = "ALTER ROLE";
break;
+ case OBJECT_ROUTINE:
+ tag = "ALTER ROUTINE";
+ break;
case OBJECT_RULE:
tag = "ALTER RULE";
break;
@@ -2261,6 +2271,12 @@ CreateCommandTag(Node *parsetree)
case OBJECT_FUNCTION:
tag = "DROP FUNCTION";
break;
+ case OBJECT_PROCEDURE:
+ tag = "DROP PROCEDURE";
+ break;
+ case OBJECT_ROUTINE:
+ tag = "DROP ROUTINE";
+ break;
case OBJECT_AGGREGATE:
tag = "DROP AGGREGATE";
break;
@@ -2359,7 +2375,20 @@ CreateCommandTag(Node *parsetree)
break;
case T_AlterFunctionStmt:
- tag = "ALTER FUNCTION";
+ switch (((AlterFunctionStmt *) parsetree)->objtype)
+ {
+ case OBJECT_FUNCTION:
+ tag = "ALTER FUNCTION";
+ break;
+ case OBJECT_PROCEDURE:
+ tag = "ALTER PROCEDURE";
+ break;
+ case OBJECT_ROUTINE:
+ tag = "ALTER ROUTINE";
+ break;
+ default:
+ tag = "???";
+ }
break;
case T_GrantStmt:
@@ -2438,7 +2467,10 @@ CreateCommandTag(Node *parsetree)
break;
case T_CreateFunctionStmt:
- tag = "CREATE FUNCTION";
+ if (((CreateFunctionStmt *) parsetree)->is_procedure)
+ tag = "CREATE PROCEDURE";
+ else
+ tag = "CREATE FUNCTION";
break;
case T_IndexStmt:
@@ -2493,6 +2525,10 @@ CreateCommandTag(Node *parsetree)
tag = "LOAD";
break;
+ case T_CallStmt:
+ tag = "CALL";
+ break;
+
case T_ClusterStmt:
tag = "CLUSTER";
break;
@@ -3116,6 +3152,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_ALL;
break;
+ case T_CallStmt:
+ lev = LOGSTMT_ALL;
+ break;
+
case T_ClusterStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 06cf32f5d7..8514c21c40 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2691,6 +2691,12 @@ pg_get_function_result(PG_FUNCTION_ARGS)
if (!HeapTupleIsValid(proctup))
PG_RETURN_NULL();
+ if (((Form_pg_proc) GETSTRUCT(proctup))->prorettype == InvalidOid)
+ {
+ ReleaseSysCache(proctup);
+ PG_RETURN_NULL();
+ }
+
initStringInfo(&buf);
print_function_rettype(&buf, proctup);
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 0ea2f2bc54..5211360777 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1614,6 +1614,25 @@ func_parallel(Oid funcid)
return result;
}
+/*
+ * get_func_isagg
+ * Given procedure id, return the function's proisagg field.
+ */
+bool
+get_func_isagg(Oid funcid)
+{
+ HeapTuple tp;
+ bool result;
+
+ tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ result = ((Form_pg_proc) GETSTRUCT(tp))->proisagg;
+ ReleaseSysCache(tp);
+ return result;
+}
+
/*
* get_func_leakproof
* Given procedure id, return the function's leakproof field.
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 70d8f24d17..12290a1aae 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -33,7 +33,7 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
* name: the object name, in the form to use in the commands (already quoted)
* subname: the sub-object name, if any (already quoted); NULL if none
* type: the object type (as seen in GRANT command: must be one of
- * TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
+ * TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
* FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT)
* acls: the ACL string fetched from the database
* racls: the ACL string of any initial-but-now-revoked privileges
@@ -524,6 +524,9 @@ do { \
else if (strcmp(type, "FUNCTION") == 0 ||
strcmp(type, "FUNCTIONS") == 0)
CONVERT_PRIV('X', "EXECUTE");
+ else if (strcmp(type, "PROCEDURE") == 0 ||
+ strcmp(type, "PROCEDURES") == 0)
+ CONVERT_PRIV('X', "EXECUTE");
else if (strcmp(type, "LANGUAGE") == 0)
CONVERT_PRIV('U', "USAGE");
else if (strcmp(type, "SCHEMA") == 0 ||
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ec2fa8b9b9..41741aefbc 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2889,7 +2889,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, RestoreOptions *ropt)
if (ropt->indexNames.head != NULL && (!(simple_string_list_member(&ropt->indexNames, te->tag))))
return 0;
}
- else if (strcmp(te->desc, "FUNCTION") == 0)
+ else if (strcmp(te->desc, "FUNCTION") == 0 ||
+ strcmp(te->desc, "PROCEDURE") == 0)
{
if (!ropt->selFunction)
return 0;
@@ -3388,7 +3389,8 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
strcmp(type, "FUNCTION") == 0 ||
strcmp(type, "OPERATOR") == 0 ||
strcmp(type, "OPERATOR CLASS") == 0 ||
- strcmp(type, "OPERATOR FAMILY") == 0)
+ strcmp(type, "OPERATOR FAMILY") == 0 ||
+ strcmp(type, "PROCEDURE") == 0)
{
/* Chop "DROP " off the front and make a modifiable copy */
char *first = pg_strdup(te->dropStmt + 5);
@@ -3560,6 +3562,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
strcmp(te->desc, "OPERATOR") == 0 ||
strcmp(te->desc, "OPERATOR CLASS") == 0 ||
strcmp(te->desc, "OPERATOR FAMILY") == 0 ||
+ strcmp(te->desc, "PROCEDURE") == 0 ||
strcmp(te->desc, "PROCEDURAL LANGUAGE") == 0 ||
strcmp(te->desc, "SCHEMA") == 0 ||
strcmp(te->desc, "EVENT TRIGGER") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d8fb356130..e6701aaa78 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11349,6 +11349,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *funcargs;
char *funciargs;
char *funcresult;
+ bool is_procedure;
char *proallargtypes;
char *proargmodes;
char *proargnames;
@@ -11370,6 +11371,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char **argnames = NULL;
char **configitems = NULL;
int nconfigitems = 0;
+ const char *keyword;
int i;
/* Skip if not to be dumped */
@@ -11513,7 +11515,11 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs"));
- funcresult = PQgetvalue(res, 0, PQfnumber(res, "funcresult"));
+ is_procedure = PQgetisnull(res, 0, PQfnumber(res, "funcresult"));
+ if (is_procedure)
+ funcresult = NULL;
+ else
+ funcresult = PQgetvalue(res, 0, PQfnumber(res, "funcresult"));
proallargtypes = proargmodes = proargnames = NULL;
}
else
@@ -11522,6 +11528,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
proargmodes = PQgetvalue(res, 0, PQfnumber(res, "proargmodes"));
proargnames = PQgetvalue(res, 0, PQfnumber(res, "proargnames"));
funcargs = funciargs = funcresult = NULL;
+ is_procedure = false;
}
if (PQfnumber(res, "protrftypes") != -1)
protrftypes = PQgetvalue(res, 0, PQfnumber(res, "protrftypes"));
@@ -11653,22 +11660,29 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
funcsig_tag = format_function_signature(fout, finfo, false);
+ keyword = is_procedure ? "PROCEDURE" : "FUNCTION";
+
/*
* DROP must be fully qualified in case same name appears in pg_catalog
*/
- appendPQExpBuffer(delqry, "DROP FUNCTION %s.%s;\n",
+ appendPQExpBuffer(delqry, "DROP %s %s.%s;\n",
+ keyword,
fmtId(finfo->dobj.namespace->dobj.name),
funcsig);
- appendPQExpBuffer(q, "CREATE FUNCTION %s ", funcfullsig ? funcfullsig :
+ appendPQExpBuffer(q, "CREATE %s %s",
+ keyword,
+ funcfullsig ? funcfullsig :
funcsig);
- if (funcresult)
- appendPQExpBuffer(q, "RETURNS %s", funcresult);
+ if (is_procedure)
+ ;
+ else if (funcresult)
+ appendPQExpBuffer(q, " RETURNS %s", funcresult);
else
{
rettypename = getFormattedTypeName(fout, finfo->prorettype,
zeroAsOpaque);
- appendPQExpBuffer(q, "RETURNS %s%s",
+ appendPQExpBuffer(q, " RETURNS %s%s",
(proretset[0] == 't') ? "SETOF " : "",
rettypename);
free(rettypename);
@@ -11775,7 +11789,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
appendPQExpBuffer(q, "\n %s;\n", asPart->data);
- appendPQExpBuffer(labelq, "FUNCTION %s", funcsig);
+ appendPQExpBuffer(labelq, "%s %s", keyword, funcsig);
if (dopt->binary_upgrade)
binary_upgrade_extension_member(q, &finfo->dobj, labelq->data);
@@ -11786,7 +11800,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
finfo->dobj.namespace->dobj.name,
NULL,
finfo->rolname, false,
- "FUNCTION", SECTION_PRE_DATA,
+ keyword, SECTION_PRE_DATA,
q->data, delqry->data, NULL,
NULL, 0,
NULL, NULL);
@@ -11803,7 +11817,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
finfo->dobj.catId, 0, finfo->dobj.dumpId);
if (finfo->dobj.dump & DUMP_COMPONENT_ACL)
- dumpACL(fout, finfo->dobj.catId, finfo->dobj.dumpId, "FUNCTION",
+ dumpACL(fout, finfo->dobj.catId, finfo->dobj.dumpId, keyword,
funcsig, NULL, funcsig_tag,
finfo->dobj.namespace->dobj.name,
finfo->rolname, finfo->proacl, finfo->rproacl,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index fa3b56a426..7cf9bdadb2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3654,6 +3654,44 @@ qr/^\QCREATE DEFAULT CONVERSION test_conversion FOR 'LATIN1' TO 'UTF8' FROM iso8
section_data => 1,
section_post_data => 1, }, },
+ 'CREATE PROCEDURE dump_test.ptest1' => {
+ all_runs => 1,
+ create_order => 41,
+ create_sql => 'CREATE PROCEDURE dump_test.ptest1(a int)
+ LANGUAGE SQL AS $$ INSERT INTO dump_test.test_table (col1) VALUES (a) $$;',
+ regexp => qr/^
+ \QCREATE PROCEDURE ptest1(a integer)\E
+ \n\s+\QLANGUAGE sql\E
+ \n\s+AS\ \$\$\Q INSERT INTO dump_test.test_table (col1) VALUES (a) \E\$\$;
+ /xm,
+ like => {
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ only_dump_test_schema => 1,
+ pg_dumpall_dbprivs => 1,
+ schema_only => 1,
+ section_pre_data => 1,
+ test_schema_plus_blobs => 1,
+ with_oids => 1, },
+ unlike => {
+ column_inserts => 1,
+ data_only => 1,
+ exclude_dump_test_schema => 1,
+ only_dump_test_table => 1,
+ pg_dumpall_globals => 1,
+ pg_dumpall_globals_clean => 1,
+ role => 1,
+ section_data => 1,
+ section_post_data => 1, }, },
+
'CREATE TYPE dump_test.int42 populated' => {
all_runs => 1,
create_order => 42,
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 804a84a0c9..3fc69c46c0 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -353,6 +353,7 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
" CASE\n"
" WHEN p.proisagg THEN '%s'\n"
" WHEN p.proiswindow THEN '%s'\n"
+ " WHEN p.prorettype = 0 THEN '%s'\n"
" WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n"
" ELSE '%s'\n"
" END as \"%s\"",
@@ -361,8 +362,9 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
/* translator: "agg" is short for "aggregate" */
gettext_noop("agg"),
gettext_noop("window"),
+ gettext_noop("proc"),
gettext_noop("trigger"),
- gettext_noop("normal"),
+ gettext_noop("func"),
gettext_noop("Type"));
else if (pset.sversion >= 80100)
appendPQExpBuffer(&buf,
@@ -407,7 +409,7 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
/* translator: "agg" is short for "aggregate" */
gettext_noop("agg"),
gettext_noop("trigger"),
- gettext_noop("normal"),
+ gettext_noop("func"),
gettext_noop("Type"));
else
appendPQExpBuffer(&buf,
@@ -424,7 +426,7 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
/* translator: "agg" is short for "aggregate" */
gettext_noop("agg"),
gettext_noop("trigger"),
- gettext_noop("normal"),
+ gettext_noop("func"),
gettext_noop("Type"));
if (verbose)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b3e3799c13..468e50aa31 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -397,7 +397,7 @@ static const SchemaQuery Query_for_list_of_functions = {
/* catname */
"pg_catalog.pg_proc p",
/* selcondition */
- NULL,
+ "p.prorettype <> 0",
/* viscondition */
"pg_catalog.pg_function_is_visible(p.oid)",
/* namespace */
@@ -423,6 +423,36 @@ static const SchemaQuery Query_for_list_of_indexes = {
NULL
};
+static const SchemaQuery Query_for_list_of_procedures = {
+ /* catname */
+ "pg_catalog.pg_proc p",
+ /* selcondition */
+ "p.prorettype = 0",
+ /* viscondition */
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ /* namespace */
+ "p.pronamespace",
+ /* result */
+ "pg_catalog.quote_ident(p.proname)",
+ /* qualresult */
+ NULL
+};
+
+static const SchemaQuery Query_for_list_of_routines = {
+ /* catname */
+ "pg_catalog.pg_proc p",
+ /* selcondition */
+ NULL,
+ /* viscondition */
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ /* namespace */
+ "p.pronamespace",
+ /* result */
+ "pg_catalog.quote_ident(p.proname)",
+ /* qualresult */
+ NULL
+};
+
static const SchemaQuery Query_for_list_of_sequences = {
/* catname */
"pg_catalog.pg_class c",
@@ -1032,8 +1062,10 @@ static const pgsql_thing_t words_after_create[] = {
{"OWNED", NULL, NULL, THING_NO_CREATE | THING_NO_ALTER}, /* for DROP OWNED BY ... */
{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
{"POLICY", NULL, NULL},
+ {"PROCEDURE", NULL, &Query_for_list_of_procedures},
{"PUBLICATION", Query_for_list_of_publications},
{"ROLE", Query_for_list_of_roles},
+ {"ROUTINE", NULL, &Query_for_list_of_routines, THING_NO_CREATE},
{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
{"SCHEMA", Query_for_list_of_schemas},
{"SEQUENCE", NULL, &Query_for_list_of_sequences},
@@ -1407,7 +1439,7 @@ psql_completion(const char *text, int start, int end)
/* Known command-starting keywords. */
static const char *const sql_commands[] = {
- "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
+ "ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
"FETCH", "GRANT", "IMPORT", "INSERT", "LISTEN", "LOAD", "LOCK",
@@ -1520,11 +1552,11 @@ psql_completion(const char *text, int start, int end)
/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
COMPLETE_WITH_CONST("SET TABLESPACE");
- /* ALTER AGGREGATE,FUNCTION */
- else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+ /* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE */
+ else if (Matches3("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
COMPLETE_WITH_CONST("(");
- /* ALTER AGGREGATE,FUNCTION (...) */
- else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+ /* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE (...) */
+ else if (Matches4("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny))
{
if (ends_with(prev_wd, ')'))
COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -2145,6 +2177,11 @@ psql_completion(const char *text, int start, int end)
/* ROLLBACK */
else if (Matches1("ROLLBACK"))
COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
+/* CALL */
+ else if (Matches1("CALL"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
+ else if (Matches2("CALL", MatchAny))
+ COMPLETE_WITH_CONST("(");
/* CLUSTER */
else if (Matches1("CLUSTER"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
@@ -2176,6 +2213,7 @@ psql_completion(const char *text, int start, int end)
"SERVER", "INDEX", "LANGUAGE", "POLICY", "PUBLICATION", "RULE",
"SCHEMA", "SEQUENCE", "STATISTICS", "SUBSCRIPTION",
"TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
+ "PROCEDURE", "ROUTINE",
"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
@@ -2685,7 +2723,7 @@ psql_completion(const char *text, int start, int end)
"COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|PUBLICATION|SCHEMA|SEQUENCE|SERVER|SUBSCRIPTION|STATISTICS|TABLE|TYPE|VIEW",
MatchAny) ||
Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
- (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
+ (Matches4("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny) &&
ends_with(prev_wd, ')')) ||
Matches4("DROP", "EVENT", "TRIGGER", MatchAny) ||
Matches5("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
@@ -2694,9 +2732,9 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
/* help completing some of the variants */
- else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+ else if (Matches3("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
COMPLETE_WITH_CONST("(");
- else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+ else if (Matches4("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, "("))
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches2("DROP", "FOREIGN"))
COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
@@ -2893,10 +2931,12 @@ psql_completion(const char *text, int start, int end)
* objects supported.
*/
if (HeadMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
- COMPLETE_WITH_LIST5("TABLES", "SEQUENCES", "FUNCTIONS", "TYPES", "SCHEMAS");
+ COMPLETE_WITH_LIST7("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
else
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
" UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
+ " UNION SELECT 'ALL PROCEDURES IN SCHEMA'"
+ " UNION SELECT 'ALL ROUTINES IN SCHEMA'"
" UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
" UNION SELECT 'ALL TABLES IN SCHEMA'"
" UNION SELECT 'DATABASE'"
@@ -2906,6 +2946,8 @@ psql_completion(const char *text, int start, int end)
" UNION SELECT 'FUNCTION'"
" UNION SELECT 'LANGUAGE'"
" UNION SELECT 'LARGE OBJECT'"
+ " UNION SELECT 'PROCEDURE'"
+ " UNION SELECT 'ROUTINE'"
" UNION SELECT 'SCHEMA'"
" UNION SELECT 'SEQUENCE'"
" UNION SELECT 'TABLE'"
@@ -2913,7 +2955,10 @@ psql_completion(const char *text, int start, int end)
" UNION SELECT 'TYPE'");
}
else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
- COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
+ COMPLETE_WITH_LIST5("FUNCTIONS IN SCHEMA",
+ "PROCEDURES IN SCHEMA",
+ "ROUTINES IN SCHEMA",
+ "SEQUENCES IN SCHEMA",
"TABLES IN SCHEMA");
else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
@@ -2934,6 +2979,10 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
else if (TailMatches1("LANGUAGE"))
COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+ else if (TailMatches1("PROCEDURE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
+ else if (TailMatches1("ROUTINE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
else if (TailMatches1("SCHEMA"))
COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
else if (TailMatches1("SEQUENCE"))
@@ -3163,7 +3212,7 @@ psql_completion(const char *text, int start, int end)
static const char *const list_SECURITY_LABEL[] =
{"TABLE", "COLUMN", "AGGREGATE", "DATABASE", "DOMAIN",
"EVENT TRIGGER", "FOREIGN TABLE", "FUNCTION", "LARGE OBJECT",
- "MATERIALIZED VIEW", "LANGUAGE", "PUBLICATION", "ROLE", "SCHEMA",
+ "MATERIALIZED VIEW", "LANGUAGE", "PUBLICATION", "PROCEDURE", "ROLE", "ROUTINE", "SCHEMA",
"SEQUENCE", "SUBSCRIPTION", "TABLESPACE", "TYPE", "VIEW", NULL};
COMPLETE_WITH_LIST(list_SECURITY_LABEL);
@@ -3233,8 +3282,8 @@ psql_completion(const char *text, int start, int end)
/* Complete SET with "TO" */
else if (Matches2("SET", MatchAny))
COMPLETE_WITH_CONST("TO");
- /* Complete ALTER DATABASE|FUNCTION|ROLE|USER ... SET */
- else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|ROLE|USER") &&
+ /* Complete ALTER DATABASE|FUNCTION||PROCEDURE|ROLE|ROUTINE|USER ... SET */
+ else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|PROCEDURE|ROLE|ROUTINE|USER") &&
TailMatches2("SET", MatchAny))
COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
/* Suggest possible variable values */
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index a30ce6b81d..b13cf62bec 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201711171
+#define CATALOG_VERSION_NO 201711301
#endif
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index bfead9af3d..52cbf61ccb 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -59,12 +59,13 @@ extern void DropTransformById(Oid transformOid);
extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
oidvector *proargtypes, Oid nspOid);
extern void ExecuteDoStmt(DoStmt *stmt);
+extern void ExecuteCallStmt(ParseState *pstate, CallStmt *stmt);
extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok);
extern Oid get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok);
extern void interpret_function_parameter_list(ParseState *pstate,
List *parameters,
Oid languageOid,
- bool is_aggregate,
+ ObjectType objtype,
oidvector **parameterTypes,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 03dc5307e8..c5b5115f5b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -414,6 +414,7 @@ typedef enum NodeTag
T_DropSubscriptionStmt,
T_CreateStatsStmt,
T_AlterCollationStmt,
+ T_CallStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 34d6afc80f..2eaa6b2774 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1642,9 +1642,11 @@ typedef enum ObjectType
OBJECT_OPERATOR,
OBJECT_OPFAMILY,
OBJECT_POLICY,
+ OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
OBJECT_ROLE,
+ OBJECT_ROUTINE,
OBJECT_RULE,
OBJECT_SCHEMA,
OBJECT_SEQUENCE,
@@ -1856,6 +1858,8 @@ typedef enum GrantObjectType
ACL_OBJECT_LANGUAGE, /* procedural language */
ACL_OBJECT_LARGEOBJECT, /* largeobject */
ACL_OBJECT_NAMESPACE, /* namespace */
+ ACL_OBJECT_PROCEDURE, /* procedure */
+ ACL_OBJECT_ROUTINE, /* routine */
ACL_OBJECT_TABLESPACE, /* tablespace */
ACL_OBJECT_TYPE /* type */
} GrantObjectType;
@@ -2749,6 +2753,7 @@ typedef struct CreateFunctionStmt
List *funcname; /* qualified name of function to create */
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
+ bool is_procedure;
List *options; /* a list of DefElem */
List *withClause; /* a list of DefElem */
} CreateFunctionStmt;
@@ -2775,6 +2780,7 @@ typedef struct FunctionParameter
typedef struct AlterFunctionStmt
{
NodeTag type;
+ ObjectType objtype;
ObjectWithArgs *func; /* name and args of function */
List *actions; /* list of DefElem */
} AlterFunctionStmt;
@@ -2799,6 +2805,16 @@ typedef struct InlineCodeBlock
bool langIsTrusted; /* trusted property of the language */
} InlineCodeBlock;
+/* ----------------------
+ * CALL statement
+ * ----------------------
+ */
+typedef struct CallStmt
+{
+ NodeTag type;
+ FuncCall *funccall;
+} CallStmt;
+
/* ----------------------
* Alter Object Rename Statement
* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f50e45e886..a932400058 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -63,6 +63,7 @@ PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD)
PG_KEYWORD("both", BOTH, RESERVED_KEYWORD)
PG_KEYWORD("by", BY, UNRESERVED_KEYWORD)
PG_KEYWORD("cache", CACHE, UNRESERVED_KEYWORD)
+PG_KEYWORD("call", CALL, UNRESERVED_KEYWORD)
PG_KEYWORD("called", CALLED, UNRESERVED_KEYWORD)
PG_KEYWORD("cascade", CASCADE, UNRESERVED_KEYWORD)
PG_KEYWORD("cascaded", CASCADED, UNRESERVED_KEYWORD)
@@ -310,6 +311,7 @@ PG_KEYWORD("prior", PRIOR, UNRESERVED_KEYWORD)
PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
+PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD)
PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD)
PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
@@ -340,6 +342,8 @@ PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD)
PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD)
PG_KEYWORD("rollup", ROLLUP, UNRESERVED_KEYWORD)
+PG_KEYWORD("routine", ROUTINE, UNRESERVED_KEYWORD)
+PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index b4b6084b1b..fccccd21ed 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -24,6 +24,7 @@ typedef enum
FUNCDETAIL_NOTFOUND, /* no matching function */
FUNCDETAIL_MULTIPLE, /* too many matching functions */
FUNCDETAIL_NORMAL, /* found a matching regular function */
+ FUNCDETAIL_PROCEDURE, /* found a matching procedure */
FUNCDETAIL_AGGREGATE, /* found a matching aggregate function */
FUNCDETAIL_WINDOWFUNC, /* found a matching window function */
FUNCDETAIL_COERCION /* it's a type coercion request */
@@ -31,7 +32,8 @@ typedef enum
extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
- Node *last_srf, FuncCall *fn, int location);
+ Node *last_srf, FuncCall *fn, bool proc_call,
+ int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
@@ -62,10 +64,8 @@ extern const char *func_signature_string(List *funcname, int nargs,
extern Oid LookupFuncName(List *funcname, int nargs, const Oid *argtypes,
bool noError);
-extern Oid LookupFuncWithArgs(ObjectWithArgs *func,
+extern Oid LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func,
bool noError);
-extern Oid LookupAggWithArgs(ObjectWithArgs *agg,
- bool noError);
extern void check_srf_call_placement(ParseState *pstate, Node *last_srf,
int location);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f0e210ad8d..565bb3dc6c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -67,7 +67,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
+ EXPR_KIND_PARTITION_EXPRESSION, /* PARTITION BY expression */
+ EXPR_KIND_CALL /* CALL argument */
} ParseExprKind;
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 07208b56ce..b316cc594c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -118,6 +118,7 @@ extern bool get_func_retset(Oid funcid);
extern bool func_strict(Oid funcid);
extern char func_volatile(Oid funcid);
extern char func_parallel(Oid funcid);
+extern bool get_func_isagg(Oid funcid);
extern bool get_func_leakproof(Oid funcid);
extern float4 get_func_cost(Oid funcid);
extern float4 get_func_rows(Oid funcid);
diff --git a/src/interfaces/ecpg/preproc/ecpg.tokens b/src/interfaces/ecpg/preproc/ecpg.tokens
index 68ba925efe..1d613af02f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.tokens
+++ b/src/interfaces/ecpg/preproc/ecpg.tokens
@@ -2,7 +2,7 @@
/* special embedded SQL tokens */
%token SQL_ALLOCATE SQL_AUTOCOMMIT SQL_BOOL SQL_BREAK
- SQL_CALL SQL_CARDINALITY SQL_CONNECT
+ SQL_CARDINALITY SQL_CONNECT
SQL_COUNT
SQL_DATETIME_INTERVAL_CODE
SQL_DATETIME_INTERVAL_PRECISION SQL_DESCRIBE
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index f60a62099d..19dc781885 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -1460,13 +1460,13 @@ action : CONTINUE_P
$$.command = NULL;
$$.str = mm_strdup("continue");
}
- | SQL_CALL name '(' c_args ')'
+ | CALL name '(' c_args ')'
{
$$.code = W_DO;
$$.command = cat_str(4, $2, mm_strdup("("), $4, mm_strdup(")"));
$$.str = cat2_str(mm_strdup("call"), mm_strdup($$.command));
}
- | SQL_CALL name
+ | CALL name
{
$$.code = W_DO;
$$.command = cat2_str($2, mm_strdup("()"));
@@ -1482,7 +1482,6 @@ ECPGKeywords: ECPGKeywords_vanames { $$ = $1; }
;
ECPGKeywords_vanames: SQL_BREAK { $$ = mm_strdup("break"); }
- | SQL_CALL { $$ = mm_strdup("call"); }
| SQL_CARDINALITY { $$ = mm_strdup("cardinality"); }
| SQL_COUNT { $$ = mm_strdup("count"); }
| SQL_DATETIME_INTERVAL_CODE { $$ = mm_strdup("datetime_interval_code"); }
diff --git a/src/interfaces/ecpg/preproc/ecpg_keywords.c b/src/interfaces/ecpg/preproc/ecpg_keywords.c
index 3b52b8f3a2..848b2d4849 100644
--- a/src/interfaces/ecpg/preproc/ecpg_keywords.c
+++ b/src/interfaces/ecpg/preproc/ecpg_keywords.c
@@ -33,7 +33,6 @@ static const ScanKeyword ECPGScanKeywords[] = {
{"autocommit", SQL_AUTOCOMMIT, 0},
{"bool", SQL_BOOL, 0},
{"break", SQL_BREAK, 0},
- {"call", SQL_CALL, 0},
{"cardinality", SQL_CARDINALITY, 0},
{"connect", SQL_CONNECT, 0},
{"count", SQL_COUNT, 0},
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index 91d1296b21..b829027d05 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -55,7 +55,7 @@ endif # win32
SHLIB_LINK = $(perl_embed_ldflags)
REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=plperl --load-extension=plperlu
-REGRESS = plperl plperl_lc plperl_trigger plperl_shared plperl_elog plperl_util plperl_init plperlu plperl_array
+REGRESS = plperl plperl_lc plperl_trigger plperl_shared plperl_elog plperl_util plperl_init plperlu plperl_array plperl_call
# if Perl can support two interpreters in one backend,
# test plperl-and-plperlu cases
ifneq ($(PERL),)
diff --git a/src/pl/plperl/expected/plperl_call.out b/src/pl/plperl/expected/plperl_call.out
new file mode 100644
index 0000000000..4bccfcb7c8
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_call.out
@@ -0,0 +1,29 @@
+CREATE PROCEDURE test_proc1()
+LANGUAGE plperl
+AS $$
+undef;
+$$;
+CALL test_proc1();
+CREATE PROCEDURE test_proc2()
+LANGUAGE plperl
+AS $$
+return 5
+$$;
+CALL test_proc2();
+CREATE TABLE test1 (a int);
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plperl
+AS $$
+spi_exec_query("INSERT INTO test1 VALUES ($_[0])");
+$$;
+CALL test_proc3(55);
+SELECT * FROM test1;
+ a
+----
+ 55
+(1 row)
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+DROP TABLE test1;
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index a57393fbdd..9f5313235f 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -1915,7 +1915,7 @@ plperl_inline_handler(PG_FUNCTION_ARGS)
desc.fn_retistuple = false;
desc.fn_retisset = false;
desc.fn_retisarray = false;
- desc.result_oid = VOIDOID;
+ desc.result_oid = InvalidOid;
desc.nargs = 0;
desc.reference = NULL;
@@ -2481,7 +2481,7 @@ plperl_func_handler(PG_FUNCTION_ARGS)
}
retval = (Datum) 0;
}
- else
+ else if (prodesc->result_oid)
{
retval = plperl_sv_to_datum(perlret,
prodesc->result_oid,
@@ -2826,7 +2826,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
* Get the required information for input conversion of the
* return value.
************************************************************/
- if (!is_trigger && !is_event_trigger)
+ if (!is_trigger && !is_event_trigger && procStruct->prorettype)
{
Oid rettype = procStruct->prorettype;
@@ -3343,7 +3343,7 @@ plperl_return_next_internal(SV *sv)
tuplestore_puttuple(current_call_data->tuple_store, tuple);
}
- else
+ else if (prodesc->result_oid)
{
Datum ret[1];
bool isNull[1];
diff --git a/src/pl/plperl/sql/plperl_call.sql b/src/pl/plperl/sql/plperl_call.sql
new file mode 100644
index 0000000000..bd2b63b418
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_call.sql
@@ -0,0 +1,36 @@
+CREATE PROCEDURE test_proc1()
+LANGUAGE plperl
+AS $$
+undef;
+$$;
+
+CALL test_proc1();
+
+
+CREATE PROCEDURE test_proc2()
+LANGUAGE plperl
+AS $$
+return 5
+$$;
+
+CALL test_proc2();
+
+
+CREATE TABLE test1 (a int);
+
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plperl
+AS $$
+spi_exec_query("INSERT INTO test1 VALUES ($_[0])");
+$$;
+
+CALL test_proc3(55);
+
+SELECT * FROM test1;
+
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+
+DROP TABLE test1;
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index d0afa59242..f459c02f7b 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -275,7 +275,6 @@ do_compile(FunctionCallInfo fcinfo,
bool isnull;
char *proc_source;
HeapTuple typeTup;
- Form_pg_type typeStruct;
PLpgSQL_variable *var;
PLpgSQL_rec *rec;
int i;
@@ -531,53 +530,58 @@ do_compile(FunctionCallInfo fcinfo,
/*
* Lookup the function's return type
*/
- typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettypeid));
- if (!HeapTupleIsValid(typeTup))
- elog(ERROR, "cache lookup failed for type %u", rettypeid);
- typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
-
- /* Disallow pseudotype result, except VOID or RECORD */
- /* (note we already replaced polymorphic types) */
- if (typeStruct->typtype == TYPTYPE_PSEUDO)
+ if (rettypeid)
{
- if (rettypeid == VOIDOID ||
- rettypeid == RECORDOID)
- /* okay */ ;
- else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("trigger functions can only be called as triggers")));
- else
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PL/pgSQL functions cannot return type %s",
- format_type_be(rettypeid))));
- }
+ Form_pg_type typeStruct;
- if (typeStruct->typrelid != InvalidOid ||
- rettypeid == RECORDOID)
- function->fn_retistuple = true;
- else
- {
- function->fn_retbyval = typeStruct->typbyval;
- function->fn_rettyplen = typeStruct->typlen;
+ typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettypeid));
+ if (!HeapTupleIsValid(typeTup))
+ elog(ERROR, "cache lookup failed for type %u", rettypeid);
+ typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
- /*
- * install $0 reference, but only for polymorphic return
- * types, and not when the return is specified through an
- * output parameter.
- */
- if (IsPolymorphicType(procStruct->prorettype) &&
- num_out_args == 0)
+ /* Disallow pseudotype result, except VOID or RECORD */
+ /* (note we already replaced polymorphic types) */
+ if (typeStruct->typtype == TYPTYPE_PSEUDO)
{
- (void) plpgsql_build_variable("$0", 0,
- build_datatype(typeTup,
- -1,
- function->fn_input_collation),
- true);
+ if (rettypeid == VOIDOID ||
+ rettypeid == RECORDOID)
+ /* okay */ ;
+ else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("trigger functions can only be called as triggers")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/pgSQL functions cannot return type %s",
+ format_type_be(rettypeid))));
}
+
+ if (typeStruct->typrelid != InvalidOid ||
+ rettypeid == RECORDOID)
+ function->fn_retistuple = true;
+ else
+ {
+ function->fn_retbyval = typeStruct->typbyval;
+ function->fn_rettyplen = typeStruct->typlen;
+
+ /*
+ * install $0 reference, but only for polymorphic return
+ * types, and not when the return is specified through an
+ * output parameter.
+ */
+ if (IsPolymorphicType(procStruct->prorettype) &&
+ num_out_args == 0)
+ {
+ (void) plpgsql_build_variable("$0", 0,
+ build_datatype(typeTup,
+ -1,
+ function->fn_input_collation),
+ true);
+ }
+ }
+ ReleaseSysCache(typeTup);
}
- ReleaseSysCache(typeTup);
break;
case PLPGSQL_DML_TRIGGER:
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index cf6120eea9..ec480cb0ba 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -462,7 +462,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
estate.err_text = NULL;
estate.err_stmt = (PLpgSQL_stmt *) (func->action);
rc = exec_stmt_block(&estate, func->action);
- if (rc != PLPGSQL_RC_RETURN)
+ if (rc != PLPGSQL_RC_RETURN && func->fn_rettype)
{
estate.err_stmt = NULL;
estate.err_text = NULL;
@@ -509,6 +509,12 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
}
else if (!estate.retisnull)
{
+ if (!func->fn_rettype)
+ {
+ ereport(ERROR,
+ (errmsg("cannot return a value from a procedure")));
+ }
+
if (estate.retistuple)
{
/*
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 7680d49cb6..cc91afebde 100644
--- a/src/pl/plpython/Makefile
+++ b/src/pl/plpython/Makefile
@@ -78,6 +78,7 @@ REGRESS = \
plpython_spi \
plpython_newline \
plpython_void \
+ plpython_call \
plpython_params \
plpython_setof \
plpython_record \
diff --git a/src/pl/plpython/expected/plpython_call.out b/src/pl/plpython/expected/plpython_call.out
new file mode 100644
index 0000000000..90785343b6
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_call.out
@@ -0,0 +1,35 @@
+--
+-- Tests for procedures / CALL syntax
+--
+CREATE PROCEDURE test_proc1()
+LANGUAGE plpythonu
+AS $$
+pass
+$$;
+CALL test_proc1();
+-- error: can't return non-None
+CREATE PROCEDURE test_proc2()
+LANGUAGE plpythonu
+AS $$
+return 5
+$$;
+CALL test_proc2();
+ERROR: PL/Python procedure did not return None
+CONTEXT: PL/Python procedure "test_proc2"
+CREATE TABLE test1 (a int);
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plpythonu
+AS $$
+plpy.execute("INSERT INTO test1 VALUES (%s)" % x)
+$$;
+CALL test_proc3(55);
+SELECT * FROM test1;
+ a
+----
+ 55
+(1 row)
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+DROP TABLE test1;
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index fe217c6a2c..4594a08ead 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -199,12 +199,19 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
error_context_stack = &plerrcontext;
/*
- * If the function is declared to return void, the Python return value
+ * For a procedure or function declared to return void, the Python return value
* must be None. For void-returning functions, we also treat a None
* return value as a special "void datum" rather than NULL (as is the
* case for non-void-returning functions).
*/
- if (proc->result.typoid == VOIDOID)
+ if (proc->is_procedure)
+ {
+ if (plrv != Py_None)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("PL/Python procedure did not return None")));
+ }
+ else if (proc->result.typoid == VOIDOID)
{
if (plrv != Py_None)
ereport(ERROR,
@@ -672,7 +679,8 @@ plpython_return_error_callback(void *arg)
{
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
- if (exec_ctx->curr_proc)
+ if (exec_ctx->curr_proc &&
+ !exec_ctx->curr_proc->is_procedure)
errcontext("while creating return value");
}
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 32d23ae5b6..695de30583 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -389,8 +389,14 @@ plpython_error_callback(void *arg)
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
if (exec_ctx->curr_proc)
- errcontext("PL/Python function \"%s\"",
- PLy_procedure_name(exec_ctx->curr_proc));
+ {
+ if (exec_ctx->curr_proc->is_procedure)
+ errcontext("PL/Python procedure \"%s\"",
+ PLy_procedure_name(exec_ctx->curr_proc));
+ else
+ errcontext("PL/Python function \"%s\"",
+ PLy_procedure_name(exec_ctx->curr_proc));
+ }
}
static void
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index faa4977463..b7c24e356f 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -189,6 +189,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
proc->fn_tid = procTup->t_self;
proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
proc->is_setof = procStruct->proretset;
+ proc->is_procedure = (procStruct->prorettype == InvalidOid);
proc->src = NULL;
proc->argnames = NULL;
proc->args = NULL;
@@ -206,9 +207,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
/*
* get information required for output conversion of the return value,
- * but only if this isn't a trigger.
+ * but only if this isn't a trigger or procedure.
*/
- if (!is_trigger)
+ if (!is_trigger && procStruct->prorettype)
{
Oid rettype = procStruct->prorettype;
HeapTuple rvTypeTup;
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
index cd1b87fdc3..8968b5c92e 100644
--- a/src/pl/plpython/plpy_procedure.h
+++ b/src/pl/plpython/plpy_procedure.h
@@ -30,7 +30,8 @@ typedef struct PLyProcedure
TransactionId fn_xmin;
ItemPointerData fn_tid;
bool fn_readonly;
- bool is_setof; /* true, if procedure returns result set */
+ bool is_setof; /* true, if function returns result set */
+ bool is_procedure;
PLyObToDatum result; /* Function result output conversion info */
PLyDatumToOb result_in; /* For converting input tuples in a trigger */
char *src; /* textual procedure code, after mangling */
diff --git a/src/pl/plpython/sql/plpython_call.sql b/src/pl/plpython/sql/plpython_call.sql
new file mode 100644
index 0000000000..3fb74de5f0
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_call.sql
@@ -0,0 +1,41 @@
+--
+-- Tests for procedures / CALL syntax
+--
+
+CREATE PROCEDURE test_proc1()
+LANGUAGE plpythonu
+AS $$
+pass
+$$;
+
+CALL test_proc1();
+
+
+-- error: can't return non-None
+CREATE PROCEDURE test_proc2()
+LANGUAGE plpythonu
+AS $$
+return 5
+$$;
+
+CALL test_proc2();
+
+
+CREATE TABLE test1 (a int);
+
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plpythonu
+AS $$
+plpy.execute("INSERT INTO test1 VALUES (%s)" % x)
+$$;
+
+CALL test_proc3(55);
+
+SELECT * FROM test1;
+
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+
+DROP TABLE test1;
diff --git a/src/pl/tcl/Makefile b/src/pl/tcl/Makefile
index b8971d3cc8..6a92a9b6aa 100644
--- a/src/pl/tcl/Makefile
+++ b/src/pl/tcl/Makefile
@@ -28,7 +28,7 @@ DATA = pltcl.control pltcl--1.0.sql pltcl--unpackaged--1.0.sql \
pltclu.control pltclu--1.0.sql pltclu--unpackaged--1.0.sql
REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=pltcl
-REGRESS = pltcl_setup pltcl_queries pltcl_start_proc pltcl_subxact pltcl_unicode
+REGRESS = pltcl_setup pltcl_queries pltcl_call pltcl_start_proc pltcl_subxact pltcl_unicode
# Tcl on win32 ships with import libraries only for Microsoft Visual C++,
# which are not compatible with mingw gcc. Therefore we need to build a
diff --git a/src/pl/tcl/expected/pltcl_call.out b/src/pl/tcl/expected/pltcl_call.out
new file mode 100644
index 0000000000..7221a37ad0
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_call.out
@@ -0,0 +1,29 @@
+CREATE PROCEDURE test_proc1()
+LANGUAGE pltcl
+AS $$
+unset
+$$;
+CALL test_proc1();
+CREATE PROCEDURE test_proc2()
+LANGUAGE pltcl
+AS $$
+return 5
+$$;
+CALL test_proc2();
+CREATE TABLE test1 (a int);
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE pltcl
+AS $$
+spi_exec "INSERT INTO test1 VALUES ($1)"
+$$;
+CALL test_proc3(55);
+SELECT * FROM test1;
+ a
+----
+ 55
+(1 row)
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+DROP TABLE test1;
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 6d97ddc99b..e0792d93e1 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -146,6 +146,7 @@ typedef struct pltcl_proc_desc
Oid result_typid; /* OID of fn's result type */
FmgrInfo result_in_func; /* input function for fn's result type */
Oid result_typioparam; /* param to pass to same */
+ bool fn_is_procedure;/* true if this is a procedure */
bool fn_retisset; /* true if function returns a set */
bool fn_retistuple; /* true if function returns composite */
bool fn_retisdomain; /* true if function returns domain */
@@ -968,7 +969,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
retval = (Datum) 0;
fcinfo->isnull = true;
}
- else if (fcinfo->isnull)
+ else if (fcinfo->isnull && !prodesc->fn_is_procedure)
{
retval = InputFunctionCall(&prodesc->result_in_func,
NULL,
@@ -1026,11 +1027,13 @@ pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
call_state);
retval = HeapTupleGetDatum(tup);
}
- else
+ else if (!prodesc->fn_is_procedure)
retval = InputFunctionCall(&prodesc->result_in_func,
utf_u2e(Tcl_GetStringResult(interp)),
prodesc->result_typioparam,
-1);
+ else
+ retval = 0;
return retval;
}
@@ -1506,7 +1509,9 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
* Get the required information for input conversion of the
* return value.
************************************************************/
- if (!is_trigger && !is_event_trigger)
+ prodesc->fn_is_procedure = (procStruct->prorettype == InvalidOid);
+
+ if (!is_trigger && !is_event_trigger && procStruct->prorettype)
{
Oid rettype = procStruct->prorettype;
@@ -2199,7 +2204,7 @@ pltcl_returnnext(ClientData cdata, Tcl_Interp *interp,
tuplestore_puttuple(call_state->tuple_store, tuple);
}
}
- else
+ else if (!prodesc->fn_is_procedure)
{
Datum retval;
bool isNull = false;
diff --git a/src/pl/tcl/sql/pltcl_call.sql b/src/pl/tcl/sql/pltcl_call.sql
new file mode 100644
index 0000000000..ef1f540f50
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_call.sql
@@ -0,0 +1,36 @@
+CREATE PROCEDURE test_proc1()
+LANGUAGE pltcl
+AS $$
+unset
+$$;
+
+CALL test_proc1();
+
+
+CREATE PROCEDURE test_proc2()
+LANGUAGE pltcl
+AS $$
+return 5
+$$;
+
+CALL test_proc2();
+
+
+CREATE TABLE test1 (a int);
+
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE pltcl
+AS $$
+spi_exec "INSERT INTO test1 VALUES ($1)"
+$$;
+
+CALL test_proc3(55);
+
+SELECT * FROM test1;
+
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+
+DROP TABLE test1;
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
new file mode 100644
index 0000000000..5538ef2f2b
--- /dev/null
+++ b/src/test/regress/expected/create_procedure.out
@@ -0,0 +1,92 @@
+CALL nonexistent(); -- error
+ERROR: function nonexistent() does not exist
+LINE 1: CALL nonexistent();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+CALL random(); -- error
+ERROR: random() is not a procedure
+LINE 1: CALL random();
+ ^
+HINT: To call a function, use SELECT.
+CREATE FUNCTION testfunc1(a int) RETURNS int LANGUAGE SQL AS $$ SELECT a $$;
+CREATE TABLE cp_test (a int, b text);
+CREATE PROCEDURE ptest1(x text)
+LANGUAGE SQL
+AS $$
+INSERT INTO cp_test VALUES (1, x);
+$$;
+SELECT ptest1('x'); -- error
+ERROR: ptest1(unknown) is a procedure
+LINE 1: SELECT ptest1('x');
+ ^
+HINT: To call a procedure, use CALL.
+CALL ptest1('a'); -- ok
+\df ptest1
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest1 | | x text | proc
+(1 row)
+
+SELECT * FROM cp_test ORDER BY a;
+ a | b
+---+---
+ 1 | a
+(1 row)
+
+CREATE PROCEDURE ptest2()
+LANGUAGE SQL
+AS $$
+SELECT 5;
+$$;
+CALL ptest2();
+-- various error cases
+CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+ERROR: invalid attribute in procedure definition
+LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT I...
+ ^
+CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+ERROR: invalid attribute in procedure definition
+LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT I...
+ ^
+CREATE PROCEDURE ptestx(OUT a int) LANGUAGE SQL AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+ERROR: procedures cannot have OUT parameters
+ALTER PROCEDURE ptest1(text) STRICT;
+ERROR: invalid attribute in procedure definition
+LINE 1: ALTER PROCEDURE ptest1(text) STRICT;
+ ^
+ALTER FUNCTION ptest1(text) VOLATILE; -- error: not a function
+ERROR: ptest1(text) is not a function
+ALTER PROCEDURE testfunc1(int) VOLATILE; -- error: not a procedure
+ERROR: testfunc1(integer) is not a procedure
+ALTER PROCEDURE nonexistent() VOLATILE;
+ERROR: procedure nonexistent() does not exist
+DROP FUNCTION ptest1(text); -- error: not a function
+ERROR: ptest1(text) is not a function
+DROP PROCEDURE testfunc1(int); -- error: not a procedure
+ERROR: testfunc1(integer) is not a procedure
+DROP PROCEDURE nonexistent();
+ERROR: procedure nonexistent() does not exist
+-- privileges
+CREATE USER regress_user1;
+GRANT INSERT ON cp_test TO regress_user1;
+REVOKE EXECUTE ON PROCEDURE ptest1(text) FROM PUBLIC;
+SET ROLE regress_user1;
+CALL ptest1('a'); -- error
+ERROR: permission denied for function ptest1
+RESET ROLE;
+GRANT EXECUTE ON PROCEDURE ptest1(text) TO regress_user1;
+SET ROLE regress_user1;
+CALL ptest1('a'); -- ok
+RESET ROLE;
+-- ROUTINE syntax
+ALTER ROUTINE testfunc1(int) RENAME TO testfunc1a;
+ALTER ROUTINE testfunc1a RENAME TO testfunc1;
+ALTER ROUTINE ptest1(text) RENAME TO ptest1a;
+ALTER ROUTINE ptest1a RENAME TO ptest1;
+DROP ROUTINE testfunc1(int);
+-- cleanup
+DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest2;
+DROP TABLE cp_test;
+DROP USER regress_user1;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 1fdadbc9ef..bfd9d54c11 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -29,6 +29,7 @@ CREATE DOMAIN addr_nsp.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0);
CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$;
CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig();
CREATE POLICY genpol ON addr_nsp.gentable;
+CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$;
CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
@@ -88,7 +89,7 @@ BEGIN
('table'), ('index'), ('sequence'), ('view'),
('materialized view'), ('foreign table'),
('table column'), ('foreign table column'),
- ('aggregate'), ('function'), ('type'), ('cast'),
+ ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'),
('table constraint'), ('domain constraint'), ('conversion'), ('default value'),
('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
('text search parser'), ('text search dictionary'),
@@ -171,6 +172,12 @@ WARNING: error for function,{addr_nsp,zwei},{}: function addr_nsp.zwei() does n
WARNING: error for function,{addr_nsp,zwei},{integer}: function addr_nsp.zwei(integer) does not exist
WARNING: error for function,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
WARNING: error for function,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for procedure,{eins},{}: procedure eins() does not exist
+WARNING: error for procedure,{eins},{integer}: procedure eins(integer) does not exist
+WARNING: error for procedure,{addr_nsp,zwei},{}: procedure addr_nsp.zwei() does not exist
+WARNING: error for procedure,{addr_nsp,zwei},{integer}: procedure addr_nsp.zwei(integer) does not exist
+WARNING: error for procedure,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei
+WARNING: error for procedure,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei
WARNING: error for type,{eins},{}: type "eins" does not exist
WARNING: error for type,{eins},{integer}: type "eins" does not exist
WARNING: error for type,{addr_nsp,zwei},{}: name list length must be exactly 1
@@ -371,6 +378,7 @@ WITH objects (type, name, args) AS (VALUES
('foreign table column', '{addr_nsp, genftable, a}', '{}'),
('aggregate', '{addr_nsp, genaggr}', '{int4}'),
('function', '{pg_catalog, pg_identify_object}', '{pg_catalog.oid, pg_catalog.oid, int4}'),
+ ('procedure', '{addr_nsp, proc}', '{int4}'),
('type', '{pg_catalog._int4}', '{}'),
('type', '{addr_nsp.gendomain}', '{}'),
('type', '{addr_nsp.gencomptype}', '{}'),
@@ -431,6 +439,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
type | addr_nsp | gendomain | addr_nsp.gendomain | t
function | pg_catalog | | pg_catalog.pg_identify_object(pg_catalog.oid,pg_catalog.oid,integer) | t
aggregate | addr_nsp | | addr_nsp.genaggr(integer) | t
+ procedure | addr_nsp | | addr_nsp.proc(integer) | t
sequence | addr_nsp | gentable_a_seq | addr_nsp.gentable_a_seq | t
table | addr_nsp | gentable | addr_nsp.gentable | t
table column | addr_nsp | gentable | addr_nsp.gentable.b | t
@@ -469,7 +478,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*,
subscription | | addr_sub | addr_sub | t
publication | | addr_pub | addr_pub | t
publication relation | | | gentable in publication addr_pub | t
-(46 rows)
+(47 rows)
---
--- Cleanup resources
@@ -480,6 +489,6 @@ NOTICE: drop cascades to 4 other objects
DROP PUBLICATION addr_pub;
DROP SUBSCRIPTION addr_sub;
DROP SCHEMA addr_nsp CASCADE;
-NOTICE: drop cascades to 12 other objects
+NOTICE: drop cascades to 13 other objects
DROP OWNED BY regress_addr_user;
DROP USER regress_addr_user;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index bb3532676b..d6e5bc3353 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -6040,3 +6040,44 @@ END; $$ LANGUAGE plpgsql;
ERROR: "x" is not a scalar variable
LINE 3: GET DIAGNOSTICS x = ROW_COUNT;
^
+--
+-- Procedures
+--
+CREATE PROCEDURE test_proc1()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ NULL;
+END;
+$$;
+CALL test_proc1();
+-- error: can't return non-NULL
+CREATE PROCEDURE test_proc2()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN 5;
+END;
+$$;
+CALL test_proc2();
+ERROR: cannot return a value from a procedure
+CONTEXT: PL/pgSQL function test_proc2() while casting return value to function's return type
+CREATE TABLE proc_test1 (a int);
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ INSERT INTO proc_test1 VALUES (x);
+END;
+$$;
+CALL test_proc3(55);
+SELECT * FROM proc_test1;
+ a
+----
+ 55
+(1 row)
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+DROP TABLE proc_test1;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 91cfb743b6..66e35a6a5c 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -915,10 +915,10 @@ select dfunc();
-- verify it lists properly
\df dfunc
- List of functions
- Schema | Name | Result data type | Argument data types | Type
---------+-------+------------------+-----------------------------------------------------------+--------
- public | dfunc | integer | a integer DEFAULT 1, OUT sum integer, b integer DEFAULT 2 | normal
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-----------------------------------------------------------+------
+ public | dfunc | integer | a integer DEFAULT 1, OUT sum integer, b integer DEFAULT 2 | func
(1 row)
drop function dfunc(int, int);
@@ -1083,10 +1083,10 @@ $$ select array_upper($1, 1) $$ language sql;
ERROR: cannot remove parameter defaults from existing function
HINT: Use DROP FUNCTION dfunc(integer[]) first.
\df dfunc
- List of functions
- Schema | Name | Result data type | Argument data types | Type
---------+-------+------------------+-------------------------------------------------+--------
- public | dfunc | integer | VARIADIC a integer[] DEFAULT ARRAY[]::integer[] | normal
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------+------
+ public | dfunc | integer | VARIADIC a integer[] DEFAULT ARRAY[]::integer[] | func
(1 row)
drop function dfunc(a variadic int[]);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 771971a095..e6994f0490 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -651,13 +651,25 @@ GRANT USAGE ON LANGUAGE sql TO regress_user2; -- fail
WARNING: no privileges were granted for "sql"
CREATE FUNCTION testfunc1(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql;
CREATE FUNCTION testfunc2(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql;
-REVOKE ALL ON FUNCTION testfunc1(int), testfunc2(int) FROM PUBLIC;
-GRANT EXECUTE ON FUNCTION testfunc1(int), testfunc2(int) TO regress_user2;
+CREATE AGGREGATE testagg1(int) (sfunc = int4pl, stype = int4);
+CREATE PROCEDURE testproc1(int) AS 'select $1;' LANGUAGE sql;
+REVOKE ALL ON FUNCTION testfunc1(int), testfunc2(int), testagg1(int) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION testfunc1(int), testfunc2(int), testagg1(int) TO regress_user2;
+REVOKE ALL ON FUNCTION testproc1(int) FROM PUBLIC; -- fail, not a function
+ERROR: testproc1(integer) is not a function
+REVOKE ALL ON PROCEDURE testproc1(int) FROM PUBLIC;
+GRANT EXECUTE ON PROCEDURE testproc1(int) TO regress_user2;
GRANT USAGE ON FUNCTION testfunc1(int) TO regress_user3; -- semantic error
ERROR: invalid privilege type USAGE for function
+GRANT USAGE ON FUNCTION testagg1(int) TO regress_user3; -- semantic error
+ERROR: invalid privilege type USAGE for function
+GRANT USAGE ON PROCEDURE testproc1(int) TO regress_user3; -- semantic error
+ERROR: invalid privilege type USAGE for procedure
GRANT ALL PRIVILEGES ON FUNCTION testfunc1(int) TO regress_user4;
GRANT ALL PRIVILEGES ON FUNCTION testfunc_nosuch(int) TO regress_user4;
ERROR: function testfunc_nosuch(integer) does not exist
+GRANT ALL PRIVILEGES ON FUNCTION testagg1(int) TO regress_user4;
+GRANT ALL PRIVILEGES ON PROCEDURE testproc1(int) TO regress_user4;
CREATE FUNCTION testfunc4(boolean) RETURNS text
AS 'select col1 from atest2 where col2 = $1;'
LANGUAGE sql SECURITY DEFINER;
@@ -671,9 +683,20 @@ SELECT testfunc1(5), testfunc2(5); -- ok
CREATE FUNCTION testfunc3(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; -- fail
ERROR: permission denied for language sql
+SELECT testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok
+ testagg1
+----------
+ 6
+(1 row)
+
+CALL testproc1(6); -- ok
SET SESSION AUTHORIZATION regress_user3;
SELECT testfunc1(5); -- fail
ERROR: permission denied for function testfunc1
+SELECT testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- fail
+ERROR: permission denied for function testagg1
+CALL testproc1(6); -- fail
+ERROR: permission denied for function testproc1
SELECT col1 FROM atest2 WHERE col2 = true; -- fail
ERROR: permission denied for relation atest2
SELECT testfunc4(true); -- ok
@@ -689,8 +712,19 @@ SELECT testfunc1(5); -- ok
10
(1 row)
+SELECT testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok
+ testagg1
+----------
+ 6
+(1 row)
+
+CALL testproc1(6); -- ok
DROP FUNCTION testfunc1(int); -- fail
ERROR: must be owner of function testfunc1
+DROP AGGREGATE testagg1(int); -- fail
+ERROR: must be owner of function testagg1
+DROP PROCEDURE testproc1(int); -- fail
+ERROR: must be owner of function testproc1
\c -
DROP FUNCTION testfunc1(int); -- ok
-- restore to sanity
@@ -1537,22 +1571,54 @@ SELECT has_schema_privilege('regress_user2', 'testns5', 'CREATE'); -- no
SET ROLE regress_user1;
CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4);
+CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql;
SELECT has_function_privilege('regress_user2', 'testns.foo()', 'EXECUTE'); -- no
has_function_privilege
------------------------
f
(1 row)
-ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON FUNCTIONS to public;
+SELECT has_function_privilege('regress_user2', 'testns.agg1(int)', 'EXECUTE'); -- no
+ has_function_privilege
+------------------------
+ f
+(1 row)
+
+SELECT has_function_privilege('regress_user2', 'testns.bar()', 'EXECUTE'); -- no
+ has_function_privilege
+------------------------
+ f
+(1 row)
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON ROUTINES to public;
DROP FUNCTION testns.foo();
CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+DROP AGGREGATE testns.agg1(int);
+CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4);
+DROP PROCEDURE testns.bar();
+CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql;
SELECT has_function_privilege('regress_user2', 'testns.foo()', 'EXECUTE'); -- yes
has_function_privilege
------------------------
t
(1 row)
+SELECT has_function_privilege('regress_user2', 'testns.agg1(int)', 'EXECUTE'); -- yes
+ has_function_privilege
+------------------------
+ t
+(1 row)
+
+SELECT has_function_privilege('regress_user2', 'testns.bar()', 'EXECUTE'); -- yes (counts as function here)
+ has_function_privilege
+------------------------
+ t
+(1 row)
+
DROP FUNCTION testns.foo();
+DROP AGGREGATE testns.agg1(int);
+DROP PROCEDURE testns.bar();
ALTER DEFAULT PRIVILEGES FOR ROLE regress_user1 REVOKE USAGE ON TYPES FROM public;
CREATE DOMAIN testns.testdomain1 AS int;
SELECT has_type_privilege('regress_user2', 'testns.testdomain1', 'USAGE'); -- no
@@ -1631,12 +1697,26 @@ SELECT has_table_privilege('regress_user1', 'testns.t2', 'SELECT'); -- false
(1 row)
CREATE FUNCTION testns.testfunc(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql;
+CREATE AGGREGATE testns.testagg(int) (sfunc = int4pl, stype = int4);
+CREATE PROCEDURE testns.testproc(int) AS 'select 3' LANGUAGE sql;
SELECT has_function_privilege('regress_user1', 'testns.testfunc(int)', 'EXECUTE'); -- true by default
has_function_privilege
------------------------
t
(1 row)
+SELECT has_function_privilege('regress_user1', 'testns.testagg(int)', 'EXECUTE'); -- true by default
+ has_function_privilege
+------------------------
+ t
+(1 row)
+
+SELECT has_function_privilege('regress_user1', 'testns.testproc(int)', 'EXECUTE'); -- true by default
+ has_function_privilege
+------------------------
+ t
+(1 row)
+
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA testns FROM PUBLIC;
SELECT has_function_privilege('regress_user1', 'testns.testfunc(int)', 'EXECUTE'); -- false
has_function_privilege
@@ -1644,9 +1724,47 @@ SELECT has_function_privilege('regress_user1', 'testns.testfunc(int)', 'EXECUTE'
f
(1 row)
+SELECT has_function_privilege('regress_user1', 'testns.testagg(int)', 'EXECUTE'); -- false
+ has_function_privilege
+------------------------
+ f
+(1 row)
+
+SELECT has_function_privilege('regress_user1', 'testns.testproc(int)', 'EXECUTE'); -- still true, not a function
+ has_function_privilege
+------------------------
+ t
+(1 row)
+
+REVOKE ALL ON ALL PROCEDURES IN SCHEMA testns FROM PUBLIC;
+SELECT has_function_privilege('regress_user1', 'testns.testproc(int)', 'EXECUTE'); -- now false
+ has_function_privilege
+------------------------
+ f
+(1 row)
+
+GRANT ALL ON ALL ROUTINES IN SCHEMA testns TO PUBLIC;
+SELECT has_function_privilege('regress_user1', 'testns.testfunc(int)', 'EXECUTE'); -- true
+ has_function_privilege
+------------------------
+ t
+(1 row)
+
+SELECT has_function_privilege('regress_user1', 'testns.testagg(int)', 'EXECUTE'); -- true
+ has_function_privilege
+------------------------
+ t
+(1 row)
+
+SELECT has_function_privilege('regress_user1', 'testns.testproc(int)', 'EXECUTE'); -- true
+ has_function_privilege
+------------------------
+ t
+(1 row)
+
\set VERBOSITY terse \\ -- suppress cascade details
DROP SCHEMA testns CASCADE;
-NOTICE: drop cascades to 3 other objects
+NOTICE: drop cascades to 5 other objects
\set VERBOSITY default
-- Change owner of the schema & and rename of new schema owner
\c -
@@ -1729,8 +1847,10 @@ drop table dep_priv_test;
-- clean up
\c
drop sequence x_seq;
+DROP AGGREGATE testagg1(int);
DROP FUNCTION testfunc2(int);
DROP FUNCTION testfunc4(boolean);
+DROP PROCEDURE testproc1(int);
DROP VIEW atestv0;
DROP VIEW atestv1;
DROP VIEW atestv2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 892a214f2f..e224977791 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -53,7 +53,7 @@ test: copy copyselect copydml
# ----------
# More groups of parallel tests
# ----------
-test: create_misc create_operator
+test: create_misc create_operator create_procedure
# These depend on the above two
test: create_index create_view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 15a1f861a9..9fc5f1a268 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -63,6 +63,7 @@ test: copyselect
test: copydml
test: create_misc
test: create_operator
+test: create_procedure
test: create_index
test: create_view
test: create_aggregate
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
new file mode 100644
index 0000000000..f09ba2ad30
--- /dev/null
+++ b/src/test/regress/sql/create_procedure.sql
@@ -0,0 +1,79 @@
+CALL nonexistent(); -- error
+CALL random(); -- error
+
+CREATE FUNCTION testfunc1(a int) RETURNS int LANGUAGE SQL AS $$ SELECT a $$;
+
+CREATE TABLE cp_test (a int, b text);
+
+CREATE PROCEDURE ptest1(x text)
+LANGUAGE SQL
+AS $$
+INSERT INTO cp_test VALUES (1, x);
+$$;
+
+SELECT ptest1('x'); -- error
+CALL ptest1('a'); -- ok
+
+\df ptest1
+
+SELECT * FROM cp_test ORDER BY a;
+
+
+CREATE PROCEDURE ptest2()
+LANGUAGE SQL
+AS $$
+SELECT 5;
+$$;
+
+CALL ptest2();
+
+
+-- various error cases
+
+CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+CREATE PROCEDURE ptestx(OUT a int) LANGUAGE SQL AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
+
+ALTER PROCEDURE ptest1(text) STRICT;
+ALTER FUNCTION ptest1(text) VOLATILE; -- error: not a function
+ALTER PROCEDURE testfunc1(int) VOLATILE; -- error: not a procedure
+ALTER PROCEDURE nonexistent() VOLATILE;
+
+DROP FUNCTION ptest1(text); -- error: not a function
+DROP PROCEDURE testfunc1(int); -- error: not a procedure
+DROP PROCEDURE nonexistent();
+
+
+-- privileges
+
+CREATE USER regress_user1;
+GRANT INSERT ON cp_test TO regress_user1;
+REVOKE EXECUTE ON PROCEDURE ptest1(text) FROM PUBLIC;
+SET ROLE regress_user1;
+CALL ptest1('a'); -- error
+RESET ROLE;
+GRANT EXECUTE ON PROCEDURE ptest1(text) TO regress_user1;
+SET ROLE regress_user1;
+CALL ptest1('a'); -- ok
+RESET ROLE;
+
+
+-- ROUTINE syntax
+
+ALTER ROUTINE testfunc1(int) RENAME TO testfunc1a;
+ALTER ROUTINE testfunc1a RENAME TO testfunc1;
+
+ALTER ROUTINE ptest1(text) RENAME TO ptest1a;
+ALTER ROUTINE ptest1a RENAME TO ptest1;
+
+DROP ROUTINE testfunc1(int);
+
+
+-- cleanup
+
+DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest2;
+
+DROP TABLE cp_test;
+
+DROP USER regress_user1;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 63821b8008..55faa71edf 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -32,6 +32,7 @@ CREATE DOMAIN addr_nsp.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0);
CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$;
CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig();
CREATE POLICY genpol ON addr_nsp.gentable;
+CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$;
CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
@@ -81,7 +82,7 @@ BEGIN
('table'), ('index'), ('sequence'), ('view'),
('materialized view'), ('foreign table'),
('table column'), ('foreign table column'),
- ('aggregate'), ('function'), ('type'), ('cast'),
+ ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'),
('table constraint'), ('domain constraint'), ('conversion'), ('default value'),
('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
('text search parser'), ('text search dictionary'),
@@ -147,6 +148,7 @@ WITH objects (type, name, args) AS (VALUES
('foreign table column', '{addr_nsp, genftable, a}', '{}'),
('aggregate', '{addr_nsp, genaggr}', '{int4}'),
('function', '{pg_catalog, pg_identify_object}', '{pg_catalog.oid, pg_catalog.oid, int4}'),
+ ('procedure', '{addr_nsp, proc}', '{int4}'),
('type', '{pg_catalog._int4}', '{}'),
('type', '{addr_nsp.gendomain}', '{}'),
('type', '{addr_nsp.gencomptype}', '{}'),
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 6620ea6172..1c355132b7 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -4820,3 +4820,52 @@ BEGIN
GET DIAGNOSTICS x = ROW_COUNT;
RETURN;
END; $$ LANGUAGE plpgsql;
+
+
+--
+-- Procedures
+--
+
+CREATE PROCEDURE test_proc1()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ NULL;
+END;
+$$;
+
+CALL test_proc1();
+
+
+-- error: can't return non-NULL
+CREATE PROCEDURE test_proc2()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN 5;
+END;
+$$;
+
+CALL test_proc2();
+
+
+CREATE TABLE proc_test1 (a int);
+
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ INSERT INTO proc_test1 VALUES (x);
+END;
+$$;
+
+CALL test_proc3(55);
+
+SELECT * FROM proc_test1;
+
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+
+DROP TABLE proc_test1;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index a900ba2f84..ea8dd028cd 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -442,12 +442,21 @@ SET SESSION AUTHORIZATION regress_user1;
GRANT USAGE ON LANGUAGE sql TO regress_user2; -- fail
CREATE FUNCTION testfunc1(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql;
CREATE FUNCTION testfunc2(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql;
+CREATE AGGREGATE testagg1(int) (sfunc = int4pl, stype = int4);
+CREATE PROCEDURE testproc1(int) AS 'select $1;' LANGUAGE sql;
-REVOKE ALL ON FUNCTION testfunc1(int), testfunc2(int) FROM PUBLIC;
-GRANT EXECUTE ON FUNCTION testfunc1(int), testfunc2(int) TO regress_user2;
+REVOKE ALL ON FUNCTION testfunc1(int), testfunc2(int), testagg1(int) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION testfunc1(int), testfunc2(int), testagg1(int) TO regress_user2;
+REVOKE ALL ON FUNCTION testproc1(int) FROM PUBLIC; -- fail, not a function
+REVOKE ALL ON PROCEDURE testproc1(int) FROM PUBLIC;
+GRANT EXECUTE ON PROCEDURE testproc1(int) TO regress_user2;
GRANT USAGE ON FUNCTION testfunc1(int) TO regress_user3; -- semantic error
+GRANT USAGE ON FUNCTION testagg1(int) TO regress_user3; -- semantic error
+GRANT USAGE ON PROCEDURE testproc1(int) TO regress_user3; -- semantic error
GRANT ALL PRIVILEGES ON FUNCTION testfunc1(int) TO regress_user4;
GRANT ALL PRIVILEGES ON FUNCTION testfunc_nosuch(int) TO regress_user4;
+GRANT ALL PRIVILEGES ON FUNCTION testagg1(int) TO regress_user4;
+GRANT ALL PRIVILEGES ON PROCEDURE testproc1(int) TO regress_user4;
CREATE FUNCTION testfunc4(boolean) RETURNS text
AS 'select col1 from atest2 where col2 = $1;'
@@ -457,16 +466,24 @@ GRANT EXECUTE ON FUNCTION testfunc4(boolean) TO regress_user3;
SET SESSION AUTHORIZATION regress_user2;
SELECT testfunc1(5), testfunc2(5); -- ok
CREATE FUNCTION testfunc3(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; -- fail
+SELECT testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok
+CALL testproc1(6); -- ok
SET SESSION AUTHORIZATION regress_user3;
SELECT testfunc1(5); -- fail
+SELECT testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- fail
+CALL testproc1(6); -- fail
SELECT col1 FROM atest2 WHERE col2 = true; -- fail
SELECT testfunc4(true); -- ok
SET SESSION AUTHORIZATION regress_user4;
SELECT testfunc1(5); -- ok
+SELECT testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok
+CALL testproc1(6); -- ok
DROP FUNCTION testfunc1(int); -- fail
+DROP AGGREGATE testagg1(int); -- fail
+DROP PROCEDURE testproc1(int); -- fail
\c -
@@ -931,17 +948,29 @@ SELECT has_schema_privilege('regress_user2', 'testns5', 'CREATE'); -- no
SET ROLE regress_user1;
CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4);
+CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql;
SELECT has_function_privilege('regress_user2', 'testns.foo()', 'EXECUTE'); -- no
+SELECT has_function_privilege('regress_user2', 'testns.agg1(int)', 'EXECUTE'); -- no
+SELECT has_function_privilege('regress_user2', 'testns.bar()', 'EXECUTE'); -- no
-ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON FUNCTIONS to public;
+ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON ROUTINES to public;
DROP FUNCTION testns.foo();
CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+DROP AGGREGATE testns.agg1(int);
+CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4);
+DROP PROCEDURE testns.bar();
+CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql;
SELECT has_function_privilege('regress_user2', 'testns.foo()', 'EXECUTE'); -- yes
+SELECT has_function_privilege('regress_user2', 'testns.agg1(int)', 'EXECUTE'); -- yes
+SELECT has_function_privilege('regress_user2', 'testns.bar()', 'EXECUTE'); -- yes (counts as function here)
DROP FUNCTION testns.foo();
+DROP AGGREGATE testns.agg1(int);
+DROP PROCEDURE testns.bar();
ALTER DEFAULT PRIVILEGES FOR ROLE regress_user1 REVOKE USAGE ON TYPES FROM public;
@@ -995,12 +1024,28 @@ SELECT has_table_privilege('regress_user1', 'testns.t1', 'SELECT'); -- false
SELECT has_table_privilege('regress_user1', 'testns.t2', 'SELECT'); -- false
CREATE FUNCTION testns.testfunc(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql;
+CREATE AGGREGATE testns.testagg(int) (sfunc = int4pl, stype = int4);
+CREATE PROCEDURE testns.testproc(int) AS 'select 3' LANGUAGE sql;
SELECT has_function_privilege('regress_user1', 'testns.testfunc(int)', 'EXECUTE'); -- true by default
+SELECT has_function_privilege('regress_user1', 'testns.testagg(int)', 'EXECUTE'); -- true by default
+SELECT has_function_privilege('regress_user1', 'testns.testproc(int)', 'EXECUTE'); -- true by default
REVOKE ALL ON ALL FUNCTIONS IN SCHEMA testns FROM PUBLIC;
SELECT has_function_privilege('regress_user1', 'testns.testfunc(int)', 'EXECUTE'); -- false
+SELECT has_function_privilege('regress_user1', 'testns.testagg(int)', 'EXECUTE'); -- false
+SELECT has_function_privilege('regress_user1', 'testns.testproc(int)', 'EXECUTE'); -- still true, not a function
+
+REVOKE ALL ON ALL PROCEDURES IN SCHEMA testns FROM PUBLIC;
+
+SELECT has_function_privilege('regress_user1', 'testns.testproc(int)', 'EXECUTE'); -- now false
+
+GRANT ALL ON ALL ROUTINES IN SCHEMA testns TO PUBLIC;
+
+SELECT has_function_privilege('regress_user1', 'testns.testfunc(int)', 'EXECUTE'); -- true
+SELECT has_function_privilege('regress_user1', 'testns.testagg(int)', 'EXECUTE'); -- true
+SELECT has_function_privilege('regress_user1', 'testns.testproc(int)', 'EXECUTE'); -- true
\set VERBOSITY terse \\ -- suppress cascade details
DROP SCHEMA testns CASCADE;
@@ -1064,8 +1109,10 @@ drop table dep_priv_test;
drop sequence x_seq;
+DROP AGGREGATE testagg1(int);
DROP FUNCTION testfunc2(int);
DROP FUNCTION testfunc4(boolean);
+DROP PROCEDURE testproc1(int);
DROP VIEW atestv0;
DROP VIEW atestv1;