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.
This commit is contained in:
Tom Lane 2011-03-25 15:06:36 -04:00
parent f6f0916dbd
commit a4425e3200
6 changed files with 207 additions and 102 deletions

View File

@ -487,7 +487,8 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval
new = (PLpgSQL_var *)
plpgsql_build_variable($1.name, $1.lineno,
plpgsql_build_datatype(REFCURSOROID,
-1),
-1,
InvalidOid),
true);
curname_def = palloc0(sizeof(PLpgSQL_expr));
@ -1248,7 +1249,8 @@ for_control : for_variable K_IN
plpgsql_build_variable($1.name,
$1.lineno,
plpgsql_build_datatype(INT4OID,
-1),
-1,
InvalidOid),
true);
new = palloc0(sizeof(PLpgSQL_stmt_fori));
@ -1932,13 +1934,17 @@ exception_sect :
PLpgSQL_variable *var;
var = plpgsql_build_variable("sqlstate", lineno,
plpgsql_build_datatype(TEXTOID, -1),
plpgsql_build_datatype(TEXTOID,
-1,
plpgsql_curr_compile->fn_input_collation),
true);
((PLpgSQL_var *) var)->isconst = true;
new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno,
plpgsql_build_datatype(TEXTOID, -1),
plpgsql_build_datatype(TEXTOID,
-1,
plpgsql_curr_compile->fn_input_collation),
true);
((PLpgSQL_var *) var)->isconst = true;
new->sqlerrm_varno = var->dno;
@ -3227,7 +3233,8 @@ parse_datatype(const char *string, int location)
error_context_stack = syntax_errcontext.previous;
/* 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,
*/
t_var = (PLpgSQL_var *)
plpgsql_build_variable(varname, new->lineno,
plpgsql_build_datatype(INT4OID, -1),
plpgsql_build_datatype(INT4OID,
-1,
InvalidOid),
true);
new->t_varno = t_var->dno;

View File

@ -104,7 +104,7 @@ static Node *resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr,
static Node *make_datum_param(PLpgSQL_expr *expr, int dno, int location);
static PLpgSQL_row *build_row_from_class(Oid classOid);
static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod, Oid collation);
static void compute_function_hashkey(FunctionCallInfo fcinfo,
Form_pg_proc procStruct,
PLpgSQL_func_hashkey *hashkey,
@ -348,6 +348,7 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_tid = procTup->t_self;
function->fn_is_trigger = is_trigger;
function->fn_input_collation = fcinfo->flinfo->fn_collation;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
@ -411,7 +412,9 @@ do_compile(FunctionCallInfo fcinfo,
snprintf(buf, sizeof(buf), "$%d", i + 1);
/* Create datatype info */
argdtype = plpgsql_build_datatype(argtypeid, -1);
argdtype = plpgsql_build_datatype(argtypeid,
-1,
function->fn_input_collation);
/* Disallow pseudotype argument */
/* (note we already replaced polymorphic types) */
@ -556,7 +559,9 @@ do_compile(FunctionCallInfo fcinfo,
num_out_args == 0)
{
(void) plpgsql_build_variable("$0", 0,
build_datatype(typeTup, -1),
build_datatype(typeTup,
-1,
function->fn_input_collation),
true);
}
}
@ -587,61 +592,81 @@ do_compile(FunctionCallInfo fcinfo,
/* Add the variable tg_name */
var = plpgsql_build_variable("tg_name", 0,
plpgsql_build_datatype(NAMEOID, -1),
plpgsql_build_datatype(NAMEOID,
-1,
InvalidOid),
true);
function->tg_name_varno = var->dno;
/* Add the variable tg_when */
var = plpgsql_build_variable("tg_when", 0,
plpgsql_build_datatype(TEXTOID, -1),
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation),
true);
function->tg_when_varno = var->dno;
/* Add the variable tg_level */
var = plpgsql_build_variable("tg_level", 0,
plpgsql_build_datatype(TEXTOID, -1),
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation),
true);
function->tg_level_varno = var->dno;
/* Add the variable tg_op */
var = plpgsql_build_variable("tg_op", 0,
plpgsql_build_datatype(TEXTOID, -1),
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation),
true);
function->tg_op_varno = var->dno;
/* Add the variable tg_relid */
var = plpgsql_build_variable("tg_relid", 0,
plpgsql_build_datatype(OIDOID, -1),
plpgsql_build_datatype(OIDOID,
-1,
InvalidOid),
true);
function->tg_relid_varno = var->dno;
/* Add the variable tg_relname */
var = plpgsql_build_variable("tg_relname", 0,
plpgsql_build_datatype(NAMEOID, -1),
plpgsql_build_datatype(NAMEOID,
-1,
InvalidOid),
true);
function->tg_relname_varno = var->dno;
/* tg_table_name is now preferred to tg_relname */
var = plpgsql_build_variable("tg_table_name", 0,
plpgsql_build_datatype(NAMEOID, -1),
plpgsql_build_datatype(NAMEOID,
-1,
InvalidOid),
true);
function->tg_table_name_varno = var->dno;
/* add the variable tg_table_schema */
var = plpgsql_build_variable("tg_table_schema", 0,
plpgsql_build_datatype(NAMEOID, -1),
plpgsql_build_datatype(NAMEOID,
-1,
InvalidOid),
true);
function->tg_table_schema_varno = var->dno;
/* Add the variable tg_nargs */
var = plpgsql_build_variable("tg_nargs", 0,
plpgsql_build_datatype(INT4OID, -1),
plpgsql_build_datatype(INT4OID,
-1,
InvalidOid),
true);
function->tg_nargs_varno = var->dno;
/* Add the variable tg_argv */
var = plpgsql_build_variable("tg_argv", 0,
plpgsql_build_datatype(TEXTARRAYOID, -1),
plpgsql_build_datatype(TEXTARRAYOID,
-1,
function->fn_input_collation),
true);
function->tg_argv_varno = var->dno;
@ -659,7 +684,9 @@ do_compile(FunctionCallInfo fcinfo,
* Create the magic FOUND variable.
*/
var = plpgsql_build_variable("found", 0,
plpgsql_build_datatype(BOOLOID, -1),
plpgsql_build_datatype(BOOLOID,
-1,
InvalidOid),
true);
function->found_varno = var->dno;
@ -777,6 +804,7 @@ plpgsql_compile_inline(char *proc_source)
function->fn_name = pstrdup(func_name);
function->fn_is_trigger = false;
function->fn_input_collation = InvalidOid;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
@ -810,7 +838,9 @@ plpgsql_compile_inline(char *proc_source)
* Create the magic FOUND variable.
*/
var = plpgsql_build_variable("found", 0,
plpgsql_build_datatype(BOOLOID, -1),
plpgsql_build_datatype(BOOLOID,
-1,
InvalidOid),
true);
function->found_varno = var->dno;
@ -1218,13 +1248,14 @@ static Node *
make_datum_param(PLpgSQL_expr *expr, int dno, int location)
{
PLpgSQL_execstate *estate;
PLpgSQL_datum *datum;
Param *param;
MemoryContext oldcontext;
/* see comment in resolve_column_ref */
estate = expr->func->cur_estate;
Assert(dno >= 0 && dno < estate->ndatums);
datum = estate->datums[dno];
/*
* Bitmapset must be allocated in function's permanent memory context
@ -1236,9 +1267,9 @@ make_datum_param(PLpgSQL_expr *expr, int dno, int location)
param = makeNode(Param);
param->paramkind = PARAM_EXTERN;
param->paramid = dno + 1;
param->paramtype = exec_get_datum_type(estate, estate->datums[dno]);
param->paramtype = exec_get_datum_type(estate, datum);
param->paramtypmod = -1;
param->paramcollid = get_typcollation(param->paramtype);
param->paramcollid = exec_get_datum_collation(estate, datum);
param->location = location;
return (Node *) param;
@ -1578,7 +1609,8 @@ plpgsql_parse_wordtype(char *ident)
return NULL;
}
dtype = build_datatype(typeTup, -1);
dtype = build_datatype(typeTup, -1,
plpgsql_curr_compile->fn_input_collation);
ReleaseSysCache(typeTup);
return dtype;
@ -1687,7 +1719,9 @@ plpgsql_parse_cwordtype(List *idents)
* return it
*/
MemoryContextSwitchTo(oldCxt);
dtype = build_datatype(typetup, attrStruct->atttypmod);
dtype = build_datatype(typetup,
attrStruct->atttypmod,
attrStruct->attcollation);
MemoryContextSwitchTo(compile_tmp_cxt);
done:
@ -1720,7 +1754,7 @@ plpgsql_parse_wordrowtype(char *ident)
errmsg("relation \"%s\" does not exist", ident)));
/* Build and return the row type struct */
return plpgsql_build_datatype(get_rel_type_id(classOid), -1);
return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid);
}
/* ----------
@ -1755,7 +1789,7 @@ plpgsql_parse_cwordrowtype(List *idents)
MemoryContextSwitchTo(oldCxt);
/* Build and return the row type struct */
return plpgsql_build_datatype(get_rel_type_id(classOid), -1);
return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid);
}
/*
@ -1935,7 +1969,8 @@ build_row_from_class(Oid classOid)
*/
var = plpgsql_build_variable(refname, 0,
plpgsql_build_datatype(attrStruct->atttypid,
attrStruct->atttypmod),
attrStruct->atttypmod,
attrStruct->attcollation),
false);
/* Add the variable to the row */
@ -2013,10 +2048,13 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars)
/*
* plpgsql_build_datatype
* Build PLpgSQL_type struct given type OID and typmod.
* Build PLpgSQL_type struct given type OID, typmod, and collation.
*
* If collation is not InvalidOid then it overrides the type's default
* collation. But collation is ignored if the datatype is non-collatable.
*/
PLpgSQL_type *
plpgsql_build_datatype(Oid typeOid, int32 typmod)
plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation)
{
HeapTuple typeTup;
PLpgSQL_type *typ;
@ -2025,7 +2063,7 @@ plpgsql_build_datatype(Oid typeOid, int32 typmod)
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u", typeOid);
typ = build_datatype(typeTup, typmod);
typ = build_datatype(typeTup, typmod, collation);
ReleaseSysCache(typeTup);
@ -2036,7 +2074,7 @@ plpgsql_build_datatype(Oid typeOid, int32 typmod)
* Utility subroutine to make a PLpgSQL_type struct given a pg_type entry
*/
static PLpgSQL_type *
build_datatype(HeapTuple typeTup, int32 typmod)
build_datatype(HeapTuple typeTup, int32 typmod, Oid collation)
{
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
PLpgSQL_type *typ;
@ -2077,6 +2115,9 @@ build_datatype(HeapTuple typeTup, int32 typmod)
typ->typbyval = typeStruct->typbyval;
typ->typrelid = typeStruct->typrelid;
typ->typioparam = getTypeIOParam(typeTup);
typ->collation = typeStruct->typcollation;
if (OidIsValid(collation) && OidIsValid(typ->collation))
typ->collation = collation;
fmgr_info(typeStruct->typinput, &(typ->typinput));
typ->atttypmod = typmod;
@ -2285,6 +2326,9 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
hashkey->trigrelOid = RelationGetRelid(trigdata->tg_relation);
}
/* get input collation, if known */
hashkey->inputCollation = fcinfo->flinfo->fn_collation;
if (procStruct->pronargs > 0)
{
/* get the argument types */

View File

@ -1516,7 +1516,9 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
* this doesn't affect the originally stored function parse tree.
*/
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 */
exec_assign_value(estate,
@ -4307,33 +4309,64 @@ exec_get_datum_type(PLpgSQL_execstate *estate,
}
/*
* exec_get_rec_fieldtype Get datatype of a PLpgSQL record field
*
* Also returns the field number to *fieldno.
* exec_get_datum_collation Get collation of a PLpgSQL_datum
*/
Oid
exec_get_rec_fieldtype(PLpgSQL_rec *rec, const char *fieldname,
int *fieldno)
exec_get_datum_collation(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum)
{
Oid typeid;
int fno;
Oid collid;
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, 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);
switch (datum->dtype)
{
case PLPGSQL_DTYPE_VAR:
{
PLpgSQL_var *var = (PLpgSQL_var *) datum;
*fieldno = fno;
return typeid;
collid = var->datatype->collation;
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;
}
/* ----------

View File

@ -167,6 +167,7 @@ typedef struct
bool typbyval;
Oid typrelid;
Oid typioparam;
Oid collation; /* from pg_type, but can be overridden */
FmgrInfo typinput; /* lookup info for typinput function */
int32 atttypmod; /* typmod (taken from someplace else) */
} PLpgSQL_type;
@ -634,12 +635,19 @@ typedef struct PLpgSQL_func_hashkey
/*
* 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
* not called as a trigger.
*/
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
* PLpgSQL functions. Be careful that extra positions are zeroed!
@ -655,6 +663,7 @@ typedef struct PLpgSQL_function
TransactionId fn_xmin;
ItemPointerData fn_tid;
bool fn_is_trigger;
Oid fn_input_collation;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt;
@ -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_wordrowtype(char *ident);
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,
PLpgSQL_type *dtype,
bool add2namespace);
@ -895,8 +905,8 @@ extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
SubTransactionId parentSubid, void *arg);
extern Oid exec_get_datum_type(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum);
extern Oid exec_get_rec_fieldtype(PLpgSQL_rec *rec, const char *fieldname,
int *fieldno);
extern Oid exec_get_datum_collation(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum);
/* ----------
* Functions for namespace handling in pl_funcs.c

View File

@ -686,57 +686,61 @@ SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2;
2 | äbc
(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
AS $$ select $1 < $2 $$;
CREATE FUNCTION mylt_noninline (text, text) RETURNS boolean LANGUAGE sql
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,
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
ORDER BY a.b, b.b;
a | b | lt | mylt | mylt_noninline
-----+-----+----+------+----------------
abc | abc | f | f | f
abc | ABC | t | t | t
abc | äbc | t | t | t
abc | bbc | t | t | t
ABC | abc | f | f | f
ABC | ABC | f | f | f
ABC | äbc | t | t | t
ABC | bbc | t | t | t
äbc | abc | f | f | f
äbc | ABC | f | f | f
äbc | äbc | f | f | f
äbc | bbc | t | t | t
bbc | abc | f | f | f
bbc | ABC | f | f | f
bbc | äbc | f | f | f
bbc | bbc | f | f | f
a | b | lt | mylt | mylt_noninline | mylt_plpgsql
-----+-----+----+------+----------------+--------------
abc | abc | f | f | f | f
abc | ABC | t | t | t | t
abc | äbc | t | t | t | t
abc | bbc | t | t | t | t
ABC | abc | f | f | f | f
ABC | ABC | f | f | f | f
ABC | äbc | t | t | t | t
ABC | bbc | t | t | t | t
äbc | abc | f | f | f | f
äbc | ABC | f | f | f | f
äbc | äbc | f | f | f | f
äbc | bbc | t | t | t | t
bbc | abc | f | f | f | f
bbc | ABC | f | f | f | f
bbc | äbc | f | f | f | f
bbc | bbc | f | f | f | f
(16 rows)
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
ORDER BY a.b, b.b;
a | b | lt | mylt | mylt_noninline
-----+-----+----+------+----------------
abc | abc | f | f | f
abc | ABC | f | f | f
abc | äbc | t | t | t
abc | bbc | t | t | t
ABC | abc | t | t | t
ABC | ABC | f | f | f
ABC | äbc | t | t | t
ABC | bbc | t | t | t
äbc | abc | f | f | f
äbc | ABC | f | f | f
äbc | äbc | f | f | f
äbc | bbc | f | f | f
bbc | abc | f | f | f
bbc | ABC | f | f | f
bbc | äbc | t | t | t
bbc | bbc | f | f | f
a | b | lt | mylt | mylt_noninline | mylt_plpgsql
-----+-----+----+------+----------------+--------------
abc | abc | f | f | f | f
abc | ABC | f | f | f | f
abc | äbc | t | t | t | t
abc | bbc | t | t | t | t
ABC | abc | t | t | t | t
ABC | ABC | f | f | f | f
ABC | äbc | t | t | t | t
ABC | bbc | t | t | t | t
äbc | abc | f | f | f | f
äbc | ABC | f | f | f | f
äbc | äbc | f | f | f | f
äbc | bbc | f | f | f | f
bbc | abc | f | f | f | f
bbc | ABC | f | f | f | f
bbc | äbc | t | t | t | t
bbc | bbc | f | f | f | f
(16 rows)
-- polymorphism

View File

@ -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;
-- 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
AS $$ select $1 < $2 $$;
@ -220,13 +221,17 @@ CREATE FUNCTION mylt (text, text) RETURNS boolean LANGUAGE sql
CREATE FUNCTION mylt_noninline (text, text) RETURNS boolean LANGUAGE sql
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,
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
ORDER BY a.b, b.b;
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
ORDER BY a.b, b.b;