Commit a4425e32 authored by Tom Lane's avatar Tom Lane

Fix collation handling in plpgsql functions.

Make plpgsql treat the input collation as a polymorphism variable, so
that we cache separate plans for each input collation that's used in a
particular session, as per recent discussion.  Propagate the input
collation to all collatable input parameters.

I chose to also propagate the input collation to all declared variables of
collatable types, which is a bit more debatable but seems to be necessary
for non-astonishing behavior.  (Copying a parameter into a separate local
variable shouldn't result in a change of behavior, for example.)  There is
enough infrastructure here to support declaring a collation for each local
variable to override that default, but I thought we should wait to see what
the field demand is before adding such a feature.

In passing, remove exec_get_rec_fieldtype(), which wasn't used anywhere.

Documentation patch to follow.
parent f6f0916d
...@@ -487,7 +487,8 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval ...@@ -487,7 +487,8 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval
new = (PLpgSQL_var *) new = (PLpgSQL_var *)
plpgsql_build_variable($1.name, $1.lineno, plpgsql_build_variable($1.name, $1.lineno,
plpgsql_build_datatype(REFCURSOROID, plpgsql_build_datatype(REFCURSOROID,
-1), -1,
InvalidOid),
true); true);
curname_def = palloc0(sizeof(PLpgSQL_expr)); curname_def = palloc0(sizeof(PLpgSQL_expr));
...@@ -1248,7 +1249,8 @@ for_control : for_variable K_IN ...@@ -1248,7 +1249,8 @@ for_control : for_variable K_IN
plpgsql_build_variable($1.name, plpgsql_build_variable($1.name,
$1.lineno, $1.lineno,
plpgsql_build_datatype(INT4OID, plpgsql_build_datatype(INT4OID,
-1), -1,
InvalidOid),
true); true);
new = palloc0(sizeof(PLpgSQL_stmt_fori)); new = palloc0(sizeof(PLpgSQL_stmt_fori));
...@@ -1932,13 +1934,17 @@ exception_sect : ...@@ -1932,13 +1934,17 @@ exception_sect :
PLpgSQL_variable *var; PLpgSQL_variable *var;
var = plpgsql_build_variable("sqlstate", lineno, var = plpgsql_build_variable("sqlstate", lineno,
plpgsql_build_datatype(TEXTOID, -1), plpgsql_build_datatype(TEXTOID,
-1,
plpgsql_curr_compile->fn_input_collation),
true); true);
((PLpgSQL_var *) var)->isconst = true; ((PLpgSQL_var *) var)->isconst = true;
new->sqlstate_varno = var->dno; new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno, var = plpgsql_build_variable("sqlerrm", lineno,
plpgsql_build_datatype(TEXTOID, -1), plpgsql_build_datatype(TEXTOID,
-1,
plpgsql_curr_compile->fn_input_collation),
true); true);
((PLpgSQL_var *) var)->isconst = true; ((PLpgSQL_var *) var)->isconst = true;
new->sqlerrm_varno = var->dno; new->sqlerrm_varno = var->dno;
...@@ -3227,7 +3233,8 @@ parse_datatype(const char *string, int location) ...@@ -3227,7 +3233,8 @@ parse_datatype(const char *string, int location)
error_context_stack = syntax_errcontext.previous; error_context_stack = syntax_errcontext.previous;
/* Okay, build a PLpgSQL_type data structure for it */ /* Okay, build a PLpgSQL_type data structure for it */
return plpgsql_build_datatype(type_id, typmod); return plpgsql_build_datatype(type_id, typmod,
plpgsql_curr_compile->fn_input_collation);
} }
/* /*
...@@ -3400,7 +3407,9 @@ make_case(int location, PLpgSQL_expr *t_expr, ...@@ -3400,7 +3407,9 @@ make_case(int location, PLpgSQL_expr *t_expr,
*/ */
t_var = (PLpgSQL_var *) t_var = (PLpgSQL_var *)
plpgsql_build_variable(varname, new->lineno, plpgsql_build_variable(varname, new->lineno,
plpgsql_build_datatype(INT4OID, -1), plpgsql_build_datatype(INT4OID,
-1,
InvalidOid),
true); true);
new->t_varno = t_var->dno; new->t_varno = t_var->dno;
......
This diff is collapsed.
...@@ -1516,7 +1516,9 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt) ...@@ -1516,7 +1516,9 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
* this doesn't affect the originally stored function parse tree. * this doesn't affect the originally stored function parse tree.
*/ */
if (t_var->datatype->typoid != t_oid) if (t_var->datatype->typoid != t_oid)
t_var->datatype = plpgsql_build_datatype(t_oid, -1); t_var->datatype = plpgsql_build_datatype(t_oid,
-1,
estate->func->fn_input_collation);
/* now we can assign to the variable */ /* now we can assign to the variable */
exec_assign_value(estate, exec_assign_value(estate,
...@@ -4307,33 +4309,64 @@ exec_get_datum_type(PLpgSQL_execstate *estate, ...@@ -4307,33 +4309,64 @@ exec_get_datum_type(PLpgSQL_execstate *estate,
} }
/* /*
* exec_get_rec_fieldtype Get datatype of a PLpgSQL record field * exec_get_datum_collation Get collation of a PLpgSQL_datum
*
* Also returns the field number to *fieldno.
*/ */
Oid Oid
exec_get_rec_fieldtype(PLpgSQL_rec *rec, const char *fieldname, exec_get_datum_collation(PLpgSQL_execstate *estate,
int *fieldno) PLpgSQL_datum *datum)
{ {
Oid typeid; Oid collid;
int fno;
if (rec->tupdesc == NULL) switch (datum->dtype)
ereport(ERROR, {
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), case PLPGSQL_DTYPE_VAR:
errmsg("record \"%s\" is not assigned yet", {
rec->refname), PLpgSQL_var *var = (PLpgSQL_var *) datum;
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
fno = SPI_fnumber(rec->tupdesc, fieldname);
if (fno == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, fieldname)));
typeid = SPI_gettypeid(rec->tupdesc, fno);
*fieldno = fno; collid = var->datatype->collation;
return typeid; break;
}
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_REC:
/* composite types are never collatable */
collid = InvalidOid;
break;
case PLPGSQL_DTYPE_RECFIELD:
{
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
PLpgSQL_rec *rec;
int fno;
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
if (rec->tupdesc == NULL)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("record \"%s\" is not assigned yet",
rec->refname),
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
if (fno == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, recfield->fieldname)));
/* XXX there's no SPI_getcollid, as yet */
if (fno > 0)
collid = rec->tupdesc->attrs[fno - 1]->attcollation;
else /* no system column types have collation */
collid = InvalidOid;
break;
}
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
collid = InvalidOid; /* keep compiler quiet */
break;
}
return collid;
} }
/* ---------- /* ----------
......
...@@ -167,6 +167,7 @@ typedef struct ...@@ -167,6 +167,7 @@ typedef struct
bool typbyval; bool typbyval;
Oid typrelid; Oid typrelid;
Oid typioparam; Oid typioparam;
Oid collation; /* from pg_type, but can be overridden */
FmgrInfo typinput; /* lookup info for typinput function */ FmgrInfo typinput; /* lookup info for typinput function */
int32 atttypmod; /* typmod (taken from someplace else) */ int32 atttypmod; /* typmod (taken from someplace else) */
} PLpgSQL_type; } PLpgSQL_type;
...@@ -634,12 +635,19 @@ typedef struct PLpgSQL_func_hashkey ...@@ -634,12 +635,19 @@ typedef struct PLpgSQL_func_hashkey
/* /*
* For a trigger function, the OID of the relation triggered on is part of * For a trigger function, the OID of the relation triggered on is part of
* the hashkey --- we want to compile the trigger separately for each * the hash key --- we want to compile the trigger separately for each
* relation it is used with, in case the rowtype is different. Zero if * relation it is used with, in case the rowtype is different. Zero if
* not called as a trigger. * not called as a trigger.
*/ */
Oid trigrelOid; Oid trigrelOid;
/*
* We must include the input collation as part of the hash key too,
* because we have to generate different plans (with different Param
* collations) for different collation settings.
*/
Oid inputCollation;
/* /*
* We include actual argument types in the hash key to support polymorphic * We include actual argument types in the hash key to support polymorphic
* PLpgSQL functions. Be careful that extra positions are zeroed! * PLpgSQL functions. Be careful that extra positions are zeroed!
...@@ -655,6 +663,7 @@ typedef struct PLpgSQL_function ...@@ -655,6 +663,7 @@ typedef struct PLpgSQL_function
TransactionId fn_xmin; TransactionId fn_xmin;
ItemPointerData fn_tid; ItemPointerData fn_tid;
bool fn_is_trigger; bool fn_is_trigger;
Oid fn_input_collation;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */ PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt; MemoryContext fn_cxt;
...@@ -860,7 +869,8 @@ extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident); ...@@ -860,7 +869,8 @@ extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident);
extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents); extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents);
extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident); extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident);
extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents); extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents);
extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod); extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod,
Oid collation);
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno, extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
PLpgSQL_type *dtype, PLpgSQL_type *dtype,
bool add2namespace); bool add2namespace);
...@@ -895,8 +905,8 @@ extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid, ...@@ -895,8 +905,8 @@ extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
SubTransactionId parentSubid, void *arg); SubTransactionId parentSubid, void *arg);
extern Oid exec_get_datum_type(PLpgSQL_execstate *estate, extern Oid exec_get_datum_type(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum); PLpgSQL_datum *datum);
extern Oid exec_get_rec_fieldtype(PLpgSQL_rec *rec, const char *fieldname, extern Oid exec_get_datum_collation(PLpgSQL_execstate *estate,
int *fieldno); PLpgSQL_datum *datum);
/* ---------- /* ----------
* Functions for namespace handling in pl_funcs.c * Functions for namespace handling in pl_funcs.c
......
...@@ -686,57 +686,61 @@ SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2; ...@@ -686,57 +686,61 @@ SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2;
2 | äbc 2 | äbc
(4 rows) (4 rows)
-- propagation of collation in inlined and non-inlined cases -- propagation of collation in SQL functions (inlined and non-inlined cases)
-- and plpgsql functions too
CREATE FUNCTION mylt (text, text) RETURNS boolean LANGUAGE sql CREATE FUNCTION mylt (text, text) RETURNS boolean LANGUAGE sql
AS $$ select $1 < $2 $$; AS $$ select $1 < $2 $$;
CREATE FUNCTION mylt_noninline (text, text) RETURNS boolean LANGUAGE sql CREATE FUNCTION mylt_noninline (text, text) RETURNS boolean LANGUAGE sql
AS $$ select $1 < $2 limit 1 $$; AS $$ select $1 < $2 limit 1 $$;
CREATE FUNCTION mylt_plpgsql (text, text) RETURNS boolean LANGUAGE plpgsql
AS $$ begin return $1 < $2; end $$;
SELECT a.b AS a, b.b AS b, a.b < b.b AS lt, SELECT a.b AS a, b.b AS b, a.b < b.b AS lt,
mylt(a.b, b.b), mylt_noninline(a.b, b.b) mylt(a.b, b.b), mylt_noninline(a.b, b.b), mylt_plpgsql(a.b, b.b)
FROM collate_test1 a, collate_test1 b FROM collate_test1 a, collate_test1 b
ORDER BY a.b, b.b; ORDER BY a.b, b.b;
a | b | lt | mylt | mylt_noninline a | b | lt | mylt | mylt_noninline | mylt_plpgsql
-----+-----+----+------+---------------- -----+-----+----+------+----------------+--------------
abc | abc | f | f | f abc | abc | f | f | f | f
abc | ABC | t | t | t abc | ABC | t | t | t | t
abc | äbc | t | t | t abc | äbc | t | t | t | t
abc | bbc | t | t | t abc | bbc | t | t | t | t
ABC | abc | f | f | f ABC | abc | f | f | f | f
ABC | ABC | f | f | f ABC | ABC | f | f | f | f
ABC | äbc | t | t | t ABC | äbc | t | t | t | t
ABC | bbc | t | t | t ABC | bbc | t | t | t | t
äbc | abc | f | f | f äbc | abc | f | f | f | f
äbc | ABC | f | f | f äbc | ABC | f | f | f | f
äbc | äbc | f | f | f äbc | äbc | f | f | f | f
äbc | bbc | t | t | t äbc | bbc | t | t | t | t
bbc | abc | f | f | f bbc | abc | f | f | f | f
bbc | ABC | f | f | f bbc | ABC | f | f | f | f
bbc | äbc | f | f | f bbc | äbc | f | f | f | f
bbc | bbc | f | f | f bbc | bbc | f | f | f | f
(16 rows) (16 rows)
SELECT a.b AS a, b.b AS b, a.b < b.b COLLATE "C" AS lt, SELECT a.b AS a, b.b AS b, a.b < b.b COLLATE "C" AS lt,
mylt(a.b, b.b COLLATE "C"), mylt_noninline(a.b, b.b COLLATE "C") mylt(a.b, b.b COLLATE "C"), mylt_noninline(a.b, b.b COLLATE "C"),
mylt_plpgsql(a.b, b.b COLLATE "C")
FROM collate_test1 a, collate_test1 b FROM collate_test1 a, collate_test1 b
ORDER BY a.b, b.b; ORDER BY a.b, b.b;
a | b | lt | mylt | mylt_noninline a | b | lt | mylt | mylt_noninline | mylt_plpgsql
-----+-----+----+------+---------------- -----+-----+----+------+----------------+--------------
abc | abc | f | f | f abc | abc | f | f | f | f
abc | ABC | f | f | f abc | ABC | f | f | f | f
abc | äbc | t | t | t abc | äbc | t | t | t | t
abc | bbc | t | t | t abc | bbc | t | t | t | t
ABC | abc | t | t | t ABC | abc | t | t | t | t
ABC | ABC | f | f | f ABC | ABC | f | f | f | f
ABC | äbc | t | t | t ABC | äbc | t | t | t | t
ABC | bbc | t | t | t ABC | bbc | t | t | t | t
äbc | abc | f | f | f äbc | abc | f | f | f | f
äbc | ABC | f | f | f äbc | ABC | f | f | f | f
äbc | äbc | f | f | f äbc | äbc | f | f | f | f
äbc | bbc | f | f | f äbc | bbc | f | f | f | f
bbc | abc | f | f | f bbc | abc | f | f | f | f
bbc | ABC | f | f | f bbc | ABC | f | f | f | f
bbc | äbc | t | t | t bbc | äbc | t | t | t | t
bbc | bbc | f | f | f bbc | bbc | f | f | f | f
(16 rows) (16 rows)
-- polymorphism -- polymorphism
......
...@@ -212,7 +212,8 @@ SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2; ...@@ -212,7 +212,8 @@ SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2;
SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2; SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2;
-- propagation of collation in inlined and non-inlined cases -- propagation of collation in SQL functions (inlined and non-inlined cases)
-- and plpgsql functions too
CREATE FUNCTION mylt (text, text) RETURNS boolean LANGUAGE sql CREATE FUNCTION mylt (text, text) RETURNS boolean LANGUAGE sql
AS $$ select $1 < $2 $$; AS $$ select $1 < $2 $$;
...@@ -220,13 +221,17 @@ CREATE FUNCTION mylt (text, text) RETURNS boolean LANGUAGE sql ...@@ -220,13 +221,17 @@ CREATE FUNCTION mylt (text, text) RETURNS boolean LANGUAGE sql
CREATE FUNCTION mylt_noninline (text, text) RETURNS boolean LANGUAGE sql CREATE FUNCTION mylt_noninline (text, text) RETURNS boolean LANGUAGE sql
AS $$ select $1 < $2 limit 1 $$; AS $$ select $1 < $2 limit 1 $$;
CREATE FUNCTION mylt_plpgsql (text, text) RETURNS boolean LANGUAGE plpgsql
AS $$ begin return $1 < $2; end $$;
SELECT a.b AS a, b.b AS b, a.b < b.b AS lt, SELECT a.b AS a, b.b AS b, a.b < b.b AS lt,
mylt(a.b, b.b), mylt_noninline(a.b, b.b) mylt(a.b, b.b), mylt_noninline(a.b, b.b), mylt_plpgsql(a.b, b.b)
FROM collate_test1 a, collate_test1 b FROM collate_test1 a, collate_test1 b
ORDER BY a.b, b.b; ORDER BY a.b, b.b;
SELECT a.b AS a, b.b AS b, a.b < b.b COLLATE "C" AS lt, SELECT a.b AS a, b.b AS b, a.b < b.b COLLATE "C" AS lt,
mylt(a.b, b.b COLLATE "C"), mylt_noninline(a.b, b.b COLLATE "C") mylt(a.b, b.b COLLATE "C"), mylt_noninline(a.b, b.b COLLATE "C"),
mylt_plpgsql(a.b, b.b COLLATE "C")
FROM collate_test1 a, collate_test1 b FROM collate_test1 a, collate_test1 b
ORDER BY a.b, b.b; ORDER BY a.b, b.b;
......
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