mirror of
https://github.com/postgres/postgres.git
synced 2025-07-31 22:04:40 +03:00
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.
This commit is contained in:
@ -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;
|
|||||||
</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
|
|||||||
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 @@
|
|||||||
#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);
|
|||||||
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);
|
|||||||
%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);
|
|||||||
%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
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
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
|
|||||||
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
|
|||||||
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 :
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
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)
|
|||||||
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[] = {
|
|||||||
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;
|
|||||||
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
|
|||||||
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;
|
||||||
|
Reference in New Issue
Block a user