Commit c9473258 authored by Tom Lane's avatar Tom Lane

Support a COLLATE clause in plpgsql variable declarations.

This allows the usual rules for assigning a collation to a local variable
to be overridden.  Per discussion, it seems appropriate to support this
rather than forcing all local variables to have the argument-derived
collation.
parent 88dc6fa7
...@@ -328,15 +328,17 @@ arow RECORD; ...@@ -328,15 +328,17 @@ arow RECORD;
<para> <para>
The general syntax of a variable declaration is: The general syntax of a variable declaration is:
<synopsis> <synopsis>
<replaceable>name</replaceable> <optional> CONSTANT </optional> <replaceable>type</replaceable> <optional> NOT NULL </optional> <optional> { DEFAULT | := } <replaceable>expression</replaceable> </optional>; <replaceable>name</replaceable> <optional> CONSTANT </optional> <replaceable>type</replaceable> <optional> COLLATE <replaceable>collation_name</replaceable> </optional> <optional> NOT NULL </optional> <optional> { DEFAULT | := } <replaceable>expression</replaceable> </optional>;
</synopsis> </synopsis>
The <literal>DEFAULT</> clause, if given, specifies the initial value assigned The <literal>DEFAULT</> clause, if given, specifies the initial value assigned
to the variable when the block is entered. If the <literal>DEFAULT</> clause to the variable when the block is entered. If the <literal>DEFAULT</> clause
is not given then the variable is initialized to the is not given then the variable is initialized to the
<acronym>SQL</acronym> null value. <acronym>SQL</acronym> null value.
The <literal>CONSTANT</> option prevents the variable from being The <literal>CONSTANT</> option prevents the variable from being
assigned to, so that its value will remain constant for the duration of assigned to after initialization, so that its value will remain constant
the block. for the duration of the block.
The <literal>COLLATE</> option specifies a collation to use for the
variable (see <xref linkend="plpgsql-declaration-collation">).
If <literal>NOT NULL</> If <literal>NOT NULL</>
is specified, an assignment of a null value results in a run-time is specified, an assignment of a null value results in a run-time
error. All variables declared as <literal>NOT NULL</> error. All variables declared as <literal>NOT NULL</>
...@@ -768,9 +770,23 @@ $$ LANGUAGE plpgsql; ...@@ -768,9 +770,23 @@ $$ LANGUAGE plpgsql;
</para> </para>
<para> <para>
Explicit <literal>COLLATE</> clauses can be written inside a function A local variable of a collatable data type can have a different collation
if it is desired to force a particular collation to be used regardless associated with it by including the <literal>COLLATE</> option in its
of what the function is called with. For example, declaration, for example
<programlisting>
DECLARE
local_a text COLLATE "en_US";
</programlisting>
This option overrides the collation that would otherwise be
given to the variable according to the rules above.
</para>
<para>
Also, of course explicit <literal>COLLATE</> clauses can be written inside
a function if it is desired to force a particular collation to be used in
a particular operation. For example,
<programlisting> <programlisting>
CREATE FUNCTION less_than_c(a text, b text) RETURNS boolean AS $$ CREATE FUNCTION less_than_c(a text, b text) RETURNS boolean AS $$
...@@ -779,6 +795,10 @@ BEGIN ...@@ -779,6 +795,10 @@ BEGIN
END; END;
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
</programlisting> </programlisting>
This overrides the collations associated with the table columns,
parameters, or local variables used in the expression, just as would
happen in a plain SQL command.
</para> </para>
</sect2> </sect2>
</sect1> </sect1>
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "parser/parse_type.h" #include "parser/parse_type.h"
#include "parser/scanner.h" #include "parser/scanner.h"
#include "parser/scansup.h" #include "parser/scansup.h"
#include "utils/builtins.h"
/* Location tracking support --- simpler than bison's default */ /* Location tracking support --- simpler than bison's default */
...@@ -122,6 +123,7 @@ static List *read_raise_options(void); ...@@ -122,6 +123,7 @@ static List *read_raise_options(void);
PLcword cword; PLcword cword;
PLwdatum wdatum; PLwdatum wdatum;
bool boolean; bool boolean;
Oid oid;
struct struct
{ {
char *name; char *name;
...@@ -167,6 +169,7 @@ static List *read_raise_options(void); ...@@ -167,6 +169,7 @@ static List *read_raise_options(void);
%type <boolean> decl_const decl_notnull exit_type %type <boolean> decl_const decl_notnull exit_type
%type <expr> decl_defval decl_cursor_query %type <expr> decl_defval decl_cursor_query
%type <dtype> decl_datatype %type <dtype> decl_datatype
%type <oid> decl_collate
%type <datum> decl_cursor_args %type <datum> decl_cursor_args
%type <list> decl_cursor_arglist %type <list> decl_cursor_arglist
%type <nsitem> decl_aliasitem %type <nsitem> decl_aliasitem
...@@ -245,6 +248,7 @@ static List *read_raise_options(void); ...@@ -245,6 +248,7 @@ static List *read_raise_options(void);
%token <keyword> K_BY %token <keyword> K_BY
%token <keyword> K_CASE %token <keyword> K_CASE
%token <keyword> K_CLOSE %token <keyword> K_CLOSE
%token <keyword> K_COLLATE
%token <keyword> K_CONSTANT %token <keyword> K_CONSTANT
%token <keyword> K_CONTINUE %token <keyword> K_CONTINUE
%token <keyword> K_CURSOR %token <keyword> K_CURSOR
...@@ -428,10 +432,27 @@ decl_stmt : decl_statement ...@@ -428,10 +432,27 @@ decl_stmt : decl_statement
} }
; ;
decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval
{ {
PLpgSQL_variable *var; PLpgSQL_variable *var;
/*
* If a collation is supplied, insert it into the
* datatype. We assume decl_datatype always returns
* a freshly built struct not shared with other
* variables.
*/
if (OidIsValid($4))
{
if (!OidIsValid($3->collation))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("collations are not supported by type %s",
format_type_be($3->typoid)),
parser_errposition(@4)));
$3->collation = $4;
}
var = plpgsql_build_variable($1.name, $1.lineno, var = plpgsql_build_variable($1.name, $1.lineno,
$3, true); $3, true);
if ($2) if ($2)
...@@ -444,10 +465,10 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval ...@@ -444,10 +465,10 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval
errmsg("row or record variable cannot be CONSTANT"), errmsg("row or record variable cannot be CONSTANT"),
parser_errposition(@2))); parser_errposition(@2)));
} }
if ($4) if ($5)
{ {
if (var->dtype == PLPGSQL_DTYPE_VAR) if (var->dtype == PLPGSQL_DTYPE_VAR)
((PLpgSQL_var *) var)->notnull = $4; ((PLpgSQL_var *) var)->notnull = $5;
else else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
...@@ -455,10 +476,10 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval ...@@ -455,10 +476,10 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval
parser_errposition(@4))); parser_errposition(@4)));
} }
if ($5 != NULL) if ($6 != NULL)
{ {
if (var->dtype == PLPGSQL_DTYPE_VAR) if (var->dtype == PLPGSQL_DTYPE_VAR)
((PLpgSQL_var *) var)->default_val = $5; ((PLpgSQL_var *) var)->default_val = $6;
else else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
...@@ -685,6 +706,19 @@ decl_datatype : ...@@ -685,6 +706,19 @@ decl_datatype :
} }
; ;
decl_collate :
{ $$ = InvalidOid; }
| K_COLLATE T_WORD
{
$$ = get_collation_oid(list_make1(makeString($2.ident)),
false);
}
| K_COLLATE T_CWORD
{
$$ = get_collation_oid($2.idents, false);
}
;
decl_notnull : decl_notnull :
{ $$ = false; } { $$ = false; }
| K_NOT K_NULL | K_NOT K_NULL
...@@ -2432,7 +2466,8 @@ read_datatype(int tok) ...@@ -2432,7 +2466,8 @@ read_datatype(int tok)
yyerror("incomplete data type declaration"); yyerror("incomplete data type declaration");
} }
/* Possible followers for datatype in a declaration */ /* Possible followers for datatype in a declaration */
if (tok == K_NOT || tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT) if (tok == K_COLLATE || tok == K_NOT ||
tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT)
break; break;
/* Possible followers for datatype in a cursor_arg list */ /* Possible followers for datatype in a cursor_arg list */
if ((tok == ',' || tok == ')') && parenlevel == 0) if ((tok == ',' || tok == ')') && parenlevel == 0)
......
...@@ -64,6 +64,7 @@ static const ScanKeyword reserved_keywords[] = { ...@@ -64,6 +64,7 @@ static const ScanKeyword reserved_keywords[] = {
PG_KEYWORD("by", K_BY, RESERVED_KEYWORD) PG_KEYWORD("by", K_BY, RESERVED_KEYWORD)
PG_KEYWORD("case", K_CASE, RESERVED_KEYWORD) PG_KEYWORD("case", K_CASE, RESERVED_KEYWORD)
PG_KEYWORD("close", K_CLOSE, RESERVED_KEYWORD) PG_KEYWORD("close", K_CLOSE, RESERVED_KEYWORD)
PG_KEYWORD("collate", K_COLLATE, RESERVED_KEYWORD)
PG_KEYWORD("continue", K_CONTINUE, RESERVED_KEYWORD) PG_KEYWORD("continue", K_CONTINUE, RESERVED_KEYWORD)
PG_KEYWORD("declare", K_DECLARE, RESERVED_KEYWORD) PG_KEYWORD("declare", K_DECLARE, RESERVED_KEYWORD)
PG_KEYWORD("default", K_DEFAULT, RESERVED_KEYWORD) PG_KEYWORD("default", K_DEFAULT, RESERVED_KEYWORD)
......
...@@ -825,6 +825,46 @@ ORDER BY a.b, b.b; ...@@ -825,6 +825,46 @@ ORDER BY a.b, b.b;
bbc | bbc | f | f | f | f bbc | bbc | f | f | f | f
(16 rows) (16 rows)
-- collation override in plpgsql
CREATE FUNCTION mylt2 (x text, y text) RETURNS boolean LANGUAGE plpgsql AS $$
declare
xx text := x;
yy text := y;
begin
return xx < yy;
end
$$;
SELECT mylt2('a', 'B' collate "en_US") as t, mylt2('a', 'B' collate "C") as f;
t | f
---+---
t | f
(1 row)
CREATE OR REPLACE FUNCTION
mylt2 (x text, y text) RETURNS boolean LANGUAGE plpgsql AS $$
declare
xx text COLLATE "POSIX" := x;
yy text := y;
begin
return xx < yy;
end
$$;
SELECT mylt2('a', 'B') as f;
f
---
f
(1 row)
SELECT mylt2('a', 'B' collate "C") as fail; -- conflicting collations
ERROR: could not determine which collation to use for string comparison
HINT: Use the COLLATE clause to set the collation explicitly.
CONTEXT: PL/pgSQL function "mylt2" line 6 at RETURN
SELECT mylt2('a', 'B' collate "POSIX") as f;
f
---
f
(1 row)
-- polymorphism -- polymorphism
SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1; SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1;
unnest unnest
......
...@@ -256,6 +256,34 @@ FROM collate_test1 a, collate_test1 b ...@@ -256,6 +256,34 @@ FROM collate_test1 a, collate_test1 b
ORDER BY a.b, b.b; ORDER BY a.b, b.b;
-- collation override in plpgsql
CREATE FUNCTION mylt2 (x text, y text) RETURNS boolean LANGUAGE plpgsql AS $$
declare
xx text := x;
yy text := y;
begin
return xx < yy;
end
$$;
SELECT mylt2('a', 'B' collate "en_US") as t, mylt2('a', 'B' collate "C") as f;
CREATE OR REPLACE FUNCTION
mylt2 (x text, y text) RETURNS boolean LANGUAGE plpgsql AS $$
declare
xx text COLLATE "POSIX" := x;
yy text := y;
begin
return xx < yy;
end
$$;
SELECT mylt2('a', 'B') as f;
SELECT mylt2('a', 'B' collate "C") as fail; -- conflicting collations
SELECT mylt2('a', 'B' collate "POSIX") as f;
-- polymorphism -- polymorphism
SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1; SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment