Add support for planner support functions in PostgreSQL 12+ functions. Fixes #4333
parent
108af5c10b
commit
adb5cd34bf
|
@ -87,6 +87,8 @@ function:
|
||||||
The default assumption is 1000 rows.
|
The default assumption is 1000 rows.
|
||||||
* Move the *Leak proof?* switch to indicate whether the function has side
|
* Move the *Leak proof?* switch to indicate whether the function has side
|
||||||
effects. The default is *No*. This option can only be set by the superuser.
|
effects. The default is *No*. This option can only be set by the superuser.
|
||||||
|
* Use the *Support function* field to specify a planner support function to
|
||||||
|
use for the function.
|
||||||
|
|
||||||
Click the *Arguments* tab to continue.
|
Click the *Arguments* tab to continue.
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 98 KiB |
|
@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
|
||||||
New features
|
New features
|
||||||
************
|
************
|
||||||
|
|
||||||
|
| `Issue #4333 <https://redmine.postgresql.org/issues/4333>`_ - Add support for planner support functions in PostgreSQL 12+ functions.
|
||||||
|
|
||||||
Housekeeping
|
Housekeeping
|
||||||
************
|
************
|
||||||
|
|
|
@ -208,7 +208,9 @@ class FunctionView(PGChildNodeView, DataTypeReader):
|
||||||
'get_languages': [{'get': 'get_languages'}, {'get': 'get_languages'}],
|
'get_languages': [{'get': 'get_languages'}, {'get': 'get_languages'}],
|
||||||
'vopts': [{}, {'get': 'variable_options'}],
|
'vopts': [{}, {'get': 'variable_options'}],
|
||||||
'select_sql': [{'get': 'select_sql'}],
|
'select_sql': [{'get': 'select_sql'}],
|
||||||
'exec_sql': [{'get': 'exec_sql'}]
|
'exec_sql': [{'get': 'exec_sql'}],
|
||||||
|
'get_support_functions': [{'get': 'get_support_functions'},
|
||||||
|
{'get': 'get_support_functions'}]
|
||||||
})
|
})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1158,7 +1160,7 @@ class FunctionView(PGChildNodeView, DataTypeReader):
|
||||||
fun_change_args = ['lanname', 'prosrc', 'probin', 'prosrc_c',
|
fun_change_args = ['lanname', 'prosrc', 'probin', 'prosrc_c',
|
||||||
'provolatile', 'proisstrict', 'prosecdef',
|
'provolatile', 'proisstrict', 'prosecdef',
|
||||||
'proparallel', 'procost', 'proleakproof',
|
'proparallel', 'procost', 'proleakproof',
|
||||||
'arguments', 'prorows']
|
'arguments', 'prorows', 'prosupportfunc']
|
||||||
|
|
||||||
data['change_func'] = False
|
data['change_func'] = False
|
||||||
for arg in fun_change_args:
|
for arg in fun_change_args:
|
||||||
|
@ -1390,6 +1392,36 @@ class FunctionView(PGChildNodeView, DataTypeReader):
|
||||||
|
|
||||||
return revoke_all
|
return revoke_all
|
||||||
|
|
||||||
|
@check_precondition
|
||||||
|
def get_support_functions(self, gid, sid, did, scid):
|
||||||
|
"""
|
||||||
|
This function will return list of available support functions.
|
||||||
|
"""
|
||||||
|
res = [{'label': '', 'value': ''}]
|
||||||
|
|
||||||
|
try:
|
||||||
|
SQL = render_template(
|
||||||
|
"/".join([self.sql_template_path,
|
||||||
|
'get_support_functions.sql']),
|
||||||
|
show_system_objects=self.blueprint.show_system_objects
|
||||||
|
)
|
||||||
|
status, rset = self.conn.execute_2darray(SQL)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
for row in rset['rows']:
|
||||||
|
res.append(
|
||||||
|
{'label': row['sfunctions'],
|
||||||
|
'value': row['sfunctions']}
|
||||||
|
)
|
||||||
|
return make_json_response(
|
||||||
|
data=res,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
@check_precondition
|
@check_precondition
|
||||||
def dependents(self, gid, sid, did, scid, fnid):
|
def dependents(self, gid, sid, did, scid, fnid):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -178,6 +178,7 @@ define('pgadmin.node.function', [
|
||||||
procost: undefined, /* Estimated execution Cost */
|
procost: undefined, /* Estimated execution Cost */
|
||||||
prorows: undefined, /* Estimated number of rows */
|
prorows: undefined, /* Estimated number of rows */
|
||||||
proleakproof: undefined,
|
proleakproof: undefined,
|
||||||
|
prosupportfunc: undefined, /* Support function */
|
||||||
arguments: [],
|
arguments: [],
|
||||||
prosrc: undefined,
|
prosrc: undefined,
|
||||||
prosrc_c: undefined,
|
prosrc_c: undefined,
|
||||||
|
@ -312,6 +313,12 @@ define('pgadmin.node.function', [
|
||||||
id: 'proleakproof', label: gettext('Leak proof?'),
|
id: 'proleakproof', label: gettext('Leak proof?'),
|
||||||
group: gettext('Options'), cell:'boolean', type: 'switch', min_version: 90200,
|
group: gettext('Options'), cell:'boolean', type: 'switch', min_version: 90200,
|
||||||
disabled: 'isDisabled', deps: ['lanname'],
|
disabled: 'isDisabled', deps: ['lanname'],
|
||||||
|
},{
|
||||||
|
id: 'prosupportfunc', label: gettext('Support function'),
|
||||||
|
type: 'text', disabled: 'isDisabled',
|
||||||
|
group: gettext('Options'), visible: 'isVisible',
|
||||||
|
control: 'node-ajax-options', url: 'get_support_functions',
|
||||||
|
cache_node: 'function', min_version: 120000,
|
||||||
},{
|
},{
|
||||||
id: 'proacl', label: gettext('Privileges'), type: 'text',
|
id: 'proacl', label: gettext('Privileges'), type: 'text',
|
||||||
mode: ['properties'], group: gettext('Security'),
|
mode: ['properties'], group: gettext('Security'),
|
||||||
|
@ -440,6 +447,11 @@ define('pgadmin.node.function', [
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
case 'prosupportfunc':
|
||||||
|
var item = pgAdmin.Browser.tree.selected();
|
||||||
|
if(pgAdmin.Browser.Nodes['function'].getTreeNodeHierarchy(item).server.user.is_superuser)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
{% import 'macros/functions/security.macros' as SECLABEL %}
|
||||||
|
{% import 'macros/functions/privilege.macros' as PRIVILEGE %}
|
||||||
|
{% import 'macros/functions/variable.macros' as VARIABLE %}
|
||||||
|
{% set is_columns = [] %}
|
||||||
|
{% if data %}
|
||||||
|
{% if query_for == 'sql_panel' and func_def is defined %}
|
||||||
|
CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{func_def}}
|
||||||
|
{% else %}
|
||||||
|
CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({% if data.arguments %}
|
||||||
|
{% for p in data.arguments %}{% if p.argmode %}{{p.argmode}} {% endif %}{% if p.argname %}{{ conn|qtIdent(p.argname) }} {% endif %}{% if p.argtype %}{{ p.argtype }}{% endif %}{% if p.argdefval %} DEFAULT {{p.argdefval}}{% endif %}
|
||||||
|
{% if not loop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
)
|
||||||
|
{% endif %}
|
||||||
|
RETURNS{% if data.proretset and (data.prorettypename.startswith('SETOF ') or data.prorettypename.startswith('TABLE')) %} {{ data.prorettypename }} {% elif data.proretset %} SETOF {{ data.prorettypename }}{% else %} {{ data.prorettypename }}{% endif %}
|
||||||
|
|
||||||
|
LANGUAGE {{ data.lanname|qtLiteral }}
|
||||||
|
{% if data.procost %}
|
||||||
|
|
||||||
|
COST {{data.procost}}
|
||||||
|
{% endif %}
|
||||||
|
{% if data.provolatile %}{% if data.provolatile == 'i' %}IMMUTABLE{% elif data.provolatile == 's' %}STABLE{% else %}VOLATILE{% endif %} {% endif %}{% if data.proleakproof %}LEAKPROOF {% endif %}
|
||||||
|
{% if data.proisstrict %}STRICT {% endif %}
|
||||||
|
{% if data.prosecdef %}SECURITY DEFINER {% endif %}
|
||||||
|
{% if data.proiswindow %}WINDOW {% endif %}
|
||||||
|
{% if data.proparallel and (data.proparallel == 'r' or data.proparallel == 's') %}
|
||||||
|
{% if data.proparallel == 'r' %}PARALLEL RESTRICTED{% elif data.proparallel == 's' %}PARALLEL SAFE{% endif %}{% endif -%}
|
||||||
|
{% if data.prorows and (data.prorows | int) > 0 %}
|
||||||
|
|
||||||
|
ROWS {{data.prorows}}{% endif %}
|
||||||
|
|
||||||
|
{% if data.prosupportfunc %}SUPPORT {{ data.prosupportfunc }}{% endif -%}{% if data.variables %}{% for v in data.variables %}
|
||||||
|
|
||||||
|
SET {{ conn|qtIdent(v.name) }}={{ v.value|qtLiteral }}{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
AS {% if data.lanname == 'c' %}
|
||||||
|
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
|
||||||
|
{% else %}
|
||||||
|
$BODY${{ data.prosrc }}$BODY${% endif -%};
|
||||||
|
{% if data.funcowner %}
|
||||||
|
|
||||||
|
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
|
||||||
|
OWNER TO {{ conn|qtIdent(data.funcowner) }};
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.acl %}
|
||||||
|
{% for p in data.acl %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.SET(conn, "FUNCTION", p.grantee, data.name, p.without_grant, p.with_grant, data.pronamespace, data.func_args_without)}}
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
{% if data.revoke_all %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.UNSETALL(conn, "FUNCTION", "PUBLIC", data.name, data.pronamespace, data.func_args_without)}}
|
||||||
|
{% endif %}
|
||||||
|
{% if data.description %}
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
|
||||||
|
IS {{ data.description|qtLiteral }};
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.seclabels %}
|
||||||
|
{% for r in data.seclabels %}
|
||||||
|
{% if r.label and r.provider %}
|
||||||
|
|
||||||
|
{{ SECLABEL.SET(conn, 'FUNCTION', data.name, r.provider, r.label, data.pronamespace, data.func_args_without) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,12 @@
|
||||||
|
SELECT quote_ident(nspname) || '.' || quote_ident(proname) AS sfunctions
|
||||||
|
FROM pg_proc p, pg_namespace n, pg_language l
|
||||||
|
WHERE p.pronamespace = n.oid
|
||||||
|
AND p.prolang = l.oid
|
||||||
|
AND p.prorettype = 'internal'::regtype::oid
|
||||||
|
AND pg_catalog.array_to_string(p.proargtypes, ',') = 'internal'::regtype::oid::text
|
||||||
|
AND (l.lanname = 'internal' or l.lanname = 'c' )
|
||||||
|
-- -- If Show SystemObjects is not true
|
||||||
|
{% if not show_system_objects %}
|
||||||
|
AND (nspname NOT LIKE 'pg\_%' AND nspname NOT in ('information_schema'))
|
||||||
|
{% endif %}
|
||||||
|
ORDER BY nspname ASC, proname ASC
|
|
@ -0,0 +1,40 @@
|
||||||
|
SELECT
|
||||||
|
pr.oid, pr.xmin, pr.*, pr.prosrc AS prosrc_c,
|
||||||
|
pr.proname AS name, pg_get_function_result(pr.oid) AS prorettypename,
|
||||||
|
typns.nspname AS typnsp, lanname, proargnames, oidvectortypes(proargtypes) AS proargtypenames,
|
||||||
|
pg_get_expr(proargdefaults, 'pg_catalog.pg_class'::regclass) AS proargdefaultvals,
|
||||||
|
pronargdefaults, proconfig, pg_get_userbyid(proowner) AS funcowner, description,
|
||||||
|
CASE WHEN pr.prokind = 'w' THEN TRUE ELSE FALSE END AS proiswindow,
|
||||||
|
CASE WHEN prosupport = 0::oid THEN ''
|
||||||
|
ELSE (
|
||||||
|
SELECT quote_ident(nspname) || '.' || quote_ident(proname) AS tfunctions
|
||||||
|
FROM pg_proc p, pg_namespace n
|
||||||
|
WHERE p.pronamespace = n.oid
|
||||||
|
AND p.oid = pr.prosupport::OID
|
||||||
|
) END AS prosupportfunc,
|
||||||
|
(SELECT
|
||||||
|
array_agg(provider || '=' || label)
|
||||||
|
FROM
|
||||||
|
pg_seclabel sl1
|
||||||
|
WHERE
|
||||||
|
sl1.objoid=pr.oid) AS seclabels
|
||||||
|
FROM
|
||||||
|
pg_proc pr
|
||||||
|
JOIN
|
||||||
|
pg_type typ ON typ.oid=prorettype
|
||||||
|
JOIN
|
||||||
|
pg_namespace typns ON typns.oid=typ.typnamespace
|
||||||
|
JOIN
|
||||||
|
pg_language lng ON lng.oid=prolang
|
||||||
|
LEFT OUTER JOIN
|
||||||
|
pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
|
||||||
|
WHERE
|
||||||
|
pr.prokind IN ('f', 'w')
|
||||||
|
{% if fnid %}
|
||||||
|
AND pr.oid = {{fnid}}::oid
|
||||||
|
{% else %}
|
||||||
|
AND pronamespace = {{scid}}::oid
|
||||||
|
{% endif %}
|
||||||
|
AND typname NOT IN ('trigger', 'event_trigger')
|
||||||
|
ORDER BY
|
||||||
|
proname;
|
|
@ -0,0 +1,118 @@
|
||||||
|
{% import 'macros/functions/security.macros' as SECLABEL %}
|
||||||
|
{% import 'macros/functions/privilege.macros' as PRIVILEGE %}
|
||||||
|
{% import 'macros/functions/variable.macros' as VARIABLE %}{% if data %}
|
||||||
|
{% set name = o_data.name %}
|
||||||
|
{% if data.name %}
|
||||||
|
{% if data.name != o_data.name %}
|
||||||
|
ALTER FUNCTION {{ conn|qtIdent(o_data.pronamespace, o_data.name) }}({{
|
||||||
|
o_data.proargtypenames }})
|
||||||
|
RENAME TO {{ conn|qtIdent(data.name) }};
|
||||||
|
{% set name = data.name %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.change_func %}
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION {{ conn|qtIdent(o_data.pronamespace, name) }}({% if data.arguments %}
|
||||||
|
{% for p in data.arguments %}{% if p.argmode %}{{p.argmode}} {% endif %}{% if p.argname %}{{ conn|qtIdent(p.argname) }} {% endif %}{% if p.argtype %}{{ p.argtype }}{% endif %}{% if p.argdefval %} DEFAULT {{p.argdefval}}{% endif %}
|
||||||
|
{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
)
|
||||||
|
RETURNS {{ o_data.prorettypename }}
|
||||||
|
{% if 'lanname' in data %}
|
||||||
|
LANGUAGE {{ data.lanname|qtLiteral }} {% else %}
|
||||||
|
LANGUAGE {{ o_data.lanname|qtLiteral }}
|
||||||
|
{% endif %}{% if 'provolatile' in data and data.provolatile %}{{ data.provolatile }} {% elif 'provolatile' not in data and o_data.provolatile %}{{ o_data.provolatile }}{% endif %}
|
||||||
|
{% if ('proleakproof' in data and data.proleakproof) or ('proleakproof' not in data and o_data.proleakproof) %} LEAKPROOF{% elif 'proleakproof' in data and not data.proleakproof %} NOT LEAKPROOF{% endif %}
|
||||||
|
{% if ('proisstrict' in data and data.proisstrict) or ('proisstrict' not in data and o_data.proisstrict) %} STRICT{% endif %}
|
||||||
|
{% if ('prosecdef' in data and data.prosecdef) or ('prosecdef' not in data and o_data.prosecdef) %} SECURITY DEFINER{% endif %}
|
||||||
|
{% if ('proiswindow' in data and data.proiswindow) or ('proiswindow' not in data and o_data.proiswindow) %} WINDOW{% endif %}
|
||||||
|
|
||||||
|
{% if 'proparallel' in data and data.proparallel %}PARALLEL {{ data.proparallel }}{% elif 'proparallel' not in data and o_data.proparallel %}PARALLEL {{ o_data.proparallel }}{% endif %}
|
||||||
|
|
||||||
|
{% if data.procost %}COST {{data.procost}}{% elif o_data.procost %}COST {{o_data.procost}}{% endif %}{% if data.prorows %}
|
||||||
|
|
||||||
|
ROWS {{data.prorows}}{% elif data.prorows is not defined and o_data.prorows and o_data.prorows != '0' %} ROWS {{o_data.prorows}} {%endif %}
|
||||||
|
|
||||||
|
{% if data.prosupportfunc %}SUPPORT {{ data.prosupportfunc }}{% elif data.prosupportfunc is not defined and o_data.prosupportfunc %}SUPPORT {{ o_data.prosupportfunc }}{% endif -%}{% if data.merged_variables %}{% for v in data.merged_variables %}
|
||||||
|
|
||||||
|
SET {{ conn|qtIdent(v.name) }}={{ v.value|qtLiteral }}{% endfor -%}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
AS {% if 'probin' in data or 'prosrc_c' in data %}
|
||||||
|
{% if 'probin' in data %}{{ data.probin|qtLiteral }}{% else %}{{ o_data.probin|qtLiteral }}{% endif %}, {% if 'prosrc_c' in data %}{{ data.prosrc_c|qtLiteral }}{% else %}{{ o_data.prosrc_c|qtLiteral }}{% endif %}{% elif 'prosrc' in data %}
|
||||||
|
$BODY${{ data.prosrc }}$BODY${% elif o_data.lanname == 'c' %}
|
||||||
|
{{ o_data.probin|qtLiteral }}, {{ o_data.prosrc_c|qtLiteral }}{% else %}
|
||||||
|
$BODY${{ o_data.prosrc }}$BODY${% endif -%};
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.funcowner %}
|
||||||
|
|
||||||
|
ALTER FUNCTION {{ conn|qtIdent(o_data.pronamespace, name) }}({{o_data.proargtypenames }})
|
||||||
|
OWNER TO {{ conn|qtIdent(data.funcowner) }};
|
||||||
|
{% endif -%}
|
||||||
|
{# The SQL generated below will change priviledges #}
|
||||||
|
{% if data.acl %}
|
||||||
|
{% if 'deleted' in data.acl %}
|
||||||
|
{% for priv in data.acl.deleted %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.UNSETALL(conn, 'FUNCTION', priv.grantee, name, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'changed' in data.datacl %}
|
||||||
|
{% for priv in data.acl.changed %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.UNSETALL(conn, 'FUNCTION', priv.grantee, name, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.SET(conn, 'FUNCTION', priv.grantee, name, priv.without_grant, priv.with_grant, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'added' in data.acl %}
|
||||||
|
{% for priv in data.acl.added %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.SET(conn, 'FUNCTION', priv.grantee, name, priv.without_grant, priv.with_grant, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.change_func == False %}
|
||||||
|
{% if data.variables %}
|
||||||
|
{% if 'deleted' in data.variables and data.variables.deleted|length > 0 %}
|
||||||
|
|
||||||
|
{{ VARIABLE.UNSET(conn, 'FUNCTION', name, data.variables.deleted, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'merged_variables' in data and data.merged_variables|length > 0 %}
|
||||||
|
|
||||||
|
{{ VARIABLE.SET(conn, 'FUNCTION', name, data.merged_variables, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
{% set seclabels = data.seclabels %}
|
||||||
|
{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %}
|
||||||
|
{% for r in seclabels.deleted %}
|
||||||
|
|
||||||
|
{{ SECLABEL.UNSET(conn, 'FUNCTION', name, r.provider, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'added' in seclabels and seclabels.added|length > 0 %}
|
||||||
|
{% for r in seclabels.added %}
|
||||||
|
|
||||||
|
{{ SECLABEL.SET(conn, 'FUNCTION', name, r.provider, r.label, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'changed' in seclabels and seclabels.changed|length > 0 %}
|
||||||
|
{% for r in seclabels.changed %}
|
||||||
|
|
||||||
|
{{ SECLABEL.SET(conn, 'FUNCTION', name, r.provider, r.label, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.description is defined and data.description != o_data.description%}
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION {{ conn|qtIdent(o_data.pronamespace, name) }}({{o_data.proargtypenames }})
|
||||||
|
IS {{ data.description|qtLiteral }};
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% if data.pronamespace %}
|
||||||
|
|
||||||
|
ALTER FUNCTION {{ conn|qtIdent(o_data.pronamespace, name) }}({{o_data.proargtypenames }})
|
||||||
|
SET SCHEMA {{ conn|qtIdent(data.pronamespace) }};
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,68 @@
|
||||||
|
{% import 'macros/functions/security.macros' as SECLABEL %}
|
||||||
|
{% import 'macros/functions/privilege.macros' as PRIVILEGE %}
|
||||||
|
{% import 'macros/functions/variable.macros' as VARIABLE %}
|
||||||
|
{% set is_columns = [] %}
|
||||||
|
{% if data %}
|
||||||
|
{% if query_for == 'sql_panel' and func_def is defined %}
|
||||||
|
CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{func_def}}
|
||||||
|
{% else %}
|
||||||
|
CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({% if data.arguments %}
|
||||||
|
{% for p in data.arguments %}{% if p.argmode %}{{p.argmode}} {% endif %}{% if p.argname %}{{ conn|qtIdent(p.argname) }} {% endif %}{% if p.argtype %}{{ p.argtype }}{% endif %}{% if p.argdefval %} DEFAULT {{p.argdefval}}{% endif %}
|
||||||
|
{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
)
|
||||||
|
{% endif -%}
|
||||||
|
RETURNS{% if data.proretset and (data.prorettypename.startswith('SETOF ') or data.prorettypename.startswith('TABLE')) %} {{ data.prorettypename }} {% elif data.proretset %} SETOF {{ data.prorettypename }}{% else %} {{ data.prorettypename }}{% endif %}
|
||||||
|
|
||||||
|
LANGUAGE {{ data.lanname|qtLiteral }}
|
||||||
|
{% if data.provolatile %}{% if data.provolatile == 'i' %}IMMUTABLE{% elif data.provolatile == 's' %}STABLE{% else %}VOLATILE{% endif %} {% endif %}{% if data.proleakproof %}LEAKPROOF {% endif %}
|
||||||
|
{% if data.proisstrict %}STRICT {% endif %}
|
||||||
|
{% if data.prosecdef %}SECURITY DEFINER {% endif %}
|
||||||
|
{% if data.proiswindow %}WINDOW {% endif %}
|
||||||
|
{% if data.proparallel and (data.proparallel == 'r' or data.proparallel == 's') %}
|
||||||
|
{% if data.proparallel == 'r' %}PARALLEL RESTRICTED{% elif data.proparallel == 's' %}PARALLEL SAFE{% endif %}{% endif %}
|
||||||
|
{% if data.procost %}
|
||||||
|
|
||||||
|
COST {{data.procost}}{% endif %}{% if data.prorows and (data.prorows | int) > 0 %}
|
||||||
|
|
||||||
|
ROWS {{data.prorows}}{% endif %}
|
||||||
|
|
||||||
|
{% if data.prosupportfunc %}SUPPORT {{ data.prosupportfunc }}{% endif -%}{% if data.variables %}{% for v in data.variables %}
|
||||||
|
|
||||||
|
SET {{ conn|qtIdent(v.name) }}={{ v.value|qtLiteral }}{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
AS {% if data.lanname == 'c' %}
|
||||||
|
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
|
||||||
|
{% else %}
|
||||||
|
$BODY${{ data.prosrc }}$BODY${% endif -%};
|
||||||
|
{% if data.funcowner %}
|
||||||
|
|
||||||
|
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
|
||||||
|
OWNER TO {{ conn|qtIdent(data.funcowner) }};
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.acl %}
|
||||||
|
{% for p in data.acl %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.SET(conn, "FUNCTION", p.grantee, data.name, p.without_grant, p.with_grant, data.pronamespace, data.func_args_without)}}
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
{% if data.revoke_all %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.UNSETALL(conn, "FUNCTION", "PUBLIC", data.name, data.pronamespace, data.func_args_without)}}
|
||||||
|
{% endif %}
|
||||||
|
{% if data.description %}
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
|
||||||
|
IS {{ data.description|qtLiteral }};
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.seclabels %}
|
||||||
|
{% for r in data.seclabels %}
|
||||||
|
{% if r.label and r.provider %}
|
||||||
|
|
||||||
|
{{ SECLABEL.SET(conn, 'FUNCTION', data.name, r.provider, r.label, data.pronamespace, data.func_args_without) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,33 @@
|
||||||
|
SELECT
|
||||||
|
pr.oid, pr.xmin, pr.*, pr.prosrc AS prosrc_c,
|
||||||
|
pr.proname AS name, pg_get_function_result(pr.oid) AS prorettypename,
|
||||||
|
typns.nspname AS typnsp, lanname, proargnames, oidvectortypes(proargtypes) AS proargtypenames,
|
||||||
|
pg_get_expr(proargdefaults, 'pg_catalog.pg_class'::regclass) AS proargdefaultvals,
|
||||||
|
pronargdefaults, proconfig, pg_get_userbyid(proowner) AS funcowner, description,
|
||||||
|
CASE WHEN prosupport = 0::oid THEN '' ELSE prosupport::text END AS prosupportfunc,
|
||||||
|
(SELECT
|
||||||
|
array_agg(provider || '=' || label)
|
||||||
|
FROM
|
||||||
|
pg_seclabel sl1
|
||||||
|
WHERE
|
||||||
|
sl1.objoid=pr.oid) AS seclabels
|
||||||
|
FROM
|
||||||
|
pg_proc pr
|
||||||
|
JOIN
|
||||||
|
pg_type typ ON typ.oid=prorettype
|
||||||
|
JOIN
|
||||||
|
pg_namespace typns ON typns.oid=typ.typnamespace
|
||||||
|
JOIN
|
||||||
|
pg_language lng ON lng.oid=prolang
|
||||||
|
LEFT OUTER JOIN
|
||||||
|
pg_description des ON (des.objoid=pr.oid AND des.classoid='pg_proc'::regclass)
|
||||||
|
WHERE
|
||||||
|
pr.prokind IN ('f', 'w')
|
||||||
|
AND typname NOT IN ('trigger', 'event_trigger')
|
||||||
|
{% if fnid %}
|
||||||
|
AND pr.oid = {{fnid}}::oid
|
||||||
|
{% else %}
|
||||||
|
AND pronamespace = {{scid}}::oid
|
||||||
|
{% endif %}
|
||||||
|
ORDER BY
|
||||||
|
proname;
|
|
@ -0,0 +1,118 @@
|
||||||
|
{% import 'macros/functions/security.macros' as SECLABEL %}
|
||||||
|
{% import 'macros/functions/privilege.macros' as PRIVILEGE %}
|
||||||
|
{% import 'macros/functions/variable.macros' as VARIABLE %}{% if data %}
|
||||||
|
{% set name = o_data.name %}
|
||||||
|
{% if data.name %}
|
||||||
|
{% if data.name != o_data.name %}
|
||||||
|
ALTER FUNCTION {{ conn|qtIdent(o_data.pronamespace, o_data.name) }}({{
|
||||||
|
o_data.proargtypenames }})
|
||||||
|
RENAME TO {{ conn|qtIdent(data.name) }};
|
||||||
|
{% set name = data.name %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.change_func %}
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION {{ conn|qtIdent(o_data.pronamespace, name) }}({% if data.arguments %}
|
||||||
|
{% for p in data.arguments %}{% if p.argmode %}{{p.argmode}} {% endif %}{% if p.argname %}{{ conn|qtIdent(p.argname) }} {% endif %}{% if p.argtype %}{{ p.argtype }}{% endif %}{% if p.argdefval %} DEFAULT {{p.argdefval}}{% endif %}
|
||||||
|
{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
)
|
||||||
|
RETURNS {{ o_data.prorettypename }}
|
||||||
|
{% if 'lanname' in data %}
|
||||||
|
LANGUAGE {{ data.lanname|qtLiteral }} {% else %}
|
||||||
|
LANGUAGE {{ o_data.lanname|qtLiteral }}
|
||||||
|
{% endif %}{% if 'provolatile' in data and data.provolatile %}{{ data.provolatile }} {% elif 'provolatile' not in data and o_data.provolatile %}{{ o_data.provolatile }}{% endif %}
|
||||||
|
{% if ('proleakproof' in data and data.proleakproof) or ('proleakproof' not in data and o_data.proleakproof) %} LEAKPROOF{% elif 'proleakproof' in data and not data.proleakproof %} NOT LEAKPROOF{% endif %}
|
||||||
|
{% if ('proisstrict' in data and data.proisstrict) or ('proisstrict' not in data and o_data.proisstrict) %} STRICT{% endif %}
|
||||||
|
{% if ('prosecdef' in data and data.prosecdef) or ('prosecdef' not in data and o_data.prosecdef) %} SECURITY DEFINER{% endif %}
|
||||||
|
{% if ('proiswindow' in data and data.proiswindow) or ('proiswindow' not in data and o_data.proiswindow) %} WINDOW{% endif %}
|
||||||
|
|
||||||
|
{% if 'proparallel' in data and data.proparallel %}PARALLEL {{ data.proparallel }}{% elif 'proparallel' not in data and o_data.proparallel %}PARALLEL {{ o_data.proparallel }}{% endif %}
|
||||||
|
|
||||||
|
{% if data.procost %}COST {{data.procost}}{% elif o_data.procost %}COST {{o_data.procost}}{% endif %}{% if data.prorows %}
|
||||||
|
|
||||||
|
ROWS {{data.prorows}}{% elif data.prorows is not defined and o_data.prorows and o_data.prorows != '0' %} ROWS {{o_data.prorows}} {%endif %}
|
||||||
|
|
||||||
|
{% if data.prosupportfunc %}SUPPORT {{ data.prosupportfunc }}{% elif data.prosupportfunc is not defined and o_data.prosupportfunc %}SUPPORT {{ o_data.prosupportfunc }}{% endif -%}{% if data.merged_variables %}{% for v in data.merged_variables %}
|
||||||
|
|
||||||
|
SET {{ conn|qtIdent(v.name) }}={{ v.value|qtLiteral }}{% endfor -%}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
AS {% if 'probin' in data or 'prosrc_c' in data %}
|
||||||
|
{% if 'probin' in data %}{{ data.probin|qtLiteral }}{% else %}{{ o_data.probin|qtLiteral }}{% endif %}, {% if 'prosrc_c' in data %}{{ data.prosrc_c|qtLiteral }}{% else %}{{ o_data.prosrc_c|qtLiteral }}{% endif %}{% elif 'prosrc' in data %}
|
||||||
|
$BODY${{ data.prosrc }}$BODY${% elif o_data.lanname == 'c' %}
|
||||||
|
{{ o_data.probin|qtLiteral }}, {{ o_data.prosrc_c|qtLiteral }}{% else %}
|
||||||
|
$BODY${{ o_data.prosrc }}$BODY${% endif -%};
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.funcowner %}
|
||||||
|
|
||||||
|
ALTER FUNCTION {{ conn|qtIdent(o_data.pronamespace, name) }}({{o_data.proargtypenames }})
|
||||||
|
OWNER TO {{ conn|qtIdent(data.funcowner) }};
|
||||||
|
{% endif -%}
|
||||||
|
{# The SQL generated below will change priviledges #}
|
||||||
|
{% if data.acl %}
|
||||||
|
{% if 'deleted' in data.acl %}
|
||||||
|
{% for priv in data.acl.deleted %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.UNSETALL(conn, 'FUNCTION', priv.grantee, name, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'changed' in data.datacl %}
|
||||||
|
{% for priv in data.acl.changed %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.UNSETALL(conn, 'FUNCTION', priv.grantee, name, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.SET(conn, 'FUNCTION', priv.grantee, name, priv.without_grant, priv.with_grant, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'added' in data.acl %}
|
||||||
|
{% for priv in data.acl.added %}
|
||||||
|
|
||||||
|
{{ PRIVILEGE.SET(conn, 'FUNCTION', priv.grantee, name, priv.without_grant, priv.with_grant, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.change_func == False %}
|
||||||
|
{% if data.variables %}
|
||||||
|
{% if 'deleted' in data.variables and data.variables.deleted|length > 0 %}
|
||||||
|
|
||||||
|
{{ VARIABLE.UNSET(conn, 'FUNCTION', name, data.variables.deleted, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'merged_variables' in data and data.merged_variables|length > 0 %}
|
||||||
|
|
||||||
|
{{ VARIABLE.SET(conn, 'FUNCTION', name, data.merged_variables, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
{% set seclabels = data.seclabels %}
|
||||||
|
{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %}
|
||||||
|
{% for r in seclabels.deleted %}
|
||||||
|
|
||||||
|
{{ SECLABEL.UNSET(conn, 'FUNCTION', name, r.provider, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'added' in seclabels and seclabels.added|length > 0 %}
|
||||||
|
{% for r in seclabels.added %}
|
||||||
|
|
||||||
|
{{ SECLABEL.SET(conn, 'FUNCTION', name, r.provider, r.label, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if 'changed' in seclabels and seclabels.changed|length > 0 %}
|
||||||
|
{% for r in seclabels.changed %}
|
||||||
|
|
||||||
|
{{ SECLABEL.SET(conn, 'FUNCTION', name, r.provider, r.label, o_data.pronamespace, o_data.proargtypenames) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif -%}
|
||||||
|
{% if data.description is defined and data.description != o_data.description%}
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION {{ conn|qtIdent(o_data.pronamespace, name) }}({{o_data.proargtypenames }})
|
||||||
|
IS {{ data.description|qtLiteral }};
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% if data.pronamespace %}
|
||||||
|
|
||||||
|
ALTER FUNCTION {{ conn|qtIdent(o_data.pronamespace, name) }}({{o_data.proargtypenames }})
|
||||||
|
SET SCHEMA {{ conn|qtIdent(data.pronamespace) }};
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% endif %}
|
|
@ -67,6 +67,18 @@ class FunctionAddTestCase(BaseTestGenerator):
|
||||||
data['pronamespace'] = self.schema_id
|
data['pronamespace'] = self.schema_id
|
||||||
else:
|
else:
|
||||||
self.schema_id = data['pronamespace']
|
self.schema_id = data['pronamespace']
|
||||||
|
|
||||||
|
if self.server_version >= 120000:
|
||||||
|
support_function_name = 'supportfunc_%s' % str(uuid.uuid4())[1:8]
|
||||||
|
support_func = funcs_utils.create_support_internal_function(
|
||||||
|
self.server,
|
||||||
|
self.db_name,
|
||||||
|
self.schema_name,
|
||||||
|
support_function_name
|
||||||
|
)
|
||||||
|
|
||||||
|
data['prosupportfuc'] = support_function_name
|
||||||
|
|
||||||
response = self.tester.post(
|
response = self.tester.post(
|
||||||
self.url + str(utils.SERVER_GROUP) + '/' +
|
self.url + str(utils.SERVER_GROUP) + '/' +
|
||||||
str(self.server_id) + '/' + str(self.db_id) +
|
str(self.server_id) + '/' + str(self.db_id) +
|
||||||
|
|
|
@ -42,6 +42,17 @@ class FunctionPutTestCase(BaseTestGenerator):
|
||||||
"id": func_id
|
"id": func_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.server_version >= 120000:
|
||||||
|
support_function_name = 'supportfunc_%s' % str(uuid.uuid4())[1:8]
|
||||||
|
support_func = funcs_utils.create_support_internal_function(
|
||||||
|
self.server,
|
||||||
|
self.db_name,
|
||||||
|
self.schema_name,
|
||||||
|
support_function_name
|
||||||
|
)
|
||||||
|
|
||||||
|
data['prosupportfuc'] = support_function_name
|
||||||
|
|
||||||
put_response = self.tester.put(
|
put_response = self.tester.put(
|
||||||
self.url + str(utils.SERVER_GROUP) +
|
self.url + str(utils.SERVER_GROUP) +
|
||||||
'/' + str(self.server_id) + '/' + str(self.db_id) + '/' +
|
'/' + str(self.server_id) + '/' + str(self.db_id) + '/' +
|
||||||
|
|
|
@ -171,6 +171,34 @@ def create_function(server, db_name, schema_name, func_name):
|
||||||
traceback.print_exc(file=sys.stderr)
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def create_support_internal_function(server, db_name, schema_name, func_name):
|
||||||
|
"""Add the function to schema which will be used as support function for
|
||||||
|
another function."""
|
||||||
|
try:
|
||||||
|
connection = utils.get_db_connection(db_name,
|
||||||
|
server['username'],
|
||||||
|
server['db_password'],
|
||||||
|
server['host'],
|
||||||
|
server['port'],
|
||||||
|
server['sslmode'])
|
||||||
|
pg_cursor = connection.cursor()
|
||||||
|
query = "CREATE FUNCTION " + schema_name + "." + func_name + \
|
||||||
|
"(internal)" \
|
||||||
|
" RETURNS internal LANGUAGE 'internal'" \
|
||||||
|
" AS $BODY$cidr_abbrev$BODY$;"
|
||||||
|
pg_cursor.execute(query)
|
||||||
|
connection.commit()
|
||||||
|
# Get 'oid' from newly created function
|
||||||
|
pg_cursor.execute("SELECT pro.oid, pro.proname FROM"
|
||||||
|
" pg_proc pro WHERE pro.proname='%s'" %
|
||||||
|
func_name)
|
||||||
|
functions = pg_cursor.fetchone()
|
||||||
|
connection.close()
|
||||||
|
return functions
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def verify_procedure(server, db_name, proc_name):
|
def verify_procedure(server, db_name, proc_name):
|
||||||
"""This function verifies the procedure in db"""
|
"""This function verifies the procedure in db"""
|
||||||
connection = utils.get_db_connection(db_name,
|
connection = utils.get_db_connection(db_name,
|
||||||
|
|
|
@ -873,7 +873,7 @@ class TriggerView(PGChildNodeView):
|
||||||
|
|
||||||
status, result = self.conn.execute_dict(SQL)
|
status, result = self.conn.execute_dict(SQL)
|
||||||
if not status:
|
if not status:
|
||||||
return internal_server_error(errormsg=res)
|
return internal_server_error(errormsg=result)
|
||||||
|
|
||||||
# Update the trigger function which we have fetched with schema
|
# Update the trigger function which we have fetched with schema
|
||||||
# name
|
# name
|
||||||
|
|
Loading…
Reference in New Issue