Skip to content

Commit a4425e3

Browse files
committed
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.
1 parent f6f0916 commit a4425e3

File tree

6 files changed

+207
-102
lines changed

6 files changed

+207
-102
lines changed

src/pl/plpgsql/src/gram.y

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,8 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval
487487
new = (PLpgSQL_var *)
488488
plpgsql_build_variable($1.name, $1.lineno,
489489
plpgsql_build_datatype(REFCURSOROID,
490-
-1),
490+
-1,
491+
InvalidOid),
491492
true);
492493

493494
curname_def = palloc0(sizeof(PLpgSQL_expr));
@@ -1248,7 +1249,8 @@ for_control : for_variable K_IN
12481249
plpgsql_build_variable($1.name,
12491250
$1.lineno,
12501251
plpgsql_build_datatype(INT4OID,
1251-
-1),
1252+
-1,
1253+
InvalidOid),
12521254
true);
12531255

12541256
new = palloc0(sizeof(PLpgSQL_stmt_fori));
@@ -1932,13 +1934,17 @@ exception_sect :
19321934
PLpgSQL_variable *var;
19331935

19341936
var = plpgsql_build_variable("sqlstate", lineno,
1935-
plpgsql_build_datatype(TEXTOID, -1),
1937+
plpgsql_build_datatype(TEXTOID,
1938+
-1,
1939+
plpgsql_curr_compile->fn_input_collation),
19361940
true);
19371941
((PLpgSQL_var *) var)->isconst = true;
19381942
new->sqlstate_varno = var->dno;
19391943

19401944
var = plpgsql_build_variable("sqlerrm", lineno,
1941-
plpgsql_build_datatype(TEXTOID, -1),
1945+
plpgsql_build_datatype(TEXTOID,
1946+
-1,
1947+
plpgsql_curr_compile->fn_input_collation),
19421948
true);
19431949
((PLpgSQL_var *) var)->isconst = true;
19441950
new->sqlerrm_varno = var->dno;
@@ -3227,7 +3233,8 @@ parse_datatype(const char *string, int location)
32273233
error_context_stack = syntax_errcontext.previous;
32283234

32293235
/* Okay, build a PLpgSQL_type data structure for it */
3230-
return plpgsql_build_datatype(type_id, typmod);
3236+
return plpgsql_build_datatype(type_id, typmod,
3237+
plpgsql_curr_compile->fn_input_collation);
32313238
}
32323239

32333240
/*
@@ -3400,7 +3407,9 @@ make_case(int location, PLpgSQL_expr *t_expr,
34003407
*/
34013408
t_var = (PLpgSQL_var *)
34023409
plpgsql_build_variable(varname, new->lineno,
3403-
plpgsql_build_datatype(INT4OID, -1),
3410+
plpgsql_build_datatype(INT4OID,
3411+
-1,
3412+
InvalidOid),
34043413
true);
34053414
new->t_varno = t_var->dno;
34063415

src/pl/plpgsql/src/pl_comp.c

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ static Node *resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr,
104104
static Node *make_datum_param(PLpgSQL_expr *expr, int dno, int location);
105105
static PLpgSQL_row *build_row_from_class(Oid classOid);
106106
static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);
107-
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
107+
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod, Oid collation);
108108
static void compute_function_hashkey(FunctionCallInfo fcinfo,
109109
Form_pg_proc procStruct,
110110
PLpgSQL_func_hashkey *hashkey,
@@ -348,6 +348,7 @@ do_compile(FunctionCallInfo fcinfo,
348348
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
349349
function->fn_tid = procTup->t_self;
350350
function->fn_is_trigger = is_trigger;
351+
function->fn_input_collation = fcinfo->flinfo->fn_collation;
351352
function->fn_cxt = func_cxt;
352353
function->out_param_varno = -1; /* set up for no OUT param */
353354
function->resolve_option = plpgsql_variable_conflict;
@@ -411,7 +412,9 @@ do_compile(FunctionCallInfo fcinfo,
411412
snprintf(buf, sizeof(buf), "$%d", i + 1);
412413

413414
/* Create datatype info */
414-
argdtype = plpgsql_build_datatype(argtypeid, -1);
415+
argdtype = plpgsql_build_datatype(argtypeid,
416+
-1,
417+
function->fn_input_collation);
415418

416419
/* Disallow pseudotype argument */
417420
/* (note we already replaced polymorphic types) */
@@ -556,7 +559,9 @@ do_compile(FunctionCallInfo fcinfo,
556559
num_out_args == 0)
557560
{
558561
(void) plpgsql_build_variable("$0", 0,
559-
build_datatype(typeTup, -1),
562+
build_datatype(typeTup,
563+
-1,
564+
function->fn_input_collation),
560565
true);
561566
}
562567
}
@@ -587,61 +592,81 @@ do_compile(FunctionCallInfo fcinfo,
587592

588593
/* Add the variable tg_name */
589594
var = plpgsql_build_variable("tg_name", 0,
590-
plpgsql_build_datatype(NAMEOID, -1),
595+
plpgsql_build_datatype(NAMEOID,
596+
-1,
597+
InvalidOid),
591598
true);
592599
function->tg_name_varno = var->dno;
593600

594601
/* Add the variable tg_when */
595602
var = plpgsql_build_variable("tg_when", 0,
596-
plpgsql_build_datatype(TEXTOID, -1),
603+
plpgsql_build_datatype(TEXTOID,
604+
-1,
605+
function->fn_input_collation),
597606
true);
598607
function->tg_when_varno = var->dno;
599608

600609
/* Add the variable tg_level */
601610
var = plpgsql_build_variable("tg_level", 0,
602-
plpgsql_build_datatype(TEXTOID, -1),
611+
plpgsql_build_datatype(TEXTOID,
612+
-1,
613+
function->fn_input_collation),
603614
true);
604615
function->tg_level_varno = var->dno;
605616

606617
/* Add the variable tg_op */
607618
var = plpgsql_build_variable("tg_op", 0,
608-
plpgsql_build_datatype(TEXTOID, -1),
619+
plpgsql_build_datatype(TEXTOID,
620+
-1,
621+
function->fn_input_collation),
609622
true);
610623
function->tg_op_varno = var->dno;
611624

612625
/* Add the variable tg_relid */
613626
var = plpgsql_build_variable("tg_relid", 0,
614-
plpgsql_build_datatype(OIDOID, -1),
627+
plpgsql_build_datatype(OIDOID,
628+
-1,
629+
InvalidOid),
615630
true);
616631
function->tg_relid_varno = var->dno;
617632

618633
/* Add the variable tg_relname */
619634
var = plpgsql_build_variable("tg_relname", 0,
620-
plpgsql_build_datatype(NAMEOID, -1),
635+
plpgsql_build_datatype(NAMEOID,
636+
-1,
637+
InvalidOid),
621638
true);
622639
function->tg_relname_varno = var->dno;
623640

624641
/* tg_table_name is now preferred to tg_relname */
625642
var = plpgsql_build_variable("tg_table_name", 0,
626-
plpgsql_build_datatype(NAMEOID, -1),
643+
plpgsql_build_datatype(NAMEOID,
644+
-1,
645+
InvalidOid),
627646
true);
628647
function->tg_table_name_varno = var->dno;
629648

630649
/* add the variable tg_table_schema */
631650
var = plpgsql_build_variable("tg_table_schema", 0,
632-
plpgsql_build_datatype(NAMEOID, -1),
651+
plpgsql_build_datatype(NAMEOID,
652+
-1,
653+
InvalidOid),
633654
true);
634655
function->tg_table_schema_varno = var->dno;
635656

636657
/* Add the variable tg_nargs */
637658
var = plpgsql_build_variable("tg_nargs", 0,
638-
plpgsql_build_datatype(INT4OID, -1),
659+
plpgsql_build_datatype(INT4OID,
660+
-1,
661+
InvalidOid),
639662
true);
640663
function->tg_nargs_varno = var->dno;
641664

642665
/* Add the variable tg_argv */
643666
var = plpgsql_build_variable("tg_argv", 0,
644-
plpgsql_build_datatype(TEXTARRAYOID, -1),
667+
plpgsql_build_datatype(TEXTARRAYOID,
668+
-1,
669+
function->fn_input_collation),
645670
true);
646671
function->tg_argv_varno = var->dno;
647672

@@ -659,7 +684,9 @@ do_compile(FunctionCallInfo fcinfo,
659684
* Create the magic FOUND variable.
660685
*/
661686
var = plpgsql_build_variable("found", 0,
662-
plpgsql_build_datatype(BOOLOID, -1),
687+
plpgsql_build_datatype(BOOLOID,
688+
-1,
689+
InvalidOid),
663690
true);
664691
function->found_varno = var->dno;
665692

@@ -777,6 +804,7 @@ plpgsql_compile_inline(char *proc_source)
777804

778805
function->fn_name = pstrdup(func_name);
779806
function->fn_is_trigger = false;
807+
function->fn_input_collation = InvalidOid;
780808
function->fn_cxt = func_cxt;
781809
function->out_param_varno = -1; /* set up for no OUT param */
782810
function->resolve_option = plpgsql_variable_conflict;
@@ -810,7 +838,9 @@ plpgsql_compile_inline(char *proc_source)
810838
* Create the magic FOUND variable.
811839
*/
812840
var = plpgsql_build_variable("found", 0,
813-
plpgsql_build_datatype(BOOLOID, -1),
841+
plpgsql_build_datatype(BOOLOID,
842+
-1,
843+
InvalidOid),
814844
true);
815845
function->found_varno = var->dno;
816846

@@ -1218,13 +1248,14 @@ static Node *
12181248
make_datum_param(PLpgSQL_expr *expr, int dno, int location)
12191249
{
12201250
PLpgSQL_execstate *estate;
1251+
PLpgSQL_datum *datum;
12211252
Param *param;
12221253
MemoryContext oldcontext;
12231254

12241255
/* see comment in resolve_column_ref */
12251256
estate = expr->func->cur_estate;
1226-
12271257
Assert(dno >= 0 && dno < estate->ndatums);
1258+
datum = estate->datums[dno];
12281259

12291260
/*
12301261
* Bitmapset must be allocated in function's permanent memory context
@@ -1236,9 +1267,9 @@ make_datum_param(PLpgSQL_expr *expr, int dno, int location)
12361267
param = makeNode(Param);
12371268
param->paramkind = PARAM_EXTERN;
12381269
param->paramid = dno + 1;
1239-
param->paramtype = exec_get_datum_type(estate, estate->datums[dno]);
1270+
param->paramtype = exec_get_datum_type(estate, datum);
12401271
param->paramtypmod = -1;
1241-
param->paramcollid = get_typcollation(param->paramtype);
1272+
param->paramcollid = exec_get_datum_collation(estate, datum);
12421273
param->location = location;
12431274

12441275
return (Node *) param;
@@ -1578,7 +1609,8 @@ plpgsql_parse_wordtype(char *ident)
15781609
return NULL;
15791610
}
15801611

1581-
dtype = build_datatype(typeTup, -1);
1612+
dtype = build_datatype(typeTup, -1,
1613+
plpgsql_curr_compile->fn_input_collation);
15821614

15831615
ReleaseSysCache(typeTup);
15841616
return dtype;
@@ -1687,7 +1719,9 @@ plpgsql_parse_cwordtype(List *idents)
16871719
* return it
16881720
*/
16891721
MemoryContextSwitchTo(oldCxt);
1690-
dtype = build_datatype(typetup, attrStruct->atttypmod);
1722+
dtype = build_datatype(typetup,
1723+
attrStruct->atttypmod,
1724+
attrStruct->attcollation);
16911725
MemoryContextSwitchTo(compile_tmp_cxt);
16921726

16931727
done:
@@ -1720,7 +1754,7 @@ plpgsql_parse_wordrowtype(char *ident)
17201754
errmsg("relation \"%s\" does not exist", ident)));
17211755

17221756
/* Build and return the row type struct */
1723-
return plpgsql_build_datatype(get_rel_type_id(classOid), -1);
1757+
return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid);
17241758
}
17251759

17261760
/* ----------
@@ -1755,7 +1789,7 @@ plpgsql_parse_cwordrowtype(List *idents)
17551789
MemoryContextSwitchTo(oldCxt);
17561790

17571791
/* Build and return the row type struct */
1758-
return plpgsql_build_datatype(get_rel_type_id(classOid), -1);
1792+
return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid);
17591793
}
17601794

17611795
/*
@@ -1935,7 +1969,8 @@ build_row_from_class(Oid classOid)
19351969
*/
19361970
var = plpgsql_build_variable(refname, 0,
19371971
plpgsql_build_datatype(attrStruct->atttypid,
1938-
attrStruct->atttypmod),
1972+
attrStruct->atttypmod,
1973+
attrStruct->attcollation),
19391974
false);
19401975

19411976
/* Add the variable to the row */
@@ -2013,10 +2048,13 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars)
20132048

20142049
/*
20152050
* plpgsql_build_datatype
2016-
* Build PLpgSQL_type struct given type OID and typmod.
2051+
* Build PLpgSQL_type struct given type OID, typmod, and collation.
2052+
*
2053+
* If collation is not InvalidOid then it overrides the type's default
2054+
* collation. But collation is ignored if the datatype is non-collatable.
20172055
*/
20182056
PLpgSQL_type *
2019-
plpgsql_build_datatype(Oid typeOid, int32 typmod)
2057+
plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation)
20202058
{
20212059
HeapTuple typeTup;
20222060
PLpgSQL_type *typ;
@@ -2025,7 +2063,7 @@ plpgsql_build_datatype(Oid typeOid, int32 typmod)
20252063
if (!HeapTupleIsValid(typeTup))
20262064
elog(ERROR, "cache lookup failed for type %u", typeOid);
20272065

2028-
typ = build_datatype(typeTup, typmod);
2066+
typ = build_datatype(typeTup, typmod, collation);
20292067

20302068
ReleaseSysCache(typeTup);
20312069

@@ -2036,7 +2074,7 @@ plpgsql_build_datatype(Oid typeOid, int32 typmod)
20362074
* Utility subroutine to make a PLpgSQL_type struct given a pg_type entry
20372075
*/
20382076
static PLpgSQL_type *
2039-
build_datatype(HeapTuple typeTup, int32 typmod)
2077+
build_datatype(HeapTuple typeTup, int32 typmod, Oid collation)
20402078
{
20412079
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
20422080
PLpgSQL_type *typ;
@@ -2077,6 +2115,9 @@ build_datatype(HeapTuple typeTup, int32 typmod)
20772115
typ->typbyval = typeStruct->typbyval;
20782116
typ->typrelid = typeStruct->typrelid;
20792117
typ->typioparam = getTypeIOParam(typeTup);
2118+
typ->collation = typeStruct->typcollation;
2119+
if (OidIsValid(collation) && OidIsValid(typ->collation))
2120+
typ->collation = collation;
20802121
fmgr_info(typeStruct->typinput, &(typ->typinput));
20812122
typ->atttypmod = typmod;
20822123

@@ -2285,6 +2326,9 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
22852326
hashkey->trigrelOid = RelationGetRelid(trigdata->tg_relation);
22862327
}
22872328

2329+
/* get input collation, if known */
2330+
hashkey->inputCollation = fcinfo->flinfo->fn_collation;
2331+
22882332
if (procStruct->pronargs > 0)
22892333
{
22902334
/* get the argument types */

0 commit comments

Comments
 (0)