Allow LEAKPROOF functions for better performance of security views.

We don't normally allow quals to be pushed down into a view created
with the security_barrier option, but functions without side effects
are an exception: they're OK.  This allows much better performance in
common cases, such as when using an equality operator (that might
even be indexable).

There is an outstanding issue here with the CREATE FUNCTION / ALTER
FUNCTION syntax: there's no way to use ALTER FUNCTION to unset the
leakproof flag.  But I'm committing this as-is so that it doesn't
have to be rebased again; we can fix up the grammar in a future
commit.

KaiGai Kohei, with some wordsmithing by me.
This commit is contained in:
Robert Haas 2012-02-13 22:20:27 -05:00
parent 2bbd88f8f8
commit cd30728fb2
28 changed files with 3329 additions and 2427 deletions

View File

@ -4423,6 +4423,18 @@
function)</entry>
</row>
<row>
<entry><structfield>proleakproof</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
<entry>
The function has no side effects. No information about the
arguments is conveyed except via the return value. Any function
that might throw an error depending on the values of its arguments
is not leakproof.
</entry>
</row>
<row>
<entry><structfield>proisstrict</structfield></entry>
<entry><type>bool</type></entry>

View File

@ -33,7 +33,7 @@ ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="paramet
<phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
IMMUTABLE | STABLE | VOLATILE
IMMUTABLE | STABLE | VOLATILE | LEAKPROOF
[ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
COST <replaceable class="parameter">execution_cost</replaceable>
ROWS <replaceable class="parameter">result_rows</replaceable>
@ -191,6 +191,17 @@ ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="paramet
</listitem>
</varlistentry>
<varlistentry>
<term><literal>LEAKPROOF</literal></term>
<listitem>
<para>
Change whether the function is considered leakproof or not.
See <xref linkend="sql-createfunction"> for more information about
this capability.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>COST</literal> <replaceable class="parameter">execution_cost</replaceable></term>

View File

@ -26,7 +26,7 @@ CREATE [ OR REPLACE ] FUNCTION
| RETURNS TABLE ( <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">column_type</replaceable> [, ...] ) ]
{ LANGUAGE <replaceable class="parameter">lang_name</replaceable>
| WINDOW
| IMMUTABLE | STABLE | VOLATILE
| IMMUTABLE | STABLE | VOLATILE | LEAKPROOF
| CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
| [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
| COST <replaceable class="parameter">execution_cost</replaceable>
@ -324,6 +324,23 @@ CREATE [ OR REPLACE ] FUNCTION
</listitem>
</varlistentry>
<varlistentry>
<term><literal>LEAKPROOF</literal></term>
<listitem>
<para>
<literal>LEAKPROOF</literal> indicates that the function has no side
effects. It reveals no information about its arguments other than by
its return value. For example, a function which throws an error message
for some argument values but not others, or which includes the argument
values in any error message, is not leakproof. The query planner may
push leakproof functions (but not others) into views created with the
<literal>security_barrier</literal> option. See
<xref linkend="sql-createview"> and <xref linkend="rules-privileges">.
This option can only be set by the superuser.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>CALLED ON NULL INPUT</literal></term>
<term><literal>RETURNS NULL ON NULL INPUT</literal></term>

View File

@ -1890,6 +1890,20 @@ CREATE VIEW phone_number WITH (security_barrier) AS
enabled by default.
</para>
<para>
The query planner has more flexibility when dealing with functions that
have no side effects. Such functions are referred to as LEAKPROOF, and
include many simple, commonly used operators, such as many equality
operators. The query planner can safely allow such functions to be evaluated
at any point in the query execution process, since invoking them on rows
invisible to the user will not leak any information about the unseen rows.
In contrast, a function that might throw an error depending on the values
received as arguments (such as one that throws an error in the event of
overflow or division by zero) are not leak-proof, and could provide
significant information about the unseen rows if applied before the security
view's row filters.
</para>
<para>
It is important to understand that even a view created with the
<literal>security_barrier</literal> option is intended to be secure only

View File

@ -241,6 +241,7 @@ AggregateCreate(const char *aggName,
false, /* isWindowFunc */
false, /* security invoker (currently not
* definable for agg) */
false, /* isLeakProof */
false, /* isStrict (not needed for agg) */
PROVOLATILE_IMMUTABLE, /* volatility (not
* needed for agg) */

View File

@ -76,6 +76,7 @@ ProcedureCreate(const char *procedureName,
bool isAgg,
bool isWindowFunc,
bool security_definer,
bool isLeakProof,
bool isStrict,
char volatility,
oidvector *parameterTypes,
@ -334,6 +335,7 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_proisagg - 1] = BoolGetDatum(isAgg);
values[Anum_pg_proc_proiswindow - 1] = BoolGetDatum(isWindowFunc);
values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof);
values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict);
values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet);
values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility);

View File

@ -446,6 +446,7 @@ compute_common_attribute(DefElem *defel,
DefElem **volatility_item,
DefElem **strict_item,
DefElem **security_item,
DefElem **leakproof_item,
List **set_items,
DefElem **cost_item,
DefElem **rows_item)
@ -471,6 +472,13 @@ compute_common_attribute(DefElem *defel,
*security_item = defel;
}
else if (strcmp(defel->defname, "leakproof") == 0)
{
if (*leakproof_item)
goto duplicate_error;
*leakproof_item = defel;
}
else if (strcmp(defel->defname, "set") == 0)
{
*set_items = lappend(*set_items, defel->arg);
@ -564,6 +572,7 @@ compute_attributes_sql_style(List *options,
char *volatility_p,
bool *strict_p,
bool *security_definer,
bool *leakproof_p,
ArrayType **proconfig,
float4 *procost,
float4 *prorows)
@ -575,6 +584,7 @@ compute_attributes_sql_style(List *options,
DefElem *volatility_item = NULL;
DefElem *strict_item = NULL;
DefElem *security_item = NULL;
DefElem *leakproof_item = NULL;
List *set_items = NIL;
DefElem *cost_item = NULL;
DefElem *rows_item = NULL;
@ -611,6 +621,7 @@ compute_attributes_sql_style(List *options,
&volatility_item,
&strict_item,
&security_item,
&leakproof_item,
&set_items,
&cost_item,
&rows_item))
@ -653,6 +664,8 @@ compute_attributes_sql_style(List *options,
*strict_p = intVal(strict_item->arg);
if (security_item)
*security_definer = intVal(security_item->arg);
if (leakproof_item)
*leakproof_p = intVal(leakproof_item->arg);
if (set_items)
*proconfig = update_proconfig_value(NULL, set_items);
if (cost_item)
@ -805,7 +818,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
Oid requiredResultType;
bool isWindowFunc,
isStrict,
security;
security,
isLeakProof;
char volatility;
ArrayType *proconfig;
float4 procost;
@ -828,6 +842,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
isWindowFunc = false;
isStrict = false;
security = false;
isLeakProof = false;
volatility = PROVOLATILE_VOLATILE;
proconfig = NULL;
procost = -1; /* indicates not set */
@ -837,7 +852,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
compute_attributes_sql_style(stmt->options,
&as_clause, &language,
&isWindowFunc, &volatility,
&isStrict, &security,
&isStrict, &security, &isLeakProof,
&proconfig, &procost, &prorows);
/* Look up the language and validate permissions */
@ -874,6 +889,16 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
ReleaseSysCache(languageTuple);
/*
* Only superuser is allowed to create leakproof functions because
* it possibly allows unprivileged users to reference invisible tuples
* to be filtered out using views for row-level security.
*/
if (isLeakProof && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof function")));
/*
* Convert remaining parameters of CREATE to form wanted by
* ProcedureCreate.
@ -960,6 +985,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
false, /* not an aggregate */
isWindowFunc,
security,
isLeakProof,
isStrict,
volatility,
parameterTypes,
@ -1238,6 +1264,7 @@ AlterFunction(AlterFunctionStmt *stmt)
DefElem *volatility_item = NULL;
DefElem *strict_item = NULL;
DefElem *security_def_item = NULL;
DefElem *leakproof_item = NULL;
List *set_items = NIL;
DefElem *cost_item = NULL;
DefElem *rows_item = NULL;
@ -1274,6 +1301,7 @@ AlterFunction(AlterFunctionStmt *stmt)
&volatility_item,
&strict_item,
&security_def_item,
&leakproof_item,
&set_items,
&cost_item,
&rows_item) == false)
@ -1286,6 +1314,14 @@ AlterFunction(AlterFunctionStmt *stmt)
procForm->proisstrict = intVal(strict_item->arg);
if (security_def_item)
procForm->prosecdef = intVal(security_def_item->arg);
if (leakproof_item)
{
if (intVal(leakproof_item->arg) && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof function")));
procForm->proleakproof = intVal(leakproof_item->arg);
}
if (cost_item)
{
procForm->procost = defGetNumeric(cost_item);

View File

@ -131,6 +131,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* isAgg */
false, /* isWindowFunc */
false, /* security_definer */
false, /* isLeakProof */
false, /* isStrict */
PROVOLATILE_VOLATILE,
buildoidvector(funcargtypes, 0),
@ -166,6 +167,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* isAgg */
false, /* isWindowFunc */
false, /* security_definer */
false, /* isLeakProof */
true, /* isStrict */
PROVOLATILE_VOLATILE,
buildoidvector(funcargtypes, 1),
@ -204,6 +206,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* isAgg */
false, /* isWindowFunc */
false, /* security_definer */
false, /* isLeakProof */
true, /* isStrict */
PROVOLATILE_VOLATILE,
buildoidvector(funcargtypes, 1),

View File

@ -1521,6 +1521,7 @@ makeRangeConstructors(const char *name, Oid namespace,
false, /* isAgg */
false, /* isWindowFunc */
false, /* security_definer */
false, /* leakproof */
false, /* isStrict */
PROVOLATILE_IMMUTABLE, /* volatility */
constructorArgTypesVector, /* parameterTypes */

View File

@ -1042,16 +1042,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
Node *clause = (Node *) rinfo->clause;
/*
* XXX. You might wonder why we're testing rte->security_barrier
* qual-by-qual here rather than hoisting the test up into the
* surrounding if statement; after all, the answer will be the
* same for all quals. The answer is that we expect to shortly
* change this logic to allow pushing down some quals that use only
* "leakproof" operators even through a security barrier.
*/
if (!rinfo->pseudoconstant &&
!rte->security_barrier &&
(!rte->security_barrier ||
!contain_leaky_functions(clause)) &&
qual_is_pushdown_safe(subquery, rti, clause, differentTypes))
{
/* Push it down */

View File

@ -93,6 +93,7 @@ static bool contain_subplans_walker(Node *node, void *context);
static bool contain_mutable_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_leaky_functions_walker(Node *node, void *context);
static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
@ -1129,6 +1130,145 @@ contain_nonstrict_functions_walker(Node *node, void *context)
context);
}
/*****************************************************************************
* Check clauses for non-leakproof functions
*****************************************************************************/
/*
* contain_leaky_functions
* Recursively search for leaky functions within a clause.
*
* Returns true if any function call with side-effect may be present in the
* clause. Qualifiers from outside the a security_barrier view should not
* be pushed down into the view, lest the contents of tuples intended to be
* filtered out be revealed via side effects.
*/
bool
contain_leaky_functions(Node *clause)
{
return contain_leaky_functions_walker(clause, NULL);
}
static bool
contain_leaky_functions_walker(Node *node, void *context)
{
if (node == NULL)
return false;
switch (nodeTag(node))
{
case T_Var:
case T_Const:
case T_Param:
case T_ArrayExpr:
case T_NamedArgExpr:
case T_BoolExpr:
case T_RelabelType:
case T_CaseExpr:
case T_CaseTestExpr:
case T_RowExpr:
case T_MinMaxExpr:
case T_NullTest:
case T_BooleanTest:
case T_List:
/*
* We know these node types don't contain function calls; but
* something further down in the node tree might.
*/
break;
case T_FuncExpr:
{
FuncExpr *expr = (FuncExpr *) node;
if (!get_func_leakproof(expr->funcid))
return true;
}
break;
case T_OpExpr:
case T_DistinctExpr: /* struct-equivalent to OpExpr */
case T_NullIfExpr: /* struct-equivalent to OpExpr */
{
OpExpr *expr = (OpExpr *) node;
set_opfuncid(expr);
if (!get_func_leakproof(expr->opfuncid))
return true;
}
break;
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
set_sa_opfuncid(expr);
if (!get_func_leakproof(expr->opfuncid))
return true;
}
break;
case T_CoerceViaIO:
{
CoerceViaIO *expr = (CoerceViaIO *) node;
Oid funcid;
Oid ioparam;
bool varlena;
getTypeInputInfo(exprType((Node *)expr->arg),
&funcid, &ioparam);
if (!get_func_leakproof(funcid))
return true;
getTypeOutputInfo(expr->resulttype, &funcid, &varlena);
if (!get_func_leakproof(funcid))
return true;
}
break;
case T_ArrayCoerceExpr:
{
ArrayCoerceExpr *expr = (ArrayCoerceExpr *) node;
Oid funcid;
Oid ioparam;
bool varlena;
getTypeInputInfo(exprType((Node *)expr->arg),
&funcid, &ioparam);
if (!get_func_leakproof(funcid))
return true;
getTypeOutputInfo(expr->resulttype, &funcid, &varlena);
if (!get_func_leakproof(funcid))
return true;
}
break;
case T_RowCompareExpr:
{
RowCompareExpr *rcexpr = (RowCompareExpr *) node;
ListCell *opid;
foreach(opid, rcexpr->opnos)
{
Oid funcid = get_opcode(lfirst_oid(opid));
if (!get_func_leakproof(funcid))
return true;
}
}
break;
default:
/*
* If we don't recognize the node tag, assume it might be leaky.
* This prevents an unexpected security hole if someone adds a new
* node type that can call a function.
*/
return true;
}
return expression_tree_walker(node, contain_leaky_functions_walker,
context);
}
/*
* find_nonnullable_rels

View File

@ -526,7 +526,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
KEY
LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING
LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING LEAKPROOF
LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
LOCATION LOCK_P
@ -6115,6 +6115,10 @@ common_func_opt_item:
{
$$ = makeDefElem("security", (Node *)makeInteger(FALSE));
}
| LEAKPROOF
{
$$ = makeDefElem("leakproof", (Node *)makeInteger(TRUE));
}
| COST NumericOnly
{
$$ = makeDefElem("cost", (Node *)$2);
@ -12219,6 +12223,7 @@ unreserved_keyword:
| LAST_P
| LC_COLLATE_P
| LC_CTYPE_P
| LEAKPROOF
| LEVEL
| LISTEN
| LOAD

View File

@ -1547,6 +1547,25 @@ func_volatile(Oid funcid)
return result;
}
/*
* get_func_leakproof
* Given procedure id, return the function's leakproof field.
*/
bool
get_func_leakproof(Oid funcid)
{
HeapTuple tp;
bool result;
tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", funcid);
result = ((Form_pg_proc) GETSTRUCT(tp))->proleakproof;
ReleaseSysCache(tp);
return result;
}
/*
* get_func_cost
* Given procedure id, return the function's procost field.

View File

@ -9071,6 +9071,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *provolatile;
char *proisstrict;
char *prosecdef;
char *proleakproof;
char *proconfig;
char *procost;
char *prorows;
@ -9098,7 +9099,24 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
selectSourceSchema(fout, finfo->dobj.namespace->dobj.name);
/* Fetch function-specific details */
if (fout->remoteVersion >= 80400)
if (fout->remoteVersion >= 90200)
{
/*
* proleakproof was added at v9.2
*/
appendPQExpBuffer(query,
"SELECT proretset, prosrc, probin, "
"pg_catalog.pg_get_function_arguments(oid) AS funcargs, "
"pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs, "
"pg_catalog.pg_get_function_result(oid) AS funcresult, "
"proiswindow, provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
finfo->dobj.catId.oid);
}
else if (fout->remoteVersion >= 80400)
{
/*
* In 8.4 and up we rely on pg_get_function_arguments and
@ -9110,7 +9128,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs, "
"pg_catalog.pg_get_function_result(oid) AS funcresult, "
"proiswindow, provolatile, proisstrict, prosecdef, "
"proconfig, procost, prorows, "
"false AS proleakproof, "
" proconfig, procost, prorows, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -9123,6 +9142,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"proallargtypes, proargmodes, proargnames, "
"false AS proiswindow, "
"provolatile, proisstrict, prosecdef, "
"false AS proleakproof, "
"proconfig, procost, prorows, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
@ -9136,6 +9156,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"proallargtypes, proargmodes, proargnames, "
"false AS proiswindow, "
"provolatile, proisstrict, prosecdef, "
"false AS proleakproof, "
"null AS proconfig, 0 AS procost, 0 AS prorows, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
@ -9151,6 +9172,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"proargnames, "
"false AS proiswindow, "
"provolatile, proisstrict, prosecdef, "
"false AS proleakproof, "
"null AS proconfig, 0 AS procost, 0 AS prorows, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
@ -9166,6 +9188,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"null AS proargnames, "
"false AS proiswindow, "
"provolatile, proisstrict, prosecdef, "
"false AS proleakproof, "
"null AS proconfig, 0 AS procost, 0 AS prorows, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
@ -9183,6 +9206,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"case when proiscachable then 'i' else 'v' end AS provolatile, "
"proisstrict, "
"false AS prosecdef, "
"false AS proleakproof, "
"null AS proconfig, 0 AS procost, 0 AS prorows, "
"(SELECT lanname FROM pg_language WHERE oid = prolang) AS lanname "
"FROM pg_proc "
@ -9200,6 +9224,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"CASE WHEN proiscachable THEN 'i' ELSE 'v' END AS provolatile, "
"false AS proisstrict, "
"false AS prosecdef, "
"false AS proleakproof, "
"NULL AS proconfig, 0 AS procost, 0 AS prorows, "
"(SELECT lanname FROM pg_language WHERE oid = prolang) AS lanname "
"FROM pg_proc "
@ -9241,6 +9266,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
provolatile = PQgetvalue(res, 0, PQfnumber(res, "provolatile"));
proisstrict = PQgetvalue(res, 0, PQfnumber(res, "proisstrict"));
prosecdef = PQgetvalue(res, 0, PQfnumber(res, "prosecdef"));
proleakproof = PQgetvalue(res, 0, PQfnumber(res, "proleakproof"));
proconfig = PQgetvalue(res, 0, PQfnumber(res, "proconfig"));
procost = PQgetvalue(res, 0, PQfnumber(res, "procost"));
prorows = PQgetvalue(res, 0, PQfnumber(res, "prorows"));
@ -9404,6 +9430,9 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
if (prosecdef[0] == 't')
appendPQExpBuffer(q, " SECURITY DEFINER");
if (proleakproof[0] == 't')
appendPQExpBuffer(q, " LEAKPROOF");
/*
* COST and ROWS are emitted only if present and not default, so as not to
* break backwards-compatibility of the dump without need. Keep this code

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201202083
#define CATALOG_VERSION_NO 201202131
#endif

View File

@ -134,7 +134,7 @@ DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0
DESCR("");
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 26 0 t f f f f 3 _null_ _null_ ));
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
DESCR("");

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@ extern Oid ProcedureCreate(const char *procedureName,
bool isAgg,
bool isWindowFunc,
bool security_definer,
bool isLeakProof,
bool isStrict,
char volatility,
oidvector *parameterTypes,

View File

@ -61,6 +61,8 @@ extern bool contain_subplans(Node *clause);
extern bool contain_mutable_functions(Node *clause);
extern bool contain_volatile_functions(Node *clause);
extern bool contain_nonstrict_functions(Node *clause);
extern bool contain_leaky_functions(Node *clause);
extern Relids find_nonnullable_rels(Node *clause);
extern List *find_nonnullable_vars(Node *clause);
extern List *find_forced_null_vars(Node *clause);

View File

@ -215,6 +215,7 @@ PG_KEYWORD("last", LAST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("lc_collate", LC_COLLATE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("lc_ctype", LC_CTYPE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD)
PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD)
PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD)
PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)

View File

@ -91,6 +91,7 @@ extern Oid get_func_signature(Oid funcid, Oid **argtypes, int *nargs);
extern bool get_func_retset(Oid funcid);
extern bool func_strict(Oid funcid);
extern char func_volatile(Oid funcid);
extern bool get_func_leakproof(Oid funcid);
extern float4 get_func_cost(Oid funcid);
extern float4 get_func_rows(Oid funcid);
extern Oid get_relname_relid(const char *relname, Oid relnamespace);

View File

@ -0,0 +1,463 @@
--
-- CREATE FUNCTION
--
-- sanity check of pg_proc catalog to the given parameters
--
CREATE SCHEMA temp_func_test;
SET search_path TO temp_func_test, public;
--
-- ARGUMENT and RETURN TYPES
--
CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01''';
CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql'
AS 'SELECT $1[0]::int';
CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql'
AS 'SELECT false';
SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc
WHERE oid in ('functest_A_1'::regproc,
'functest_A_2'::regproc,
'functest_A_3'::regproc);
proname | prorettype | proargtypes
--------------+------------+-------------------
functest_a_1 | boolean | [0:1]={text,date}
functest_a_2 | integer | [0:0]={text[]}
functest_a_3 | boolean | {}
(3 rows)
--
-- IMMUTABLE | STABLE | VOLATILE
--
CREATE FUNCTION functest_B_1(int) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 > 0';
CREATE FUNCTION functest_B_2(int) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
CREATE FUNCTION functest_B_3(int) RETURNS bool LANGUAGE 'sql'
STABLE AS 'SELECT $1 = 0';
CREATE FUNCTION functest_B_4(int) RETURNS bool LANGUAGE 'sql'
VOLATILE AS 'SELECT $1 < 0';
SELECT proname, provolatile FROM pg_proc
WHERE oid in ('functest_B_1'::regproc,
'functest_B_2'::regproc,
'functest_B_3'::regproc,
'functest_B_4'::regproc);
proname | provolatile
--------------+-------------
functest_b_1 | v
functest_b_2 | i
functest_b_3 | s
functest_b_4 | v
(4 rows)
ALTER FUNCTION functest_B_2(int) VOLATILE;
ALTER FUNCTION functest_B_3(int) COST 100; -- unrelated change, no effect
SELECT proname, provolatile FROM pg_proc
WHERE oid in ('functest_B_1'::regproc,
'functest_B_2'::regproc,
'functest_B_3'::regproc,
'functest_B_4'::regproc);
proname | provolatile
--------------+-------------
functest_b_1 | v
functest_b_2 | v
functest_b_3 | s
functest_b_4 | v
(4 rows)
--
-- SECURITY DEFINER | INVOKER
--
CREATE FUNCTION functext_C_1(int) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 > 0';
CREATE FUNCTION functext_C_2(int) RETURNS bool LANGUAGE 'sql'
SECURITY DEFINER AS 'SELECT $1 = 0';
CREATE FUNCTION functext_C_3(int) RETURNS bool LANGUAGE 'sql'
SECURITY INVOKER AS 'SELECT $1 < 0';
SELECT proname, prosecdef FROM pg_proc
WHERE oid in ('functext_C_1'::regproc,
'functext_C_2'::regproc,
'functext_C_3'::regproc);
proname | prosecdef
--------------+-----------
functext_c_1 | f
functext_c_2 | t
functext_c_3 | f
(3 rows)
ALTER FUNCTION functext_C_1(int) IMMUTABLE; -- unrelated change, no effect
ALTER FUNCTION functext_C_2(int) SECURITY INVOKER;
ALTER FUNCTION functext_C_3(int) SECURITY DEFINER;
SELECT proname, prosecdef FROM pg_proc
WHERE oid in ('functext_C_1'::regproc,
'functext_C_2'::regproc,
'functext_C_3'::regproc);
proname | prosecdef
--------------+-----------
functext_c_1 | f
functext_c_2 | f
functext_c_3 | t
(3 rows)
--
-- COST
--
CREATE FUNCTION functext_D_1(int,int) RETURNS int LANGUAGE 'sql'
AS 'SELECT $1 + $2';
CREATE FUNCTION functext_D_2(int,int) RETURNS int LANGUAGE 'internal'
AS 'int4pl';
CREATE FUNCTION functext_D_3(int,int) RETURNS int LANGUAGE 'sql'
COST 500 AS 'SELECT $1 * $2';
CREATE FUNCTION functext_D_4(int,int) RETURNS int LANGUAGE 'sql'
COST 0 AS 'SELECT $1 / $2'; -- Error
ERROR: COST must be positive
SELECT proname, procost FROM pg_proc
WHERE oid in ('functext_D_1'::regproc,
'functext_D_2'::regproc,
'functext_D_3'::regproc);
proname | procost
--------------+---------
functext_d_1 | 100
functext_d_2 | 1
functext_d_3 | 500
(3 rows)
ALTER FUNCTION functext_D_1(int,int) STABLE; -- unrelated change, no effect
ALTER FUNCTION functext_D_2(int,int) COST 50;
ALTER FUNCTION functext_D_3(int,int) COST 0.0001;
SELECT proname, procost FROM pg_proc
WHERE oid in ('functext_D_1'::regproc,
'functext_D_2'::regproc,
'functext_D_3'::regproc);
proname | procost
--------------+---------
functext_d_1 | 100
functext_d_2 | 50
functext_d_3 | 0.0001
(3 rows)
--
-- LEAKPROOF
--
CREATE FUNCTION functext_E_1(int) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 > 100';
CREATE FUNCTION functext_E_2(int) RETURNS bool LANGUAGE 'sql'
LEAKPROOF AS 'SELECT $1 > 100';
SELECT proname, proleakproof FROM pg_proc
WHERE oid in ('functext_E_1'::regproc,
'functext_E_2'::regproc);
proname | proleakproof
--------------+--------------
functext_e_1 | f
functext_e_2 | t
(2 rows)
ALTER FUNCTION functext_E_1(int) LEAKPROOF;
ALTER FUNCTION functext_E_2(int) STABLE; -- unrelated change, no effect
SELECT proname, proleakproof FROM pg_proc
WHERE oid in ('functext_E_1'::regproc,
'functext_E_2'::regproc);
proname | proleakproof
--------------+--------------
functext_e_1 | t
functext_e_2 | t
(2 rows)
-- list of built-in leakproof functions
SELECT proname, prorettype::regtype, proargtypes::regtype[]
FROM pg_proc JOIN pg_namespace ON pronamespace = pg_namespace.oid
WHERE nspname = 'pg_catalog' AND proleakproof ORDER BY proname;
proname | prorettype | proargtypes
----------------+------------+---------------------------------------------------------------------
abstimeeq | boolean | [0:1]={abstime,abstime}
abstimege | boolean | [0:1]={abstime,abstime}
abstimegt | boolean | [0:1]={abstime,abstime}
abstimele | boolean | [0:1]={abstime,abstime}
abstimelt | boolean | [0:1]={abstime,abstime}
abstimene | boolean | [0:1]={abstime,abstime}
biteq | boolean | [0:1]={bit,bit}
bitge | boolean | [0:1]={bit,bit}
bitgt | boolean | [0:1]={bit,bit}
bitle | boolean | [0:1]={bit,bit}
bitlt | boolean | [0:1]={bit,bit}
bitne | boolean | [0:1]={bit,bit}
booleq | boolean | [0:1]={boolean,boolean}
boolge | boolean | [0:1]={boolean,boolean}
boolgt | boolean | [0:1]={boolean,boolean}
boolle | boolean | [0:1]={boolean,boolean}
boollt | boolean | [0:1]={boolean,boolean}
boolne | boolean | [0:1]={boolean,boolean}
bpchareq | boolean | [0:1]={character,character}
bpcharne | boolean | [0:1]={character,character}
byteaeq | boolean | [0:1]={bytea,bytea}
byteage | boolean | [0:1]={bytea,bytea}
byteagt | boolean | [0:1]={bytea,bytea}
byteale | boolean | [0:1]={bytea,bytea}
bytealt | boolean | [0:1]={bytea,bytea}
byteane | boolean | [0:1]={bytea,bytea}
cash_eq | boolean | [0:1]={money,money}
cash_ge | boolean | [0:1]={money,money}
cash_gt | boolean | [0:1]={money,money}
cash_le | boolean | [0:1]={money,money}
cash_lt | boolean | [0:1]={money,money}
cash_ne | boolean | [0:1]={money,money}
chareq | boolean | [0:1]={"\"char\"","\"char\""}
charge | boolean | [0:1]={"\"char\"","\"char\""}
chargt | boolean | [0:1]={"\"char\"","\"char\""}
charle | boolean | [0:1]={"\"char\"","\"char\""}
charlt | boolean | [0:1]={"\"char\"","\"char\""}
charne | boolean | [0:1]={"\"char\"","\"char\""}
cideq | boolean | [0:1]={cid,cid}
circle_eq | boolean | [0:1]={circle,circle}
circle_ge | boolean | [0:1]={circle,circle}
circle_gt | boolean | [0:1]={circle,circle}
circle_le | boolean | [0:1]={circle,circle}
circle_lt | boolean | [0:1]={circle,circle}
circle_ne | boolean | [0:1]={circle,circle}
date_eq | boolean | [0:1]={date,date}
date_ge | boolean | [0:1]={date,date}
date_gt | boolean | [0:1]={date,date}
date_le | boolean | [0:1]={date,date}
date_lt | boolean | [0:1]={date,date}
date_ne | boolean | [0:1]={date,date}
float48eq | boolean | [0:1]={real,"double precision"}
float48ge | boolean | [0:1]={real,"double precision"}
float48gt | boolean | [0:1]={real,"double precision"}
float48le | boolean | [0:1]={real,"double precision"}
float48lt | boolean | [0:1]={real,"double precision"}
float48ne | boolean | [0:1]={real,"double precision"}
float4eq | boolean | [0:1]={real,real}
float4ge | boolean | [0:1]={real,real}
float4gt | boolean | [0:1]={real,real}
float4le | boolean | [0:1]={real,real}
float4lt | boolean | [0:1]={real,real}
float4ne | boolean | [0:1]={real,real}
float84eq | boolean | [0:1]={"double precision",real}
float84ge | boolean | [0:1]={"double precision",real}
float84gt | boolean | [0:1]={"double precision",real}
float84le | boolean | [0:1]={"double precision",real}
float84lt | boolean | [0:1]={"double precision",real}
float84ne | boolean | [0:1]={"double precision",real}
float8eq | boolean | [0:1]={"double precision","double precision"}
float8ge | boolean | [0:1]={"double precision","double precision"}
float8gt | boolean | [0:1]={"double precision","double precision"}
float8le | boolean | [0:1]={"double precision","double precision"}
float8lt | boolean | [0:1]={"double precision","double precision"}
float8ne | boolean | [0:1]={"double precision","double precision"}
int24eq | boolean | [0:1]={smallint,integer}
int24ge | boolean | [0:1]={smallint,integer}
int24gt | boolean | [0:1]={smallint,integer}
int24le | boolean | [0:1]={smallint,integer}
int24lt | boolean | [0:1]={smallint,integer}
int24ne | boolean | [0:1]={smallint,integer}
int28eq | boolean | [0:1]={smallint,bigint}
int28ge | boolean | [0:1]={smallint,bigint}
int28gt | boolean | [0:1]={smallint,bigint}
int28le | boolean | [0:1]={smallint,bigint}
int28lt | boolean | [0:1]={smallint,bigint}
int28ne | boolean | [0:1]={smallint,bigint}
int2eq | boolean | [0:1]={smallint,smallint}
int2ge | boolean | [0:1]={smallint,smallint}
int2gt | boolean | [0:1]={smallint,smallint}
int2le | boolean | [0:1]={smallint,smallint}
int2lt | boolean | [0:1]={smallint,smallint}
int2ne | boolean | [0:1]={smallint,smallint}
int42eq | boolean | [0:1]={integer,smallint}
int42ge | boolean | [0:1]={integer,smallint}
int42gt | boolean | [0:1]={integer,smallint}
int42le | boolean | [0:1]={integer,smallint}
int42lt | boolean | [0:1]={integer,smallint}
int42ne | boolean | [0:1]={integer,smallint}
int48eq | boolean | [0:1]={integer,bigint}
int48ge | boolean | [0:1]={integer,bigint}
int48gt | boolean | [0:1]={integer,bigint}
int48le | boolean | [0:1]={integer,bigint}
int48lt | boolean | [0:1]={integer,bigint}
int48ne | boolean | [0:1]={integer,bigint}
int4eq | boolean | [0:1]={integer,integer}
int4ge | boolean | [0:1]={integer,integer}
int4gt | boolean | [0:1]={integer,integer}
int4le | boolean | [0:1]={integer,integer}
int4lt | boolean | [0:1]={integer,integer}
int4ne | boolean | [0:1]={integer,integer}
int82eq | boolean | [0:1]={bigint,smallint}
int82ge | boolean | [0:1]={bigint,smallint}
int82gt | boolean | [0:1]={bigint,smallint}
int82le | boolean | [0:1]={bigint,smallint}
int82lt | boolean | [0:1]={bigint,smallint}
int82ne | boolean | [0:1]={bigint,smallint}
int84eq | boolean | [0:1]={bigint,integer}
int84ge | boolean | [0:1]={bigint,integer}
int84gt | boolean | [0:1]={bigint,integer}
int84le | boolean | [0:1]={bigint,integer}
int84lt | boolean | [0:1]={bigint,integer}
int84ne | boolean | [0:1]={bigint,integer}
int8eq | boolean | [0:1]={bigint,bigint}
int8ge | boolean | [0:1]={bigint,bigint}
int8gt | boolean | [0:1]={bigint,bigint}
int8le | boolean | [0:1]={bigint,bigint}
int8lt | boolean | [0:1]={bigint,bigint}
int8ne | boolean | [0:1]={bigint,bigint}
interval_eq | boolean | [0:1]={interval,interval}
interval_ge | boolean | [0:1]={interval,interval}
interval_gt | boolean | [0:1]={interval,interval}
interval_le | boolean | [0:1]={interval,interval}
interval_lt | boolean | [0:1]={interval,interval}
interval_ne | boolean | [0:1]={interval,interval}
lseg_eq | boolean | [0:1]={lseg,lseg}
lseg_ge | boolean | [0:1]={lseg,lseg}
lseg_gt | boolean | [0:1]={lseg,lseg}
lseg_le | boolean | [0:1]={lseg,lseg}
lseg_lt | boolean | [0:1]={lseg,lseg}
lseg_ne | boolean | [0:1]={lseg,lseg}
macaddr_eq | boolean | [0:1]={macaddr,macaddr}
macaddr_ge | boolean | [0:1]={macaddr,macaddr}
macaddr_gt | boolean | [0:1]={macaddr,macaddr}
macaddr_le | boolean | [0:1]={macaddr,macaddr}
macaddr_lt | boolean | [0:1]={macaddr,macaddr}
macaddr_ne | boolean | [0:1]={macaddr,macaddr}
nameeq | boolean | [0:1]={name,name}
namege | boolean | [0:1]={name,name}
namegt | boolean | [0:1]={name,name}
namele | boolean | [0:1]={name,name}
namelt | boolean | [0:1]={name,name}
namene | boolean | [0:1]={name,name}
network_eq | boolean | [0:1]={inet,inet}
network_ge | boolean | [0:1]={inet,inet}
network_gt | boolean | [0:1]={inet,inet}
network_le | boolean | [0:1]={inet,inet}
network_lt | boolean | [0:1]={inet,inet}
network_ne | boolean | [0:1]={inet,inet}
oideq | boolean | [0:1]={oid,oid}
oidge | boolean | [0:1]={oid,oid}
oidgt | boolean | [0:1]={oid,oid}
oidle | boolean | [0:1]={oid,oid}
oidlt | boolean | [0:1]={oid,oid}
oidne | boolean | [0:1]={oid,oid}
reltimeeq | boolean | [0:1]={reltime,reltime}
reltimege | boolean | [0:1]={reltime,reltime}
reltimegt | boolean | [0:1]={reltime,reltime}
reltimele | boolean | [0:1]={reltime,reltime}
reltimelt | boolean | [0:1]={reltime,reltime}
reltimene | boolean | [0:1]={reltime,reltime}
texteq | boolean | [0:1]={text,text}
textne | boolean | [0:1]={text,text}
tideq | boolean | [0:1]={tid,tid}
tidge | boolean | [0:1]={tid,tid}
tidgt | boolean | [0:1]={tid,tid}
tidle | boolean | [0:1]={tid,tid}
tidlt | boolean | [0:1]={tid,tid}
tidne | boolean | [0:1]={tid,tid}
time_eq | boolean | [0:1]={"time without time zone","time without time zone"}
time_ge | boolean | [0:1]={"time without time zone","time without time zone"}
time_gt | boolean | [0:1]={"time without time zone","time without time zone"}
time_le | boolean | [0:1]={"time without time zone","time without time zone"}
time_lt | boolean | [0:1]={"time without time zone","time without time zone"}
time_ne | boolean | [0:1]={"time without time zone","time without time zone"}
timestamp_eq | boolean | [0:1]={"timestamp without time zone","timestamp without time zone"}
timestamp_ge | boolean | [0:1]={"timestamp without time zone","timestamp without time zone"}
timestamp_gt | boolean | [0:1]={"timestamp without time zone","timestamp without time zone"}
timestamp_le | boolean | [0:1]={"timestamp without time zone","timestamp without time zone"}
timestamp_lt | boolean | [0:1]={"timestamp without time zone","timestamp without time zone"}
timestamp_ne | boolean | [0:1]={"timestamp without time zone","timestamp without time zone"}
timestamptz_eq | boolean | [0:1]={"timestamp with time zone","timestamp with time zone"}
timestamptz_ge | boolean | [0:1]={"timestamp with time zone","timestamp with time zone"}
timestamptz_gt | boolean | [0:1]={"timestamp with time zone","timestamp with time zone"}
timestamptz_le | boolean | [0:1]={"timestamp with time zone","timestamp with time zone"}
timestamptz_lt | boolean | [0:1]={"timestamp with time zone","timestamp with time zone"}
timestamptz_ne | boolean | [0:1]={"timestamp with time zone","timestamp with time zone"}
timetz_eq | boolean | [0:1]={"time with time zone","time with time zone"}
timetz_ge | boolean | [0:1]={"time with time zone","time with time zone"}
timetz_gt | boolean | [0:1]={"time with time zone","time with time zone"}
timetz_le | boolean | [0:1]={"time with time zone","time with time zone"}
timetz_lt | boolean | [0:1]={"time with time zone","time with time zone"}
timetz_ne | boolean | [0:1]={"time with time zone","time with time zone"}
tintervaleq | boolean | [0:1]={tinterval,tinterval}
tintervalge | boolean | [0:1]={tinterval,tinterval}
tintervalgt | boolean | [0:1]={tinterval,tinterval}
tintervalle | boolean | [0:1]={tinterval,tinterval}
tintervalleneq | boolean | [0:1]={tinterval,reltime}
tintervallenge | boolean | [0:1]={tinterval,reltime}
tintervallengt | boolean | [0:1]={tinterval,reltime}
tintervallenle | boolean | [0:1]={tinterval,reltime}
tintervallenlt | boolean | [0:1]={tinterval,reltime}
tintervallenne | boolean | [0:1]={tinterval,reltime}
tintervallt | boolean | [0:1]={tinterval,tinterval}
tintervalne | boolean | [0:1]={tinterval,tinterval}
uuid_eq | boolean | [0:1]={uuid,uuid}
uuid_ge | boolean | [0:1]={uuid,uuid}
uuid_gt | boolean | [0:1]={uuid,uuid}
uuid_le | boolean | [0:1]={uuid,uuid}
uuid_lt | boolean | [0:1]={uuid,uuid}
uuid_ne | boolean | [0:1]={uuid,uuid}
varbiteq | boolean | [0:1]={"bit varying","bit varying"}
varbitge | boolean | [0:1]={"bit varying","bit varying"}
varbitgt | boolean | [0:1]={"bit varying","bit varying"}
varbitle | boolean | [0:1]={"bit varying","bit varying"}
varbitlt | boolean | [0:1]={"bit varying","bit varying"}
varbitne | boolean | [0:1]={"bit varying","bit varying"}
xideq | boolean | [0:1]={xid,xid}
(228 rows)
--
-- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
--
CREATE FUNCTION functext_F_1(int) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 > 50';
CREATE FUNCTION functext_F_2(int) RETURNS bool LANGUAGE 'sql'
CALLED ON NULL INPUT AS 'SELECT $1 = 50';
CREATE FUNCTION functext_F_3(int) RETURNS bool LANGUAGE 'sql'
RETURNS NULL ON NULL INPUT AS 'SELECT $1 < 50';
CREATE FUNCTION functext_F_4(int) RETURNS bool LANGUAGE 'sql'
STRICT AS 'SELECT $1 = 50';
SELECT proname, proisstrict FROM pg_proc
WHERE oid in ('functext_F_1'::regproc,
'functext_F_2'::regproc,
'functext_F_3'::regproc,
'functext_F_4'::regproc);
proname | proisstrict
--------------+-------------
functext_f_1 | f
functext_f_2 | f
functext_f_3 | t
functext_f_4 | t
(4 rows)
ALTER FUNCTION functext_F_1(int) IMMUTABLE; -- unrelated change, no effect
ALTER FUNCTION functext_F_2(int) STRICT;
ALTER FUNCTION functext_F_3(int) CALLED ON NULL INPUT;
SELECT proname, proisstrict FROM pg_proc
WHERE oid in ('functext_F_1'::regproc,
'functext_F_2'::regproc,
'functext_F_3'::regproc,
'functext_F_4'::regproc);
proname | proisstrict
--------------+-------------
functext_f_1 | f
functext_f_2 | t
functext_f_3 | f
functext_f_4 | t
(4 rows)
-- Cleanups
DROP SCHEMA temp_func_test CASCADE;
NOTICE: drop cascades to 19 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
drop cascades to function functest_b_1(integer)
drop cascades to function functest_b_2(integer)
drop cascades to function functest_b_3(integer)
drop cascades to function functest_b_4(integer)
drop cascades to function functext_c_1(integer)
drop cascades to function functext_c_2(integer)
drop cascades to function functext_c_3(integer)
drop cascades to function functext_d_1(integer,integer)
drop cascades to function functext_d_2(integer,integer)
drop cascades to function functext_d_3(integer,integer)
drop cascades to function functext_e_1(integer)
drop cascades to function functext_e_2(integer)
drop cascades to function functext_f_1(integer)
drop cascades to function functext_f_2(integer)
drop cascades to function functext_f_3(integer)
drop cascades to function functext_f_4(integer)
RESET search_path;

View File

@ -1397,7 +1397,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_secure WHERE f_leak(cnum);
--
-- scenario: an external qualifier can be pushed-down by in-front-of the
-- views with "security_barrier" attribute
-- views with "security_barrier" attribute, except for operators
-- implemented with leakproof functions.
--
SELECT * FROM my_credit_card_usage_normal
WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
@ -1432,8 +1433,6 @@ SELECT * FROM my_credit_card_usage_secure
WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
NOTICE: f_leak => 1111-2222-3333-4444
NOTICE: f_leak => 1111-2222-3333-4444
NOTICE: f_leak => 1111-2222-3333-4444
NOTICE: f_leak => 1111-2222-3333-4444
NOTICE: f_leak => 1111-2222-3333-4444
cid | name | tel | passwd | cnum | climit | ymd | usage
-----+---------------+------------------+-----------+---------------------+--------+------------+-------
@ -1444,21 +1443,22 @@ NOTICE: f_leak => 1111-2222-3333-4444
EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_usage_secure
WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN
------------------------------------------------------------------------------------
Subquery Scan on my_credit_card_usage_secure
Filter: (f_leak(my_credit_card_usage_secure.cnum) AND (my_credit_card_usage_secure.ymd >= '10-01-2011'::date) AND (my_credit_card_usage_secure.ymd < '11-01-2011'::date))
-> Hash Join
Hash Cond: (r.cid = l.cid)
Filter: f_leak(my_credit_card_usage_secure.cnum)
-> Nested Loop
Join Filter: (l.cid = r.cid)
-> Seq Scan on credit_usage r
-> Hash
Filter: ((ymd >= '10-01-2011'::date) AND (ymd < '11-01-2011'::date))
-> Materialize
-> Hash Join
Hash Cond: (r.cid = l.cid)
-> Seq Scan on credit_card r
-> Hash
-> Seq Scan on customer l
Filter: (name = ("current_user"())::text)
(12 rows)
(13 rows)
--
-- Test for the case when security_barrier gets changed between rewriter

View File

@ -1397,7 +1397,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_secure WHERE f_leak(cnum);
--
-- scenario: an external qualifier can be pushed-down by in-front-of the
-- views with "security_barrier" attribute
-- views with "security_barrier" attribute, except for operators
-- implemented with leakproof functions.
--
SELECT * FROM my_credit_card_usage_normal
WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
@ -1432,8 +1433,6 @@ SELECT * FROM my_credit_card_usage_secure
WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
NOTICE: f_leak => 1111-2222-3333-4444
NOTICE: f_leak => 1111-2222-3333-4444
NOTICE: f_leak => 1111-2222-3333-4444
NOTICE: f_leak => 1111-2222-3333-4444
NOTICE: f_leak => 1111-2222-3333-4444
cid | name | tel | passwd | cnum | climit | ymd | usage
-----+---------------+------------------+-----------+---------------------+--------+------------+-------
@ -1444,21 +1443,22 @@ NOTICE: f_leak => 1111-2222-3333-4444
EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_usage_secure
WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN
------------------------------------------------------------------------------------
Subquery Scan on my_credit_card_usage_secure
Filter: (f_leak(my_credit_card_usage_secure.cnum) AND (my_credit_card_usage_secure.ymd >= '10-01-2011'::date) AND (my_credit_card_usage_secure.ymd < '11-01-2011'::date))
-> Hash Join
Hash Cond: (r.cid = l.cid)
Filter: f_leak(my_credit_card_usage_secure.cnum)
-> Nested Loop
Join Filter: (l.cid = r.cid)
-> Seq Scan on credit_usage r
-> Hash
Filter: ((ymd >= '10-01-2011'::date) AND (ymd < '11-01-2011'::date))
-> Materialize
-> Hash Join
Hash Cond: (r.cid = l.cid)
-> Seq Scan on credit_card r
-> Hash
-> Seq Scan on customer l
Filter: (name = ("current_user"())::text)
(12 rows)
(13 rows)
--
-- Test for the case when security_barrier gets changed between rewriter

View File

@ -40,6 +40,7 @@ test: create_function_1
test: create_type
test: create_table
test: create_function_2
test: create_function_3
# ----------
# Load huge amounts of data

View File

@ -50,6 +50,7 @@ test: create_function_1
test: create_type
test: create_table
test: create_function_2
test: create_function_3
test: copy
test: copyselect
test: create_misc

View File

@ -0,0 +1,145 @@
--
-- CREATE FUNCTION
--
-- sanity check of pg_proc catalog to the given parameters
--
CREATE SCHEMA temp_func_test;
SET search_path TO temp_func_test, public;
--
-- ARGUMENT and RETURN TYPES
--
CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01''';
CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql'
AS 'SELECT $1[0]::int';
CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql'
AS 'SELECT false';
SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc
WHERE oid in ('functest_A_1'::regproc,
'functest_A_2'::regproc,
'functest_A_3'::regproc);
--
-- IMMUTABLE | STABLE | VOLATILE
--
CREATE FUNCTION functest_B_1(int) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 > 0';
CREATE FUNCTION functest_B_2(int) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
CREATE FUNCTION functest_B_3(int) RETURNS bool LANGUAGE 'sql'
STABLE AS 'SELECT $1 = 0';
CREATE FUNCTION functest_B_4(int) RETURNS bool LANGUAGE 'sql'
VOLATILE AS 'SELECT $1 < 0';
SELECT proname, provolatile FROM pg_proc
WHERE oid in ('functest_B_1'::regproc,
'functest_B_2'::regproc,
'functest_B_3'::regproc,
'functest_B_4'::regproc);
ALTER FUNCTION functest_B_2(int) VOLATILE;
ALTER FUNCTION functest_B_3(int) COST 100; -- unrelated change, no effect
SELECT proname, provolatile FROM pg_proc
WHERE oid in ('functest_B_1'::regproc,
'functest_B_2'::regproc,
'functest_B_3'::regproc,
'functest_B_4'::regproc);
--
-- SECURITY DEFINER | INVOKER
--
CREATE FUNCTION functext_C_1(int) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 > 0';
CREATE FUNCTION functext_C_2(int) RETURNS bool LANGUAGE 'sql'
SECURITY DEFINER AS 'SELECT $1 = 0';
CREATE FUNCTION functext_C_3(int) RETURNS bool LANGUAGE 'sql'
SECURITY INVOKER AS 'SELECT $1 < 0';
SELECT proname, prosecdef FROM pg_proc
WHERE oid in ('functext_C_1'::regproc,
'functext_C_2'::regproc,
'functext_C_3'::regproc);
ALTER FUNCTION functext_C_1(int) IMMUTABLE; -- unrelated change, no effect
ALTER FUNCTION functext_C_2(int) SECURITY INVOKER;
ALTER FUNCTION functext_C_3(int) SECURITY DEFINER;
SELECT proname, prosecdef FROM pg_proc
WHERE oid in ('functext_C_1'::regproc,
'functext_C_2'::regproc,
'functext_C_3'::regproc);
--
-- COST
--
CREATE FUNCTION functext_D_1(int,int) RETURNS int LANGUAGE 'sql'
AS 'SELECT $1 + $2';
CREATE FUNCTION functext_D_2(int,int) RETURNS int LANGUAGE 'internal'
AS 'int4pl';
CREATE FUNCTION functext_D_3(int,int) RETURNS int LANGUAGE 'sql'
COST 500 AS 'SELECT $1 * $2';
CREATE FUNCTION functext_D_4(int,int) RETURNS int LANGUAGE 'sql'
COST 0 AS 'SELECT $1 / $2'; -- Error
SELECT proname, procost FROM pg_proc
WHERE oid in ('functext_D_1'::regproc,
'functext_D_2'::regproc,
'functext_D_3'::regproc);
ALTER FUNCTION functext_D_1(int,int) STABLE; -- unrelated change, no effect
ALTER FUNCTION functext_D_2(int,int) COST 50;
ALTER FUNCTION functext_D_3(int,int) COST 0.0001;
SELECT proname, procost FROM pg_proc
WHERE oid in ('functext_D_1'::regproc,
'functext_D_2'::regproc,
'functext_D_3'::regproc);
--
-- LEAKPROOF
--
CREATE FUNCTION functext_E_1(int) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 > 100';
CREATE FUNCTION functext_E_2(int) RETURNS bool LANGUAGE 'sql'
LEAKPROOF AS 'SELECT $1 > 100';
SELECT proname, proleakproof FROM pg_proc
WHERE oid in ('functext_E_1'::regproc,
'functext_E_2'::regproc);
ALTER FUNCTION functext_E_1(int) LEAKPROOF;
ALTER FUNCTION functext_E_2(int) STABLE; -- unrelated change, no effect
SELECT proname, proleakproof FROM pg_proc
WHERE oid in ('functext_E_1'::regproc,
'functext_E_2'::regproc);
-- list of built-in leakproof functions
SELECT proname, prorettype::regtype, proargtypes::regtype[]
FROM pg_proc JOIN pg_namespace ON pronamespace = pg_namespace.oid
WHERE nspname = 'pg_catalog' AND proleakproof ORDER BY proname;
--
-- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
--
CREATE FUNCTION functext_F_1(int) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 > 50';
CREATE FUNCTION functext_F_2(int) RETURNS bool LANGUAGE 'sql'
CALLED ON NULL INPUT AS 'SELECT $1 = 50';
CREATE FUNCTION functext_F_3(int) RETURNS bool LANGUAGE 'sql'
RETURNS NULL ON NULL INPUT AS 'SELECT $1 < 50';
CREATE FUNCTION functext_F_4(int) RETURNS bool LANGUAGE 'sql'
STRICT AS 'SELECT $1 = 50';
SELECT proname, proisstrict FROM pg_proc
WHERE oid in ('functext_F_1'::regproc,
'functext_F_2'::regproc,
'functext_F_3'::regproc,
'functext_F_4'::regproc);
ALTER FUNCTION functext_F_1(int) IMMUTABLE; -- unrelated change, no effect
ALTER FUNCTION functext_F_2(int) STRICT;
ALTER FUNCTION functext_F_3(int) CALLED ON NULL INPUT;
SELECT proname, proisstrict FROM pg_proc
WHERE oid in ('functext_F_1'::regproc,
'functext_F_2'::regproc,
'functext_F_3'::regproc,
'functext_F_4'::regproc);
-- Cleanups
DROP SCHEMA temp_func_test CASCADE;
RESET search_path;

View File

@ -108,7 +108,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_secure WHERE f_leak(cnum);
--
-- scenario: an external qualifier can be pushed-down by in-front-of the
-- views with "security_barrier" attribute
-- views with "security_barrier" attribute, except for operators
-- implemented with leakproof functions.
--
SELECT * FROM my_credit_card_usage_normal
WHERE f_leak(cnum) AND ymd >= '2011-10-01' AND ymd < '2011-11-01';