Support a COLLATE clause in plpgsql variable declarations.
authorTom Lane
Sun, 17 Apr 2011 18:54:19 +0000 (14:54 -0400)
committerTom Lane
Sun, 17 Apr 2011 18:54:19 +0000 (14:54 -0400)
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.

doc/src/sgml/plpgsql.sgml
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_scanner.c
src/test/regress/expected/collate.linux.utf8.out
src/test/regress/sql/collate.linux.utf8.sql

index a04ab1391230e39825f0efd3b5e23d7a7da2b315..1866e43e0e6d9620f8e2ed153d5c7578fbc55a6e 100644 (file)
@@ -328,15 +328,17 @@ arow RECORD;
     
      The general syntax of a variable declaration is:
 
-name  CONSTANT  type  NOT NULL   { DEFAULT | := } expression ;
+name  CONSTANT  type  COLLATE collation_name   NOT NULL   { DEFAULT | := } expression ;
 
       The DEFAULT clause, if given, specifies the initial value assigned
       to the variable when the block is entered.  If the DEFAULT clause
       is not given then the variable is initialized to the
       SQL null value.
       The CONSTANT option prevents the variable from being
-      assigned to, so that its value will remain constant for the duration of
-      the block.
+      assigned to after initialization, so that its value will remain constant
+      for the duration of the block.
+      The COLLATE option specifies a collation to use for the
+      variable (see ).
       If NOT NULL
       is specified, an assignment of a null value results in a run-time
       error. All variables declared as NOT NULL
@@ -768,9 +770,23 @@ $$ LANGUAGE plpgsql;
    
 
    
-    Explicit COLLATE clauses can be written inside a function
-    if it is desired to force a particular collation to be used regardless
-    of what the function is called with.  For example,
+    A local variable of a collatable data type can have a different collation
+    associated with it by including the COLLATE option in its
+    declaration, for example
+
+
+DECLARE
+    local_a text COLLATE "en_US";
+
+
+    This option overrides the collation that would otherwise be
+    given to the variable according to the rules above.
+   
+
+   
+    Also, of course explicit 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,
 
 
 CREATE FUNCTION less_than_c(a text, b text) RETURNS boolean AS $$
@@ -779,6 +795,10 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
+
+    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.
    
   
   
index fbd441a1bc986835d9dc8729ff61ab61ee4d06d4..4e2b7058f0c471182b8276a101e33fa96ed01bdb 100644 (file)
@@ -21,6 +21,7 @@
 #include "parser/parse_type.h"
 #include "parser/scanner.h"
 #include "parser/scansup.h"
+#include "utils/builtins.h"
 
 
 /* Location tracking support --- simpler than bison's default */
@@ -122,6 +123,7 @@ static  List            *read_raise_options(void);
        PLcword                 cword;
        PLwdatum                wdatum;
        bool                    boolean;
+       Oid                     oid;
        struct
        {
            char *name;
@@ -167,6 +169,7 @@ static  List            *read_raise_options(void);
 %type     decl_const decl_notnull exit_type
 %type    decl_defval decl_cursor_query
 %type   decl_datatype
+%type         decl_collate
 %type   decl_cursor_args
 %type    decl_cursor_arglist
 %type  decl_aliasitem
@@ -245,6 +248,7 @@ static  List            *read_raise_options(void);
 %token    K_BY
 %token    K_CASE
 %token    K_CLOSE
+%token    K_COLLATE
 %token    K_CONSTANT
 %token    K_CONTINUE
 %token    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;
 
+                       /*
+                        * 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,
                                                     $3, true);
                        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"),
                                         parser_errposition(@2)));
                        }
-                       if ($4)
+                       if ($5)
                        {
                            if (var->dtype == PLPGSQL_DTYPE_VAR)
-                               ((PLpgSQL_var *) var)->notnull = $4;
+                               ((PLpgSQL_var *) var)->notnull = $5;
                            else
                                ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -455,10 +476,10 @@ decl_statement    : decl_varname decl_const decl_datatype decl_notnull decl_defval
                                         parser_errposition(@4)));
 
                        }
-                       if ($5 != NULL)
+                       if ($6 != NULL)
                        {
                            if (var->dtype == PLPGSQL_DTYPE_VAR)
-                               ((PLpgSQL_var *) var)->default_val = $5;
+                               ((PLpgSQL_var *) var)->default_val = $6;
                            else
                                ereport(ERROR,
                                        (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   :
                    { $$ = false; }
                | K_NOT K_NULL
@@ -2432,7 +2466,8 @@ read_datatype(int tok)
                yyerror("incomplete data type 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;
        /* Possible followers for datatype in a cursor_arg list */
        if ((tok == ',' || tok == ')') && parenlevel == 0)
index e8a2628f2f1dfbdd6ba466d7216c3a2ee0906d19..e1c0b625954a6c6cee45932e849df681e8d1508b 100644 (file)
@@ -64,6 +64,7 @@ static const ScanKeyword reserved_keywords[] = {
    PG_KEYWORD("by", K_BY, RESERVED_KEYWORD)
    PG_KEYWORD("case", K_CASE, 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("declare", K_DECLARE, RESERVED_KEYWORD)
    PG_KEYWORD("default", K_DEFAULT, RESERVED_KEYWORD)
index f0008ddf14b242859da9461390310f0556ed9123..f225b487306127dddd8214e582210e583ff611c2 100644 (file)
@@ -825,6 +825,46 @@ ORDER BY a.b, b.b;
  bbc | bbc | f  | f    | f              | f
 (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
 SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1;
  unnest 
index 51d65cf0da8c4538864288d9152ad40777acac1d..dfb10e4d15bfa47f7b6841f379662ea4e5b12424 100644 (file)
@@ -256,6 +256,34 @@ FROM collate_test1 a, collate_test1 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
 
 SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1;