1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

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

@ -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';