Display functions in Greenplum. Fixes #3044

This commit is contained in:
Joao Pedro De Almeida Pereira
2018-01-23 11:10:46 +00:00
committed by Dave Page
parent 678699c408
commit 65337daeba
19 changed files with 503 additions and 7 deletions

View File

@@ -71,10 +71,10 @@ class FunctionModule(SchemaChildModule):
"""
super(FunctionModule, self).__init__(*args, **kwargs)
self.min_ver = 90100
self.min_ver = None
self.max_ver = None
self.server_type = None
self.min_gpdbver = 1000000000
self.min_gpdbver = None
def get_nodes(self, gid, sid, did, scid):
"""
@@ -659,7 +659,7 @@ class FunctionView(PGChildNodeView, DataTypeReader):
condition += " AND nspname NOT LIKE E'pg\\\\_toast%' AND nspname NOT LIKE E'pg\\\\_temp%'"
# Get Types
status, types = self.get_types(self.conn, condition)
status, types = self.get_types(self.conn, condition, False, scid)
if not status:
return internal_server_error(errormsg=types)

View File

@@ -0,0 +1,38 @@
SELECT
COALESCE(gt.rolname, 'PUBLIC') AS grantee,
g.rolname AS grantor, array_agg(privilege_type) AS privileges,
array_agg(is_grantable) AS grantable
FROM
(SELECT
(d).grantee AS grantee,
(d).grantor AS grantor,
(d).is_grantable AS is_grantable,
CASE (d).privilege_type
WHEN 'EXECUTE' THEN 'X'
ELSE 'UNKNOWN' END AS privilege_type
FROM
(SELECT
u_grantor.oid AS grantor,
grantee.oid AS grantee,
pr.type AS privilege_type,
aclcontains(c.proacl, makeaclitem(grantee.oid, u_grantor.oid, pr.type, true)) AS is_grantable
FROM pg_proc c, pg_namespace nc, pg_authid u_grantor, (
SELECT pg_authid.oid, pg_authid.rolname
FROM pg_authid
UNION ALL
SELECT 0::oid AS oid, 'PUBLIC') grantee(oid, rolname),
(SELECT 'EXECUTE') pr(type)
WHERE c.pronamespace = nc.oid
AND (
c.proacl is NULL
OR aclcontains(c.proacl, makeaclitem(grantee.oid, u_grantor.oid, pr.type, false))
)
AND (pg_has_role(u_grantor.oid, 'USAGE'::text)
OR pg_has_role(grantee.oid, 'USAGE'::text)
OR grantee.rolname = 'PUBLIC'::name)
AND c.oid = {{ fnid }}::OID
) d
) d
LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid)
LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid)
GROUP BY g.rolname, gt.rolname;

View File

@@ -0,0 +1,20 @@
SELECT
funcname AS {{ conn|qtIdent(_('Name')) }},
calls AS {{ conn|qtIdent(_('Number of calls')) }},
total_time AS {{ conn|qtIdent(_('Total time')) }},
self_time AS {{ conn|qtIdent(_('Self time')) }}
FROM
pg_stat_user_functions
WHERE
schemaname = {{schema_name|qtLiteral}}
AND funcid IN (
SELECT p.oid
FROM
pg_proc p
JOIN
pg_type typ ON typ.oid=p.prorettype
WHERE
p.proisagg = FALSE
AND typname NOT IN ('trigger', 'event_trigger')
)
ORDER BY funcname;

View File

@@ -0,0 +1,63 @@
{% 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 %}{{ conn|qtTypeIdent(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 {{ conn|qtTypeIdent(data.prorettypename) }}{% else %} {{ conn|qtTypeIdent(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.proisstrict %}STRICT {% endif %}{% if data.prosecdef %}SECURITY DEFINER {% endif %}{% if data.proiswindow %}WINDOW{% endif -%}
{% if data.prorows and (data.prorows | int) > 0 %}
ROWS {{data.prorows}}{% 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 %}

View File

@@ -0,0 +1,21 @@
{% if scid and fnid %}
SELECT
pr.proname as name, '(' || COALESCE(pg_catalog
.pg_get_function_identity_arguments(pr.oid), '') || ')' as func_args,
nspname
FROM
pg_proc pr
JOIN
pg_type typ ON typ.oid=prorettype
JOIN
pg_namespace nsp ON nsp.oid=pr.pronamespace
WHERE
proisagg = FALSE
AND pronamespace = {{scid}}::oid
AND typname NOT IN ('trigger', 'event_trigger')
AND pr.oid = {{fnid}};
{% endif %}
{% if name %}
DROP FUNCTION {{ conn|qtIdent(nspname, name) }}{{func_args}}{% if cascade %} CASCADE{% endif %};
{% endif %}

View File

@@ -0,0 +1,15 @@
SELECT proretset, prosrc, probin,
pg_catalog.pg_get_function_arguments(pg_proc.oid) AS funcargs,
pg_catalog.pg_get_function_identity_arguments(pg_proc.oid) AS funciargs,
pg_catalog.pg_get_function_result(pg_proc.oid) AS funcresult,
proiswin, provolatile, proisstrict, prosecdef,
proconfig, procost, prorows, prodataaccess,
'a' as proexeclocation,
(SELECT lanname FROM pg_catalog.pg_language WHERE pg_proc.oid = prolang) as lanname,
nspname || '.' || pg_proc.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pg_proc.oid), '') || ')' as name,
nspname || '.' || pg_proc.proname || '(' || COALESCE(pg_catalog.pg_get_function_arguments(pg_proc.oid), '') || ')' as name_with_default_args
FROM pg_catalog.pg_proc
JOIN pg_namespace nsp ON nsp.oid=pg_proc.pronamespace
WHERE proisagg = FALSE
AND pronamespace = {{scid}}::oid
AND pg_proc.oid = {{fnid}}::oid;

View File

@@ -0,0 +1,4 @@
SELECT
lanname as label, lanname as value
FROM
pg_language;

View File

@@ -0,0 +1,17 @@
SELECT
pr.oid, pr.proname || '(' || COALESCE(pg_catalog
.pg_get_function_identity_arguments(pr.oid), '') || ')' as name,
lanname, pg_get_userbyid(proowner) as funcowner, pr.pronamespace as nsp
FROM
pg_proc pr
JOIN
pg_type typ ON typ.oid=prorettype
JOIN
pg_language lng ON lng.oid=prolang
JOIN
pg_namespace nsp ON nsp.oid=pr.pronamespace
AND nsp.nspname={{ nspname|qtLiteral }}
WHERE
proisagg = FALSE
AND typname NOT IN ('trigger', 'event_trigger')
AND pr.proname = {{ name|qtLiteral }};

View File

@@ -0,0 +1,6 @@
SELECT
format_type(oid, NULL) AS out_arg_type
FROM
pg_type
WHERE
oid = {{ out_arg_oid }}::oid;

View File

@@ -0,0 +1,6 @@
SELECT
nspname
FROM
pg_namespace
WHERE
oid = {{ scid }}::oid;

View File

@@ -0,0 +1,20 @@
SELECT
*
FROM
(SELECT
format_type(t.oid,NULL) AS typname,
CASE WHEN typelem > 0 THEN typelem ELSE t.oid END as elemoid, typlen, typtype, t.oid, nspname,
(SELECT COUNT(1) FROM pg_type t2 WHERE t2.typname = t.typname) > 1 AS isdup
FROM
pg_type t
JOIN
pg_namespace nsp ON typnamespace=nsp.oid
WHERE
(NOT (typname = 'unknown' AND nspname = 'pg_catalog'))
AND
(
typtype IN ('b', 'c', 'd', 'e', 'p', 'r')
AND typname NOT IN ('any', 'trigger', 'language_handler', 'event_trigger')
)
) AS dummy
ORDER BY nspname <> 'pg_catalog', nspname <> 'public', nspname, 1;

View File

@@ -0,0 +1,22 @@
SELECT
pr.oid, pr.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pr.oid), '') || ')' as name,
lanname, pg_get_userbyid(proowner) as funcowner, description
FROM
pg_proc pr
JOIN
pg_type typ ON typ.oid=prorettype
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
proisagg = FALSE
{% if fnid %}
AND pr.oid = {{ fnid|qtLiteral }}
{% endif %}
{% if scid %}
AND pronamespace = {{scid}}::oid
{% endif %}
AND typname NOT IN ('trigger', 'event_trigger')
ORDER BY
proname;

View File

@@ -0,0 +1,27 @@
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,
NULL 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
proisagg = FALSE
{% if fnid %}
AND pr.oid = {{fnid}}::oid
{% else %}
AND pronamespace = {{scid}}::oid
{% endif %}
AND typname NOT IN ('trigger', 'event_trigger')
ORDER BY
proname;

View File

@@ -0,0 +1,8 @@
SELECT
calls AS {{ conn|qtIdent(_('Number of calls')) }},
total_time AS {{ conn|qtIdent(_('Total time')) }},
self_time AS {{ conn|qtIdent(_('Self time')) }}
FROM
pg_stat_user_functions
WHERE
funcid = {{fnid}}::oid

View File

@@ -0,0 +1,105 @@
{% 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 %}{{ conn|qtTypeIdent(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 ('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 data.procost %}COST {{data.procost}}{% elif o_data.procost %}COST {{o_data.procost}}{% endif %}{% if data.prorows %}
ROWS {{data.prorows}}{% elif o_data.prorows and o_data.prorows != '0' %} ROWS {{o_data.prorows}}{%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.acl %}
{% 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 %}

View File

@@ -0,0 +1,6 @@
SELECT
name, vartype, min_val, max_val, enumvals
FROM
pg_settings
WHERE
context in ('user', 'superuser');

View File

@@ -21,5 +21,8 @@ FROM
UNION SELECT 'bigserial', 0, 8, 'b', 0, 'pg_catalog', false, false
UNION SELECT 'serial', 0, 4, 'b', 0, 'pg_catalog', false, false
{% endif %}
) AS dummy
AND (
typnamespace = {{schema_oid}}::oid
OR nsp.nspname = 'pg_catalog'
)) AS dummy
ORDER BY nspname <> 'pg_catalog', nspname <> 'public', nspname, 1

View File

@@ -0,0 +1,113 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import sys
from pgadmin.browser.server_groups.servers.databases.schemas.utils import DataTypeReader
from pgadmin.utils.route import BaseTestGenerator
if sys.version_info < (3, 3):
from mock import patch, Mock
else:
from unittest.mock import patch, Mock
_default_database_response = [
{
'typname': 'type name',
'elemoid': 1560,
'is_collatable': True
}
]
_default_expected_function_output = [
{
'label': 'type name',
'value': 'type name',
'typval': 'L',
'precision': False,
'length': True,
'min_val': 1,
'max_val': 2147483647,
'is_collatable': True
}
]
_default_manager = dict(
server_type='ppas',
version='456'
)
class DataTypeReaderTest(BaseTestGenerator):
scenarios = [
('Schema Oid is passed to the SQL Renderer',
dict(
manager=_default_manager,
execute_return_values=_default_database_response,
data_type_template_path='someplate/where/templates/are',
sql_condition='new condition',
schema_oid='123',
add_serials=False,
expected_sql_template_path='someplate/where/templates/are',
expected_function_output=_default_expected_function_output
)),
('When no data_type_template_path is present in class, should create template path with version number',
dict(
manager=_default_manager,
execute_return_values=_default_database_response,
sql_condition='new condition',
schema_oid='123',
add_serials=False,
expected_sql_template_path='datatype/sql/#456#',
expected_function_output=_default_expected_function_output
)),
('When no data_type_template_path is present in class for GreenPlum, '
'should create template path with gpdb and the version number',
dict(
manager=dict(
server_type='gpdb',
version='456'
),
execute_return_values=_default_database_response,
sql_condition='new condition',
schema_oid='123',
add_serials=False,
expected_sql_template_path='datatype/sql/#gpdb#456#',
expected_function_output=_default_expected_function_output
))
]
@patch('pgadmin.browser.server_groups.servers.databases.schemas.utils.render_template')
def runTest(self, template_mock):
connection = Mock()
connection.execute_2darray.return_value = [
True,
{
'rows': self.execute_return_values
}
]
reader = DataTypeReader()
reader.manager = Mock()
reader.manager.server_type = self.manager['server_type']
reader.manager.version = self.manager['version']
try:
reader.data_type_template_path = self.data_type_template_path
except AttributeError:
''
result = reader.get_types(connection, self.sql_condition, self.add_serials, self.schema_oid)
self.assertEqual(result[1], self.expected_function_output)
self.assertTrue(result[0])
connection.execute_2darray.assert_called()
template_mock.assert_called_with(
self.expected_sql_template_path + '/get_types.sql',
condition=self.sql_condition,
add_serials=self.add_serials,
schema_oid=self.schema_oid
)

View File

@@ -82,7 +82,7 @@ class DataTypeReader:
- Returns data-types on the basis of the condition provided.
"""
def get_types(self, conn, condition, add_serials=False):
def get_types(self, conn, condition, add_serials=False, schema_oid = ''):
"""
Returns data-types including calculation for Length and Precision.
@@ -90,6 +90,7 @@ class DataTypeReader:
conn: Connection Object
condition: condition to restrict SQL statement
add_serials: If you want to serials type
schema_oid: If needed pass the schema OID to restrict the search
"""
res = []
try:
@@ -103,11 +104,12 @@ class DataTypeReader:
) if self.manager.server_type == 'gpdb' else
'#{0}#'.format(self.manager.version)
)
SQL = render_template(
"/".join([self.data_type_template_path,'get_types.sql']),
condition=condition,
add_serials=add_serials)
add_serials=add_serials,
schema_oid=schema_oid
)
status, rset = conn.execute_2darray(SQL)
if not status:
return status, rset