Subject: [pgAdmin4][Patch]: RM 3362 - Fix the functions for PG v11, and add support procedure for PG v11

From: Khushboo Vashi <[email-masked]>
Date: Wed, 6 Jun 2018 12:12:28 +0530

Hi,

Please find the attached patch to fix #3362 - Fix the functions for PG v11,
and add support procedure for PG v11.

Thanks,
Khushboo
diff --git a/docs/en_US/procedure_dialog.rst b/docs/en_US/procedure_dialog.rst
index da5f34f..82c2227 100644
--- a/docs/en_US/procedure_dialog.rst
+++ b/docs/en_US/procedure_dialog.rst
@@ -4,7 +4,7 @@
 The Procedure Dialog
 ********************
 
-Use the *Procedure* dialog to create a procedure; procedures are supported by EDB Postgres Advanced Server. The *Procedure* dialog allows you to implement options of the CREATE PROCEDURE command; for more information about the CREATE PROCEDURE SQL command, please see the Database Compatibility for Oracle Developer's, available at:
+Use the *Procedure* dialog to create a procedure; procedures are supported by PostgreSQL v11 and EDB Postgres Advanced Server. The *Procedure* dialog allows you to implement options of the CREATE PROCEDURE command; for more information about the CREATE PROCEDURE SQL command, please see the Database Compatibility for Oracle Developer's, available at:
 
      http://www.enterprisedb.com
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py
index d8e7a68..7fecade 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py
@@ -1592,10 +1592,11 @@ class ProcedureModule(SchemaChildModule):
         """
         super(ProcedureModule, self).__init__(*args, **kwargs)
 
-        self.min_ver = 90100
+        self.min_ver = 110000
         self.max_ver = None
+        self.min_ppasver = 90100
         self.min_gpdbver = 1000000000
-        self.server_type = ['ppas']
+        self.server_type = ['pg', 'ppas']
 
     def get_nodes(self, gid, sid, did, scid):
         """
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/procedure.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/procedure.js
index 041b509..1e02c99 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/procedure.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/procedure.js
@@ -71,11 +71,7 @@ define('pgadmin.node.procedure', [
         if ('catalog' in node_hierarchy)
           return false;
 
-        // Procedures supported only in PPAS
-        return (
-          'server' in node_hierarchy &&
-            node_hierarchy['server'].server_type == 'ppas'
-        );
+        return true;
       },
       model: Function.model.extend({
         defaults: _.extend({},
@@ -103,20 +99,24 @@ define('pgadmin.node.procedure', [
             return true;
           }
           switch(this.name){
-          case 'provolatility':
+          case 'provolatile':
           case 'proisstrict':
-          case 'prosecdef':
           case 'procost':
           case 'proleakproof':
           case 'variables':
-            return this.node_info.server.version < 90500;
+            return (this.node_info.server.version < 90500 || this.node_info.server.type != 'ppas');
+          case 'prosecdef':
+            return (this.node_info.server.version < 90500);
           case 'prorows':
             var server = this.node_info.server;
             return !(server.version >= 90500 && m.get('proretset') == true);
           case 'funcowner':
-          case 'lanname':
           case 'proargs':
             return true;
+          case 'proparallel':
+            return (this.node_info.server.version < 90600 || this.node_info.server.type != 'ppas');
+          case 'lanname':
+            return this.node_info.server.version < 110000;
           default:
             return false;
           }
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/coll_stats.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/coll_stats.sql
new file mode 100644
index 0000000..cc4f734
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/coll_stats.sql
@@ -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.prokind IN ('f', 'w')
+            AND typname NOT IN ('trigger', 'event_trigger')
+    )
+ORDER BY funcname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/create.sql
new file mode 100644
index 0000000..01cdf51
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/create.sql
@@ -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 %}{{ 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.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.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 %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/delete.sql
new file mode 100644
index 0000000..540044c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/delete.sql
@@ -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
+    pr.prokind IN ('f', 'w')
+    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 %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/get_definition.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/get_definition.sql
new file mode 100644
index 0000000..ab25dbf
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/get_definition.sql
@@ -0,0 +1,15 @@
+SELECT
+    pg_get_functiondef({{fnid}}::oid) AS func_def,
+    COALESCE(pg_catalog.pg_get_function_identity_arguments(pr.oid), '') as
+    func_with_identity_arguments,
+    nspname,
+    pr.proname as proname,
+    COALESCE(pg_catalog.pg_get_function_arguments(pr.oid), '') as func_args
+FROM
+    pg_proc pr
+JOIN
+    pg_namespace nsp ON nsp.oid=pr.pronamespace
+WHERE
+    pr.prokind IN ('f', 'w')
+    AND pronamespace = {{scid}}::oid
+    AND pr.oid = {{fnid}}::oid;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/get_oid.sql
new file mode 100644
index 0000000..93a2107
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/get_oid.sql
@@ -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
+    pr.prokind IN ('f', 'w')
+    AND typname NOT IN ('trigger', 'event_trigger')
+    AND pr.proname = {{ name|qtLiteral }};
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/node.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/node.sql
new file mode 100644
index 0000000..5dae8d1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/node.sql
@@ -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
+   pr.prokind IN ('f', 'w')
+{% 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;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/properties.sql
new file mode 100644
index 0000000..82d79b0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/properties.sql
@@ -0,0 +1,32 @@
+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,
+    (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;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/update.sql
new file mode 100644
index 0000000..92bd24b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/pg/sql/11_plus/update.sql
@@ -0,0 +1,116 @@
+{% 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 ('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.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 %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/acl.sql
new file mode 100644
index 0000000..2e7aae5
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/acl.sql
@@ -0,0 +1,35 @@
+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, d.grantor, d.is_grantable,
+        CASE d.privilege_type
+        WHEN 'CONNECT' THEN 'c'
+        WHEN 'CREATE' THEN 'C'
+        WHEN 'DELETE' THEN 'd'
+        WHEN 'EXECUTE' THEN 'X'
+        WHEN 'INSERT' THEN 'a'
+        WHEN 'REFERENCES' THEN 'x'
+        WHEN 'SELECT' THEN 'r'
+        WHEN 'TEMPORARY' THEN 'T'
+        WHEN 'TRIGGER' THEN 't'
+        WHEN 'TRUNCATE' THEN 'D'
+        WHEN 'UPDATE' THEN 'w'
+        WHEN 'USAGE' THEN 'U'
+        ELSE 'UNKNOWN'
+        END AS privilege_type
+    FROM
+        (SELECT
+            (d).grantee AS grantee, (d).grantor AS grantor,
+            (d).is_grantable AS is_grantable,
+            (d).privilege_type AS privilege_type
+        FROM
+            (SELECT aclexplode(db.proacl) AS d FROM pg_proc db
+            WHERE db.oid = {{fnid}}::OID) a
+        ) 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;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/coll_stats.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/coll_stats.sql
new file mode 100644
index 0000000..5924679
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/coll_stats.sql
@@ -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.prokind = 'p'::char
+            AND typname NOT IN ('trigger', 'event_trigger')
+    )
+ORDER BY funcname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/create.sql
new file mode 100644
index 0000000..ffff17d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/create.sql
@@ -0,0 +1,46 @@
+{% 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 OR REPLACE  PROCEDURE {{func_def}}
+{% else %}
+CREATE OR REPLACE PROCEDURE {{ conn|qtIdent(data.pronamespace, data.name) }}{% if data.arguments is defined %}
+({% 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 %}
+LANGUAGE {{ data.lanname|qtLiteral }}
+{% if data.prosecdef %}SECURITY DEFINER {% endif %}
+
+AS {% if data.lanname == 'c' %}
+{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
+{% else %}
+$BODY${{ data.prosrc }}$BODY${% endif -%};
+{% if data.acl and not is_sql %}
+{% for p in data.acl %}
+
+{{ PRIVILEGE.SET(conn, "PROCEDURE", 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, "PROCEDURE", "PUBLIC", data.name, data.pronamespace, data.func_args_without)}}
+{% endif %}
+{% if data.description %}
+
+COMMENT ON PROCEDURE {{ conn|qtIdent(data.pronamespace, data.name) }}
+    IS {{ data.description|qtLiteral  }};
+{% endif -%}
+{% if data.seclabels %}
+{% for r in data.seclabels %}
+{% if r.label and r.provider %}
+
+{{ SECLABEL.SET(conn, 'PROCEDURE', data.name, r.provider, r.label, data.pronamespace, data.func_args_without) }}
+{% endif %}
+{% endfor %}
+{% endif -%}
+
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/delete.sql
new file mode 100644
index 0000000..b4fcdc2
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/delete.sql
@@ -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
+    pr.prokind = 'p'::char
+    AND pronamespace = {{scid}}::oid
+    AND typname NOT IN ('trigger', 'event_trigger')
+    AND pr.oid = {{fnid}};
+{% endif %}
+
+{% if name %}
+DROP PROCEDURE {{ conn|qtIdent(nspname, name) }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_definition.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_definition.sql
new file mode 100644
index 0000000..4c2e9d9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_definition.sql
@@ -0,0 +1,15 @@
+SELECT
+    pg_get_functiondef({{fnid}}::oid) AS func_def,
+    COALESCE(pg_catalog.pg_get_function_identity_arguments(pr.oid), '') as
+    func_with_identity_arguments,
+    nspname,
+    pr.proname as proname,
+    COALESCE(pg_catalog.pg_get_function_arguments(pr.oid), '') as func_args
+FROM
+    pg_proc pr
+JOIN
+    pg_namespace nsp ON nsp.oid=pr.pronamespace
+WHERE
+    pr.prokind = 'p'::char
+    AND pronamespace = {{scid}}::oid
+    AND pr.oid = {{fnid}}::oid;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_languages.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_languages.sql
new file mode 100644
index 0000000..f81ddfb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_languages.sql
@@ -0,0 +1,4 @@
+SELECT
+    lanname as label, lanname as value
+FROM
+    pg_language;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_oid.sql
new file mode 100644
index 0000000..be98b53
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_oid.sql
@@ -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
+    pr.prokind = 'p'::char
+    AND typname NOT IN ('trigger', 'event_trigger')
+    AND pr.proname = {{ name|qtLiteral }};
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_out_types.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_out_types.sql
new file mode 100644
index 0000000..64a1187
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_out_types.sql
@@ -0,0 +1,6 @@
+SELECT
+    format_type(oid, NULL) AS out_arg_type
+FROM
+    pg_type
+WHERE
+    oid = {{ out_arg_oid }}::oid;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_schema.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_schema.sql
new file mode 100644
index 0000000..127d4b9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_schema.sql
@@ -0,0 +1,6 @@
+SELECT
+    nspname
+FROM
+    pg_namespace
+WHERE
+    oid = {{ scid }}::oid;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_types.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_types.sql
new file mode 100644
index 0000000..2a5582e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/get_types.sql
@@ -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;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/node.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/node.sql
new file mode 100644
index 0000000..89dc768
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/node.sql
@@ -0,0 +1,29 @@
+SELECT
+    pr.oid,
+    CASE WHEN
+        pg_catalog.pg_get_function_identity_arguments(pr.oid) <> ''
+    THEN
+        pr.proname || '(' || pg_catalog.pg_get_function_identity_arguments(pr.oid) || ')'
+    ELSE
+        pr.proname
+    END 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
+    pr.prokind = 'p'::char
+{% 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;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/properties.sql
new file mode 100644
index 0000000..79bea86
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/properties.sql
@@ -0,0 +1,39 @@
+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
+        pg_catalog.pg_get_function_identity_arguments(pr.oid) <> ''
+    THEN
+        pr.proname || '(' || pg_catalog.pg_get_function_identity_arguments(pr.oid) || ')'
+    ELSE
+        pr.proname
+    END) AS name_with_args,
+    (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 = 'p'
+    AND typname NOT IN ('trigger', 'event_trigger')
+{% if fnid %}
+    AND pr.oid = {{fnid}}::oid
+{% else %}
+    AND pronamespace = {{scid}}::oid
+{% endif %}
+ORDER BY
+    proname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/stats.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/stats.sql
new file mode 100644
index 0000000..2e276b6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/stats.sql
@@ -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
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/update.sql
new file mode 100644
index 0000000..38cd413
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/update.sql
@@ -0,0 +1,90 @@
+{% import 'macros/functions/security.macros' as SECLABEL %}
+{% import 'macros/functions/privilege.macros' as PRIVILEGE %}{% if data %}
+{% set name = o_data.name %}
+{% if data.name %}
+{% if data.name != o_data.name %}
+ALTER PROCEDURE {{ conn|qtIdent(o_data.pronamespace, o_data.name) }}{% if o_data.proargtypenames %}({{ o_data.proargtypenames }}){% endif %}
+
+    RENAME TO {{ conn|qtIdent(data.name) }};
+{% set name = data.name %}
+{% endif %}
+
+{% endif -%}
+{% if data.change_func  %}
+CREATE OR REPLACE PROCEDURE {{ 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 %}
+)
+{% if 'lanname' in data %}
+    LANGUAGE {{ data.lanname|qtLiteral }} {% else %}
+    LANGUAGE {{ o_data.lanname|qtLiteral }}
+    {% endif %}
+{% if ('prosecdef' in data and data.prosecdef) or ('prosecdef' not in data and o_data.prosecdef) %}SECURITY DEFINER{% 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 PROCEDURE {{ conn|qtIdent(o_data.pronamespace, name) }}{% if o_data.proargtypenames %}({{ o_data.proargtypenames }}){% endif %}
+    OWNER TO {{ 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, 'PROCEDURE', 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, 'PROCEDURE', priv.grantee, name, o_data.pronamespace, o_data.proargtypenames) }}
+
+{{ PRIVILEGE.SET(conn, 'PROCEDURE', 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, 'PROCEDURE', priv.grantee, name, priv.without_grant, priv.with_grant, o_data.pronamespace, o_data.proargtypenames) }}
+{% endfor %}
+{% endif %}
+{% endif -%}
+{% set seclabels = data.seclabels %}
+{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %}
+{% for r in seclabels.deleted %}
+
+{{ SECLABEL.UNSET(conn, 'PROCEDURE', 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, 'PROCEDURE', 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, 'PROCEDURE', 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 PROCEDURE {{ conn|qtIdent(o_data.pronamespace, name) }}
+    IS {{ data.description|qtLiteral }};
+{% endif -%}
+{% if data.pronamespace %}
+
+ALTER PROCEDURE {{ conn|qtIdent(o_data.pronamespace, name) }}
+    SET SCHEMA {{ conn|qtIdent(data.pronamespace) }};
+{% endif -%}
+
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/variables.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/variables.sql
new file mode 100644
index 0000000..5233c71
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/procedure/pg/sql/default/variables.sql
@@ -0,0 +1,6 @@
+SELECT
+    name, vartype, min_val, max_val, enumvals
+FROM
+    pg_settings
+WHERE
+    context in ('user', 'superuser');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/coll_stats.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/coll_stats.sql
new file mode 100644
index 0000000..cd8cecd
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/coll_stats.sql
@@ -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.prokind IN ('f', 'w')
+            AND typname = 'trigger'
+    )
+ORDER BY funcname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/create.sql
new file mode 100644
index 0000000..20499a9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/create.sql
@@ -0,0 +1,57 @@
+{% 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 %}
+CREATE FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({% if data.proargnames %}{{data.proargnames}}{% endif %})
+    RETURNS{% if data.proretset %} SETOF{% endif %} {{ conn|qtTypeIdent(data.prorettypename) }}
+    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 {% else %}NOT LEAKPROOF {% 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}})
+    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)}}
+{% 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}})
+    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) }}
+{% endif %}
+{% endfor %}
+{% endif -%}
+
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/delete.sql
new file mode 100644
index 0000000..f7f906b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/delete.sql
@@ -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
+    pr.prokind IN ('f', 'w')
+    AND pronamespace = {{scid}}::oid
+    AND typname IN ('trigger', 'event_trigger')
+    AND pr.oid = {{fnid}};
+{% endif %}
+
+{% if name %}
+DROP FUNCTION {{ conn|qtIdent(nspname, name) }}{{func_args}}{% if cascade %} CASCADE{% endif %};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/get_definition.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/get_definition.sql
new file mode 100644
index 0000000..ab25dbf
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/get_definition.sql
@@ -0,0 +1,15 @@
+SELECT
+    pg_get_functiondef({{fnid}}::oid) AS func_def,
+    COALESCE(pg_catalog.pg_get_function_identity_arguments(pr.oid), '') as
+    func_with_identity_arguments,
+    nspname,
+    pr.proname as proname,
+    COALESCE(pg_catalog.pg_get_function_arguments(pr.oid), '') as func_args
+FROM
+    pg_proc pr
+JOIN
+    pg_namespace nsp ON nsp.oid=pr.pronamespace
+WHERE
+    pr.prokind IN ('f', 'w')
+    AND pronamespace = {{scid}}::oid
+    AND pr.oid = {{fnid}}::oid;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/get_oid.sql
new file mode 100644
index 0000000..413df91
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/get_oid.sql
@@ -0,0 +1,18 @@
+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
+    pr.prokind IN ('f', 'w')
+    AND typname IN ('trigger', 'event_trigger')
+    AND lanname NOT IN ('edbspl', 'sql', 'internal')
+    AND pr.proname = {{ name|qtLiteral }};
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/node.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/node.sql
new file mode 100644
index 0000000..55d7c54
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/node.sql
@@ -0,0 +1,23 @@
+SELECT
+    pr.oid, pr.proname || '()' 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
+    pr.prokind IN ('f', 'w')
+{% if fnid %}
+    AND pr.oid = {{ fnid|qtLiteral }}
+{% endif %}
+{% if scid %}
+    AND pronamespace = {{scid}}::oid
+{% endif %}
+    AND typname IN ('trigger', 'event_trigger')
+    AND lanname NOT IN ('edbspl', 'sql', 'internal')
+ORDER BY
+    proname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/properties.sql
new file mode 100644
index 0000000..09d7866
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/properties.sql
@@ -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,
+    (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 IN ('trigger', 'event_trigger')
+    AND lanname NOT IN ('edbspl', 'sql', 'internal')
+{% if fnid %}
+    AND pr.oid = {{fnid}}::oid
+{% else %}
+    AND pronamespace = {{scid}}::oid
+{% endif %}
+ORDER BY
+    proname;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/update.sql
new file mode 100644
index 0000000..413be62
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/trigger_function/pg/sql/11_plus/update.sql
@@ -0,0 +1,109 @@
+{% 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) }}()
+    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 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.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 %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_add.py
new file mode 100644
index 0000000..71ee285
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_add.py
@@ -0,0 +1,80 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+import uuid
+
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils
+
+
+class FunctionAddTestCase(BaseTestGenerator):
+    """ This class will add new function under schema node. """
+    scenarios = [
+        # Fetching default URL for function node.
+        ('Fetch Function Node URL', dict(
+            url='/browser/function/obj/'))
+    ]
+
+    def runTest(self):
+        """ This function will add function under schema node. """
+        super(FunctionAddTestCase, self).runTest()
+        self = funcs_utils.set_up(self)
+        db_user = self.server["username"]
+        data = {
+            "acl": [
+                {
+                    "grantee": db_user,
+                    "grantor": db_user,
+                    "privileges":
+                        [
+                            {
+                                "privilege_type": "X",
+                                "privilege": True,
+                                "with_grant": True
+                            }
+                        ]
+                }
+            ],
+            "arguments": [],
+            "funcowner": db_user,
+            "lanname": "sql",
+            "name": "test_function",
+            "options": [],
+            "proleakproof": True,
+            "pronamespace": 2200,
+            "prorettypename": "integer",
+            "prosecdef": True,
+            "prosrc": "SELECT 1;",
+            "probin": "$libdir/",
+            "provolatile": "s",
+            "seclabels": [],
+            "variables": []
+        }
+
+        data["name"] = "test_function_add_%s" % str(uuid.uuid4())[1:8]
+        if self.schema_id:
+            data['pronamespace'] = self.schema_id
+        else:
+            self.schema_id = data['pronamespace']
+        response = self.tester.post(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' + str(self.db_id) +
+            '/' + str(self.schema_id) + '/',
+            data=json.dumps(data),
+            content_type='html/json'
+        )
+
+        self.assertEquals(response.status_code, 200)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_delete.py
new file mode 100644
index 0000000..79a89fa
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_delete.py
@@ -0,0 +1,49 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils
+
+
+class FunctionDeleteTestCase(BaseTestGenerator):
+    """ This class will delete the function under schema node. """
+    scenarios = [
+        # Fetching default URL for function node.
+        ('Fetch Function Node URL',
+         dict(url='/browser/function/obj/'))
+    ]
+
+    def runTest(self):
+        """ This function will delete function under database node. """
+        super(FunctionDeleteTestCase, self).setUp()
+        self = funcs_utils.set_up(self)
+
+        func_name = "test_function_delete_%s" % str(uuid.uuid4())[1:8]
+        function_info = funcs_utils.create_function(
+            self.server, self.db_name, self.schema_name, func_name)
+
+        func_id = function_info[0]
+        response = self.tester.delete(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' +
+            str(self.db_id) + '/' +
+            str(self.schema_id) + '/' + str(func_id),
+            content_type='html/json'
+        )
+        self.assertEquals(response.status_code, 200)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
+
+    def tearDown(self):
+        pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_get.py
new file mode 100644
index 0000000..32a5585
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_get.py
@@ -0,0 +1,49 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils
+
+
+class FunctionGetTestCase(BaseTestGenerator):
+    """This class will fetch added function under schema node."""
+    skip_on_database = ['gpdb']
+    scenarios = [
+        # Fetching default URL for function node.
+        ('Fetch Function Node URL',
+         dict(url='/browser/function/obj/'))
+    ]
+
+    def runTest(self):
+        """ This function will delete function under database node. """
+        super(FunctionGetTestCase, self).setUp()
+        self = funcs_utils.set_up(self)
+
+        func_name = "test_function_get_%s" % str(uuid.uuid4())[1:8]
+        function_info = funcs_utils.create_function(
+            self.server, self.db_name, self.schema_name, func_name)
+
+        trigger_func_id = function_info[0]
+        response = self.tester.get(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' +
+            str(self.db_id) + '/' +
+            str(self.schema_id) + '/' + str(trigger_func_id),
+            content_type='html/json')
+        self.assertEquals(response.status_code, 200)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
+
+    def tearDown(self):
+        pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_put.py
new file mode 100644
index 0000000..8565687
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_put.py
@@ -0,0 +1,57 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+import uuid
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils
+
+
+class FunctionPutTestCase(BaseTestGenerator):
+    """ This class will update new function under schema node. """
+    skip_on_database = ['gpdb']
+    scenarios = [
+        # Fetching default URL for function node.
+        ('Fetch Function Node URL',
+         dict(url='/browser/function/obj/'))
+    ]
+
+    def runTest(self):
+        """ This function will update function under database node. """
+        super(FunctionPutTestCase, self).setUp()
+        self = funcs_utils.set_up(self)
+
+        func_name = "test_event_delete_%s" % str(uuid.uuid4())[1:8]
+        function_info = funcs_utils.create_function(
+            self.server, self.db_name, self.schema_name, func_name)
+
+        func_id = function_info[0]
+
+        data = {
+            "description": "This is a procedure update comment",
+            "id": func_id
+        }
+
+        put_response = self.tester.put(
+            self.url + str(utils.SERVER_GROUP) +
+            '/' + str(self.server_id) + '/' + str(self.db_id) + '/' +
+            str(self.schema_id) + '/' +
+            str(func_id),
+            data=json.dumps(data),
+            follow_redirects=True)
+        self.assertEquals(put_response.status_code, 200)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
+
+    def tearDown(self):
+        pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_add.py
new file mode 100644
index 0000000..b5cdf17
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_add.py
@@ -0,0 +1,90 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+import uuid
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils
+
+
+class ProcedureAddTestCase(BaseTestGenerator):
+    """ This class will add new procedure under schema node. """
+    skip_on_database = ['gpdb']
+    scenarios = [
+        # Fetching default URL for procedure node.
+        ('Fetch Procedure Node URL', dict(
+            url='/browser/procedure/obj/'))
+    ]
+
+    def runTest(self):
+        """ This function will add procedure under schema node. """
+        super(ProcedureAddTestCase, self).setUp()
+        self = funcs_utils.set_up(self)
+
+        if self.server_type == "pg" and\
+                self.server_version < 110000:
+            message = "Procedures are not supported by PG < 110000."
+            self.skipTest(message)
+
+        db_user = self.server["username"]
+        data = {
+            "acl": [
+                {
+                    "grantee": db_user,
+                    "grantor": db_user,
+                    "privileges":
+                        [
+                            {
+                                "privilege_type": "X",
+                                "privilege": True,
+                                "with_grant": True
+                            }
+                        ]
+                }
+            ],
+            "arguments": [],
+            "funcowner": db_user,
+            "lanname": "sql",
+            "name": "test_pg_11_proc",
+            "options": [],
+            "proleakproof": True,
+            "pronamespace": 2200,
+            "prosecdef": True,
+            "prosrc": "BEGIN RAISE EXCEPTION 'command % is disabled',"
+                      " tg_tag; END;",
+            "seclabels": [],
+            "variables": [
+                {
+                    "name": "enable_sort",
+                    "value": True
+                }
+            ]
+        }
+
+        data["name"] = "test_proc_add_%s" % str(uuid.uuid4())[1:8]
+        if self.server_type == 'pg':
+            data['prosrc'] = 'SELECT 1;'
+        if self.schema_id:
+            data['pronamespace'] = self.schema_id
+        else:
+            self.schema_id = data['pronamespace']
+        response = self.tester.post(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' + str(self.db_id) + '/' +
+            str(self.schema_id) + '/', data=json.dumps(data),
+            content_type='html/json'
+        )
+
+        self.assertEquals(response.status_code, 200)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_delete.py
new file mode 100644
index 0000000..4556b9f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_delete.py
@@ -0,0 +1,56 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils
+
+
+class procedureDeleteTestCase(BaseTestGenerator):
+    """ This class will delete the procedure under schema node. """
+    skip_on_database = ['gpdb']
+    scenarios = [
+        # Fetching default URL for procedure node.
+        ('Fetch Procedure Node URL',
+         dict(url='/browser/procedure/obj/'))
+    ]
+
+    def runTest(self):
+        """ This function will delete procedure under database node. """
+        super(procedureDeleteTestCase, self).setUp()
+        self = funcs_utils.set_up(self)
+
+        if self.server_type == "pg" and\
+                self.server_version < 110000:
+            message = "Procedures are not supported by PG < 110000."
+            self.skipTest(message)
+
+        func_name = "test_procedure_delete_%s" % str(uuid.uuid4())[1:8]
+        proc_info = funcs_utils.create_procedure(
+            self.server, self.db_name, self.schema_name, func_name,
+            self.server_type)
+
+        proc_id = proc_info[0]
+        response = self.tester.delete(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' +
+            str(self.db_id) + '/' +
+            str(self.schema_id) + '/' + str(proc_id),
+            content_type='html/json'
+        )
+        self.assertEquals(response.status_code, 200)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
+
+    def tearDown(self):
+        pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_get.py
new file mode 100644
index 0000000..2b15878
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_get.py
@@ -0,0 +1,55 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils
+
+
+class ProcedureGetTestCase(BaseTestGenerator):
+    """This class will fetch added procedure under schema node."""
+    skip_on_database = ['gpdb']
+    scenarios = [
+        # Fetching default URL for procedure node.
+        ('Fetch Procedure Node URL',
+         dict(url='/browser/procedure/obj/'))
+    ]
+
+    def runTest(self):
+        """ This function will get procedure under database node. """
+        super(ProcedureGetTestCase, self).setUp()
+        self = funcs_utils.set_up(self)
+
+        if self.server_type == "pg" and\
+                self.server_version < 110000:
+            message = "Procedures are not supported by PG < 110000."
+            self.skipTest(message)
+
+        func_name = "test_procedure_get_%s" % str(uuid.uuid4())[1:8]
+        proc_info = funcs_utils.create_procedure(
+            self.server, self.db_name, self.schema_name, func_name,
+            self.server_type)
+
+        proc_id = proc_info[0]
+        response = self.tester.get(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' +
+            str(self.db_id) + '/' +
+            str(self.schema_id) + '/' + str(proc_id),
+            content_type='html/json')
+        self.assertEquals(response.status_code, 200)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
+
+    def tearDown(self):
+        pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_put.py
new file mode 100644
index 0000000..73ba1ae
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_procedure_put.py
@@ -0,0 +1,62 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+import uuid
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils
+
+
+class ProcedurePutTestCase(BaseTestGenerator):
+    """ This class will update new procedure under schema node. """
+    skip_on_database = ['gpdb']
+    scenarios = [
+        # Fetching default URL for procedure node.
+        ('Fetch Procedure Node URL',
+         dict(url='/browser/procedure/obj/'))
+    ]
+
+    def runTest(self):
+        """ This function will update procedure under database node. """
+        super(ProcedurePutTestCase, self).setUp()
+        self = funcs_utils.set_up(self)
+
+        if self.server_type == "pg" and\
+                self.server_version < 110000:
+            message = "Procedures are not supported by PG < 110000."
+            self.skipTest(message)
+
+        func_name = "test_procedure_put_%s" % str(uuid.uuid4())[1:8]
+        proc_info = funcs_utils.create_procedure(
+            self.server, self.db_name, self.schema_name, func_name,
+            self.server_type)
+
+        proc_id = proc_info[0]
+        data = {
+            "description": "This is procedure update comment",
+            "id": proc_id
+        }
+
+        put_response = self.tester.put(
+            self.url + str(utils.SERVER_GROUP) +
+            '/' + str(self.server_id) + '/' + str(self.db_id) + '/' +
+            str(self.schema_id) + '/' +
+            str(proc_id),
+            data=json.dumps(data),
+            follow_redirects=True)
+        self.assertEquals(put_response.status_code, 200)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
+
+    def tearDown(self):
+        pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_add.py
index d238bf0..e99f7c3 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_add.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_add.py
@@ -10,14 +10,11 @@
 import json
 import uuid
 
-from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
-    utils as schema_utils
 from pgadmin.browser.server_groups.servers.databases.tests import utils as \
     database_utils
-from pgadmin.utils import server_utils as server_utils
 from pgadmin.utils.route import BaseTestGenerator
-from regression import parent_node_dict
 from regression.python_test_utils import test_utils as utils
+from . import utils as funcs_utils

 class TriggerFuncAddTestCase(BaseTestGenerator):
@@ -32,30 +29,7 @@ class TriggerFuncAddTestCase(BaseTestGenerator):
     def runTest(self):
         """ This function will add trigger function under schema node. """
         super(TriggerFuncAddTestCase, self).runTest()
-        db_name = parent_node_dict["database"][-1]["db_name"]
-        schema_info = parent_node_dict["schema"][-1]
-        server_id = schema_info["server_id"]
-        db_id = schema_info["db_id"]
-        prorettypename = "event_trigger/trigger"
-        server_con = server_utils.connect_server(self, server_id)
-        if not server_con["info"] == "Server connected.":
-            raise Exception("Could not connect to server to add resource "
-                            "groups.")
-        if "type" in server_con["data"]:
-            if server_con["data"]["version"] < 90300:
-                prorettypename = "trigger"
-
-        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
-                                                 server_id, db_id)
-        if not db_con['data']["connected"]:
-            raise Exception("Could not connect to database to add a function.")
-        schema_id = schema_info["schema_id"]
-        schema_name = schema_info["schema_name"]
-        schema_response = schema_utils.verify_schemas(self.server,
-                                                      db_name,
-                                                      schema_name)
-        if not schema_response:
-            raise Exception("Could not find the schema to add a function.")
+        self = funcs_utils.set_up(self)
         db_user = self.server["username"]
         data = {
             "acl": [
@@ -79,7 +53,7 @@ class TriggerFuncAddTestCase(BaseTestGenerator):
             "options": [],
             "proleakproof": True,
             "pronamespace": 2200,
-            "prorettypename": prorettypename,
+            "prorettypename": self.prorettypename,
             "prosecdef": True,
             "prosrc": "BEGIN RAISE EXCEPTION 'command % is disabled',"
                       " tg_tag; END;",
@@ -98,16 +72,17 @@ class TriggerFuncAddTestCase(BaseTestGenerator):
         for func_type in trigger_func_types:
             data['prorettypename'] = func_type
             data["name"] = "test_event_add_%s" % str(uuid.uuid4())[1:8]
-            if schema_id:
-                data['pronamespace'] = schema_id
+            if self.schema_id:
+                data['pronamespace'] = self.schema_id
             else:
-                schema_id = data['pronamespace']
+                self.schema_id = data['pronamespace']
             response = self.tester.post(
                 self.url + str(utils.SERVER_GROUP) + '/' +
-                str(server_id) + '/' + str(db_id) + '/' + str(schema_id) +
+                str(self.server_id) + '/' + str(self.db_id) +
+                '/' + str(self.schema_id) +
                 '/', data=json.dumps(data), content_type='html/json'
             )
 
             self.assertEquals(response.status_code, 200)
         # Disconnect the database
-        database_utils.disconnect_database(self, server_id, db_id)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_delete.py
index 3d1fe9f..f722470 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_delete.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_delete.py
@@ -9,13 +9,9 @@
 
 import uuid
 
-from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
-    utils as schema_utils
 from pgadmin.browser.server_groups.servers.databases.tests import utils as \
     database_utils
-from pgadmin.utils import server_utils as server_utils
 from pgadmin.utils.route import BaseTestGenerator
-from regression import parent_node_dict
 from regression.python_test_utils import test_utils as utils
 from . import utils as trigger_funcs_utils
 
@@ -29,49 +25,27 @@ class TriggerFuncDeleteTestCase(BaseTestGenerator):
          dict(url='/browser/trigger_function/obj/'))
     ]
 
-    def setUp(self):
-        super(TriggerFuncDeleteTestCase, self).setUp()
-        self.db_name = parent_node_dict["database"][-1]["db_name"]
-        self.schema_name = parent_node_dict["schema"][-1]["schema_name"]
-        self.schema_id = parent_node_dict["schema"][-1]["schema_id"]
-
     def runTest(self):
         """ This function will delete trigger function under database node. """
-        schema_info = parent_node_dict["schema"][-1]
-        server_id = schema_info["server_id"]
-        db_id = schema_info["db_id"]
+        super(TriggerFuncDeleteTestCase, self).setUp()
+        self = trigger_funcs_utils.set_up(self)
+
         func_name = "test_event_delete_%s" % str(uuid.uuid4())[1:8]
-        server_con = server_utils.connect_server(self, server_id)
-        if not server_con["info"] == "Server connected.":
-            raise Exception("Could not connect to server to add resource "
-                            "groups.")
-        server_version = 0
-        if "type" in server_con["data"]:
-            if server_con["data"]["version"] < 90300:
-                server_version = server_con["data"]["version"]
-        self.function_info = trigger_funcs_utils.create_trigger_function(
+        function_info = trigger_funcs_utils.create_trigger_function(
             self.server, self.db_name, self.schema_name, func_name,
-            server_version)
+            self.server_version)
 
-        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
-                                                 server_id, db_id)
-        if not db_con['data']["connected"]:
-            raise Exception("Could not connect to database to add collation.")
-        schema_response = schema_utils.verify_schemas(self.server,
-                                                      self.db_name,
-                                                      self.schema_name)
-        if not schema_response:
-            raise Exception("Could not find the schema to add the collation.")
-        trigger_func_id = self.function_info[0]
+        trigger_func_id = function_info[0]
         response = self.tester.delete(
-            self.url + str(utils.SERVER_GROUP) + '/' + str(server_id) + '/' +
-            str(db_id) + '/' +
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' +
+            str(self.db_id) + '/' +
             str(self.schema_id) + '/' + str(trigger_func_id),
             content_type='html/json'
         )
         self.assertEquals(response.status_code, 200)
         # Disconnect the database
-        database_utils.disconnect_database(self, server_id, db_id)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
 
     def tearDown(self):
         pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_get.py
index 7eb0d2a..15ae1ba 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_get.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_get.py
@@ -9,13 +9,9 @@
 
 import uuid
 
-from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
-    utils as schema_utils
 from pgadmin.browser.server_groups.servers.databases.tests import utils as \
     database_utils
-from pgadmin.utils import server_utils as server_utils
 from pgadmin.utils.route import BaseTestGenerator
-from regression import parent_node_dict
 from regression.python_test_utils import test_utils as utils
 from . import utils as trigger_funcs_utils
 
@@ -29,49 +25,26 @@ class TriggerFuncGetTestCase(BaseTestGenerator):
          dict(url='/browser/trigger_function/obj/'))
     ]
 
-    def setUp(self):
-        super(TriggerFuncGetTestCase, self).setUp()
-        self.db_name = parent_node_dict["database"][-1]["db_name"]
-        self.schema_name = parent_node_dict["schema"][-1]["schema_name"]
-        self.schema_id = parent_node_dict["schema"][-1]["schema_id"]
-
     def runTest(self):
         """ This function will delete trigger function under database node. """
-        schema_info = parent_node_dict["schema"][-1]
-        server_id = schema_info["server_id"]
-        db_id = schema_info["db_id"]
-        func_name = "test_event_get_%s" % str(uuid.uuid4())[1:8]
-        db_user = self.server["username"]
-        server_con = server_utils.connect_server(self, server_id)
-        if not server_con["info"] == "Server connected.":
-            raise Exception("Could not connect to server to add resource "
-                            "groups.")
-        server_version = 0
-        if "type" in server_con["data"]:
-            if server_con["data"]["version"] < 90300:
-                server_version = server_con["data"]["version"]
-        self.function_info = trigger_funcs_utils.create_trigger_function(
+        super(TriggerFuncGetTestCase, self).setUp()
+        self = trigger_funcs_utils.set_up(self)
+
+        func_name = "test_event_delete_%s" % str(uuid.uuid4())[1:8]
+        function_info = trigger_funcs_utils.create_trigger_function(
             self.server, self.db_name, self.schema_name, func_name,
-            server_version)
+            self.server_version)
 
-        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
-                                                 server_id, db_id)
-        if not db_con['data']["connected"]:
-            raise Exception("Could not connect to database to add collation.")
-        schema_response = schema_utils.verify_schemas(self.server,
-                                                      self.db_name,
-                                                      self.schema_name)
-        if not schema_response:
-            raise Exception("Could not find the schema to add the collation.")
-        trigger_func_id = self.function_info[0]
+        trigger_func_id = function_info[0]
         response = self.tester.get(
-            self.url + str(utils.SERVER_GROUP) + '/' + str(server_id) + '/' +
-            str(db_id) + '/' +
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' +
+            str(self.db_id) + '/' +
             str(self.schema_id) + '/' + str(trigger_func_id),
             content_type='html/json')
         self.assertEquals(response.status_code, 200)
         # Disconnect the database
-        database_utils.disconnect_database(self, server_id, db_id)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
 
     def tearDown(self):
         pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_put.py
index 63d4c08..78ddc8f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_put.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_put.py
@@ -10,13 +10,9 @@
 import json
 import uuid
 
-from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
-    utils as schema_utils
 from pgadmin.browser.server_groups.servers.databases.tests import utils as \
     database_utils
-from pgadmin.utils import server_utils as server_utils
 from pgadmin.utils.route import BaseTestGenerator
-from regression import parent_node_dict
 from regression.python_test_utils import test_utils as utils
 from . import utils as trigger_funcs_utils
 
@@ -30,63 +26,33 @@ class TriggerFuncPutTestCase(BaseTestGenerator):
          dict(url='/browser/trigger_function/obj/'))
     ]
 
-    def setUp(self):
-        super(TriggerFuncPutTestCase, self).setUp()
-        self.db_name = parent_node_dict["database"][-1]["db_name"]
-        self.schema_name = parent_node_dict["schema"][-1]["schema_name"]
-        self.schema_id = parent_node_dict["schema"][-1]["schema_id"]
-
     def runTest(self):
         """ This function will update trigger function under database node. """
-        schema_info = parent_node_dict["schema"][-1]
-        server_id = schema_info["server_id"]
-        db_id = schema_info["db_id"]
-        func_name = "test_event_put_%s" % str(uuid.uuid4())[1:8]
-        server_con = server_utils.connect_server(self, server_id)
-        if not server_con["info"] == "Server connected.":
-            raise Exception("Could not connect to server to add resource "
-                            "groups.")
-        server_version = 0
-        if "type" in server_con["data"]:
-            if server_con["data"]["version"] < 90300:
-                server_version = server_con["data"]["version"]
-        self.function_info = trigger_funcs_utils.create_trigger_function(
+        super(TriggerFuncPutTestCase, self).setUp()
+        self = trigger_funcs_utils.set_up(self)
+
+        func_name = "test_event_delete_%s" % str(uuid.uuid4())[1:8]
+        function_info = trigger_funcs_utils.create_trigger_function(
             self.server, self.db_name, self.schema_name, func_name,
-            server_version)
-        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
-                                                 server_id, db_id)
-        if not db_con['data']["connected"]:
-            raise Exception("Could not connect to database to add collation.")
-        schema_response = schema_utils.verify_schemas(self.server,
-                                                      self.db_name,
-                                                      self.schema_name)
-        if not schema_response:
-            raise Exception("Could not find the schema to add the collation.")
-        func_name = self.function_info[1]
-        func_response = trigger_funcs_utils.verify_trigger_function(
-            self.server,
-            self.db_name,
-            func_name)
-        if not func_response:
-            raise Exception("Could not find the trigger function to update"
-                            " it's details.")
+            self.server_version)
+
+        trigger_func_id = function_info[0]
 
-        trigger_func_id = self.function_info[0]
         data = {
-            "description": "This is trigger function update comment",
+            "description": "This is a trigger function update comment",
             "id": trigger_func_id
         }
 
         put_response = self.tester.put(
             self.url + str(utils.SERVER_GROUP) +
-            '/' + str(server_id) + '/' + str(db_id) + '/' +
+            '/' + str(self.server_id) + '/' + str(self.db_id) + '/' +
             str(self.schema_id) + '/' +
             str(trigger_func_id),
             data=json.dumps(data),
             follow_redirects=True)
         self.assertEquals(put_response.status_code, 200)
         # Disconnect the database
-        database_utils.disconnect_database(self, server_id, db_id)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
 
     def tearDown(self):
         pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/utils.py
index 4facf60..3606b45 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/utils.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/utils.py
@@ -13,6 +13,11 @@ import sys
 import traceback
 
 from regression.python_test_utils import test_utils as utils
+from pgadmin.utils import server_utils as server_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils

 def create_trigger_function(server, db_name, schema_name, func_name,
@@ -92,3 +97,118 @@ def verify_trigger_function(server, db_name, func_name):
     functions = pg_cursor.fetchone()
     connection.close()
     return functions
+
+
+def create_procedure(server, db_name, schema_name, func_name, s_type):
+    """This function add the procedure to schema"""
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        pg_cursor = connection.cursor()
+        if s_type == 'pg':
+            query = "CREATE PROCEDURE " + schema_name + "." + func_name + \
+                    "()" \
+                    " LANGUAGE 'sql'" \
+                    " SECURITY DEFINER AS $$" \
+                    " SELECT 1; $$;"
+        else:
+            query = "CREATE PROCEDURE " + schema_name + "." + func_name + \
+                    "()" \
+                    " SECURITY DEFINER AS $BODY$ BEGIN" \
+                    " NULL; END; $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 create_function(server, db_name, schema_name, func_name):
+    """This function add the procedure to schema"""
+    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 + \
+                "()" \
+                " RETURNS integer LANGUAGE 'sql' STABLE LEAKPROOF" \
+                " SECURITY DEFINER AS $$" \
+                " SELECT 1; $$;"
+        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):
+    """This function verifies the procedure in db"""
+    connection = utils.get_db_connection(db_name,
+                                         server['username'],
+                                         server['db_password'],
+                                         server['host'],
+                                         server['port'],
+                                         server['sslmode'])
+    pg_cursor = connection.cursor()
+    pg_cursor.execute("SELECT pro.oid, pro.proname FROM"
+                      " pg_proc pro WHERE pro.proname='%s'" %
+                      proc_name)
+    procs = pg_cursor.fetchone()
+    connection.close()
+    return procs
+
+
+def set_up(obj):
+    """Common set up function"""
+    from regression import parent_node_dict
+
+    obj.db_name = parent_node_dict["database"][-1]["db_name"]
+    schema_info = parent_node_dict["schema"][-1]
+    obj.server_id = schema_info["server_id"]
+    obj.db_id = schema_info["db_id"]
+    obj.prorettypename = "event_trigger/trigger"
+    server_con = server_utils.connect_server(obj, obj.server_id)
+
+    if not server_con["info"] == "Server connected.":
+        raise Exception("Could not connect to server.")
+    if "version" in server_con["data"]:
+        obj.server_version = server_con["data"]["version"]
+        if server_con["data"]["version"] < 90300:
+            obj.prorettypename = "trigger"
+    if "type" in server_con["data"]:
+        obj.server_type = server_con["data"]["type"]
+
+    db_con = database_utils.connect_database(obj, utils.SERVER_GROUP,
+                                             obj.server_id,
+                                             obj.db_id)
+    if not db_con['data']["connected"]:
+        raise Exception("Could not connect to database.")
+    obj.schema_id = schema_info["schema_id"]
+    obj.schema_name = schema_info["schema_name"]
+    schema_response = schema_utils.verify_schemas(obj.server,
+                                                  obj.db_name,
+                                                  obj.schema_name)
+    if not schema_response:
+        raise Exception("Could not find the schema.")
+
+    return obj
diff --git a/web/pgadmin/browser/utils.py b/web/pgadmin/browser/utils.py
index 00a72dd..7f6f19c 100644
--- a/web/pgadmin/browser/utils.py
+++ b/web/pgadmin/browser/utils.py
@@ -51,7 +51,9 @@ class PGChildModule(object):
 
     def __init__(self, *args, **kwargs):
         self.min_ver = 0
-        self.max_ver = 1000000000
+        self.max_ver = 1100000000
+        self.min_ppasver = 0
+        self.max_ppasver = 1100000000
         self.server_type = None
         self.min_gpdbver = 80323
         self.max_gpdbver = 1000000000
@@ -72,6 +74,9 @@ class PGChildModule(object):
         if self.server_type is None or manager.server_type in self.server_type:
             min_server_version = self.min_ver
             max_server_version = self.max_ver
+            if manager.server_type == 'ppas':
+                min_server_version = self.min_ppasver
+                max_server_version = self.max_ppasver
             if manager.server_type == 'gpdb':
                 min_server_version = self.min_gpdbver
                 max_server_version = self.max_gpdbver
diff --git a/web/pgadmin/utils/versioned_template_loader.py b/web/pgadmin/utils/versioned_template_loader.py
index 3481978..dadaeba 100644
--- a/web/pgadmin/utils/versioned_template_loader.py
+++ b/web/pgadmin/utils/versioned_template_loader.py
@@ -75,7 +75,8 @@ def get_version_mapping(template):
             {'name': "default", 'number': 0}
         )
 
-    return ({'name': "10_plus", 'number': 100000},
+    return ({'name': "11_plus", 'number': 110000},
+            {'name': "10_plus", 'number': 100000},
             {'name': "9.6_plus", 'number': 90600},
             {'name': "9.5_plus", 'number': 90500},
             {'name': "9.4_plus", 'number': 90400},

---

From: Khushboo Vashi <[email-masked]>
Date: Thu, 7 Jun 2018 15:57:50 +0530

Hi,

Please ignore my previous patch, find the attached updated one.

Thanks,
Khushboo

On Wed, Jun 6, 2018 at 12:12 PM, Khushboo Vashi <
[email-masked]> wrote:

> Hi,
>
> Please find the attached patch to fix #3362 - Fix the functions for PG
> v11, and add support procedure for PG v11.
>
> Thanks,
> Khushboo
>
>

 
 
-        self.min_ver = 90100
+        self.min_ver = 110000
         self.max_ver = None

index 0000000..50c2ca0
+    CASE WHEN pr.prokind = 'w' THEN TRUE ELSE FALSE END AS proiswindow,

 
 
 import uuid

 
-    def setUp(self):

     def tearDown(self):
         pass
 
 import uuid

 
-    def setUp(self):

     def tearDown(self):
         pass

 
-    def setUp(self):

 
     def tearDown(self):
         pass

---

From: Dave Page <[email-masked]>
Date: Fri, 8 Jun 2018 09:51:34 +0100

Hi

> Please ignore my previous patch, find the attached updated one.

I found a couple of issues with this:

- Clicking the + button on the Parameters tab does nothing in either Create
or Edit modes

- The debugger fails to start (though, perhaps that's because the plugin
doesn't have Ashesh's latest patches in it).

FYI, I was trying to test the debugger with:

-- PROCEDURE: public.dummy_proc(integer)

-- DROP PROCEDURE public.dummy_proc(integer);

CREATE OR REPLACE  PROCEDURE public.dummy_proc(
id integer)
LANGUAGE 'plpgsql'

AS $BODY$BEGIN
  raise notice 'id is %', id;
END;$BODY$;

Thanks!

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Khushboo Vashi <[email-masked]>
Date: Tue, 12 Jun 2018 12:39:48 +0530

Hi,

Please find the attached updated patch.

On Fri, Jun 8, 2018 at 2:21 PM, Dave Page <[email-masked]> wrote:

> - Clicking the + button on the Parameters tab does nothing in either
> Create or Edit modes
>
> Fixed

> Fixed. Tested with the latest code of the plugin.

Khushboo

 
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py
index d8e7a68..7b2ba2d 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py
@@ -1502,7 +1502,10 @@ class FunctionView(PGChildNodeView, DataTypeReader):
             name = name.replace(arg, formatted_arg)
 
         name = name.replace(')', '\n)')
-        sql = "EXEC {0}".format(name)
+        if self.manager.server_type == 'pg':
+            sql = "CALL {0}".format(name)
+        else:
+            sql = "EXEC {0}".format(name)
 
         return ajax_response(response=sql)
 
@@ -1592,10 +1595,11 @@ class ProcedureModule(SchemaChildModule):
         """
         super(ProcedureModule, self).__init__(*args, **kwargs)
 
-        self.min_ver = 90100
+        self.min_ver = 110000
         self.max_ver = None
 
index 041b509..3c6b686 100644
 
+            return (this.node_info.server.version < 90500 || this.node_info.server.server_type != 'ppas');
             return this.node_info.server.version < 90500;
+            return (this.node_info.server.version < 90600 || this.node_info.server.server_type != 'ppas');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/ppas/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/ppas/sql/9.2_plus/create.sql
index 213652c..16c088f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/ppas/sql/9.2_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/templates/function/ppas/sql/9.2_plus/create.sql
@@ -13,7 +13,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
 {% endif -%}
 {% endif -%}
-    RETURNS{% if data.proretset (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 %}
index 0000000..3f9151f
@@ -0,0 +1,50 @@
+{% if data.variables %}{% for v in data.variables %}
+SET {{ conn|qtIdent(v.name) }}={{ v.value|qtLiteral }}{% endfor -%}
index 0000000..e1629e9
@@ -0,0 +1,106 @@
+{% if data.merged_variables %}{% for v in data.merged_variables %}

 
 
 import uuid

 
-    def setUp(self):

     def tearDown(self):
         pass
 
 import uuid

 
-    def setUp(self):

     def tearDown(self):
         pass

 
-    def setUp(self):

 
     def tearDown(self):
         pass

 
     def __init__(self, *args, **kwargs):
         self.min_ver = 0
-        self.max_ver = 1000000000
+        self.max_ver = 1100000000
+        self.min_ppasver = 0
+        self.max_ppasver = 1100000000
         self.server_type = None
         self.min_gpdbver = 80323
         self.max_gpdbver = 1000000000
@@ -72,6 +74,9 @@ class PGChildModule(object):
         if self.server_type is None or manager.server_type in self.server_type:
             min_server_version = self.min_ver
             max_server_version = self.max_ver
+            if manager.server_type == 'ppas':
+                min_server_version = self.min_ppasver
+                max_server_version = self.max_ppasver
             if manager.server_type == 'gpdb':
                 min_server_version = self.min_gpdbver
                 max_server_version = self.max_gpdbver
diff --git a/web/pgadmin/tools/debugger/__init__.py b/web/pgadmin/tools/debugger/__init__.py
index c0e89f5..1bf797c 100644
--- a/web/pgadmin/tools/debugger/__init__.py
+++ b/web/pgadmin/tools/debugger/__init__.py
@@ -365,8 +365,11 @@ def init_function(node_type, sid, did, scid, fid, trid=None):
 
     # Check server type is ppas or not
     ppas_server = False
+    is_proc_supported = False
     if server_type == 'ppas':
         ppas_server = True
+    else:
+        is_proc_supported = True if manager.version >= 110000 else False
 
     # Set the template path required to read the sql files
     template_path = 'debugger/sql'
@@ -395,7 +398,10 @@ def init_function(node_type, sid, did, scid, fid, trid=None):
     sql = render_template(
         "/".join([template_path, 'get_function_debug_info.sql']),
         is_ppas_database=ppas_server,
-        hasFeatureFunctionDefaults=True, fid=fid
+        hasFeatureFunctionDefaults=True,
+        fid=fid,
+        is_proc_supported=is_proc_supported
+
     )
     status, r_set = conn.execute_dict(sql)
     if not status:
@@ -509,6 +515,7 @@ def init_function(node_type, sid, did, scid, fid, trid=None):
         'oid': fid,
         'name': r_set['rows'][0]['name'],
         'is_func': r_set['rows'][0]['isfunc'],
+        'is_ppas_database': ppas_server,
         'is_callable': False,
         'schema': r_set['rows'][0]['schemaname'],
         'language': r_set['rows'][0]['lanname'],
@@ -765,6 +772,7 @@ def initialize_target(debug_type, sid, did, scid, func_id, tri_id=None):
         'oid': func_data['oid'],
         'name': func_data['name'],
         'is_func': func_data['is_func'],
+        'is_ppas_database': func_data['is_ppas_database'],
         'is_callable': func_data['is_callable'],
         'schema': func_data['schema'],
         'language': func_data['language'],
@@ -1062,7 +1070,8 @@ def start_debugger_listener(trans_id):
                     func_name=func_name,
                     is_func=session_function_data['is_func'],
                     ret_type=session_function_data['return_type'],
-                    data=session_function_data['args_value']
+                    data=session_function_data['args_value'],
+                    is_ppas_database=session_function_data['is_ppas_database']
                 )
 
             status, result = conn.execute_async(str_query)
@@ -1946,8 +1955,9 @@ def poll_end_execution_result(trans_id):
         status, result = conn.poll()
         session_function_data = session['functionData'][str(trans_id)]
         if status == ASYNC_OK and \
-                not session_function_data['is_func'] and \
-                session_function_data['language'] == 'edbspl':
+            not session_function_data['is_func'] and\
+            (session_function_data['language'] == 'edbspl' or
+                session_function_data['language'] == 'plpgsql'):
             status = 'Success'
             additional_msgs = conn.messages()
             if len(additional_msgs) > 0:
diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql
index b82578b..134a17f 100644
--- a/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql
+++ b/web/pgadmin/tools/debugger/templates/debugger/sql/execute_plpgsql.sql
@@ -1,6 +1,8 @@
 {### Create executer function for plpgsql function debugging ###}
-{% if not is_func %}
+{% if is_ppas_database and not is_func %}
     EXEC {{ func_name }} (
+{% elif not is_func %}
+    CALL {{ func_name }} (
 {% elif ret_type == 'record' %}
     SELECT {{ func_name }} (
 {% else %}
diff --git a/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql b/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql
index 701f5ee..2807cad 100644
--- a/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql
+++ b/web/pgadmin/tools/debugger/templates/debugger/sql/get_function_debug_info.sql
@@ -41,7 +41,11 @@ SELECT
         0 AS pkgconsoid,
         n.oid     AS schema,
         n.nspname AS schemaname,
-        true AS isfunc,
+        {% if is_proc_supported %}
+        CASE WHEN p.prokind in ('f', 'w') THEN TRUE ELSE FALSE END AS isfunc,
+        {% else %}
+        TRUE AS isfunc,
+        {% endif %}
     {%endif%}
     pg_catalog.pg_get_function_identity_arguments(p.oid) AS signature,
 
@@ -61,4 +65,4 @@ FROM
 {% endif %}
 {% if fid %}
 WHERE p.oid = {{fid}}::oid;
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/web/pgadmin/utils/versioned_template_loader.py b/web/pgadmin/utils/versioned_template_loader.py
index 3481978..dadaeba 100644
--- a/web/pgadmin/utils/versioned_template_loader.py
+++ b/web/pgadmin/utils/versioned_template_loader.py
@@ -75,7 +75,8 @@ def get_version_mapping(template):
             {'name': "default", 'number': 0}
         )

---

From: Victoria Henry <[email-masked]>
Date: Tue, 12 Jun 2018 12:06:19 -0400

Hi Khushboo,

The following change is allowing the creation of procedures in postgresql
versions less then 11 and also GreenPlum

Now that the Procedures are a thing in Postgresql maybe they should live in
their own module.
In the tests for trigger functions we are not consistent on the naming of
the utils , in some places we call it funcs_utils in others
trigger_funcs_utils.

Thanks
​
Victoria & Joao

---

From: Khushboo Vashi <[email-masked]>
Date: Wed, 13 Jun 2018 09:32:48 +0530

Hi Victoria,

The updated patch is attached.

On Tue, Jun 12, 2018 at 9:36 PM, Victoria Henry <[email-masked]> wrote:

> Fixed.

> Now that the Procedures are a thing in Postgresql maybe they should live
> in their own module.
>
The main functionalities of the functions and procedures are almost same
and we have inherited most of the things from function itself.
So, as per me they should live in one module.

> the utils , in some places we call it funcs_utils in others
Fixed.

Khushboo

 

 

-        self.min_ver = 90100
+        self.min_ver = 110000
         self.max_ver = None
 
     def get_nodes(self, gid, sid, did, scid):
         """
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/procedure.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/procedure.js
index 041b509..765d6e2 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/procedure.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/procedure.js
@@ -71,10 +71,13 @@ define('pgadmin.node.procedure', [
         if ('catalog' in node_hierarchy)
           return false;
 
+        // Procedures supported only in PPAS and PG >= 11
         return (
+          'server' in node_hierarchy && (
+          node_hierarchy['server'].server_type == 'ppas' ||
+            (node_hierarchy['server'].server_type == 'pg' &&
+             node_hierarchy['server'].version >= 110000)
         );
@@ -103,20 +106,24 @@ define('pgadmin.node.procedure', [

 
index 3d1fe9f..e2faa4c 100644
@@ -9,15 +9,11 @@
 
 import uuid
 
-from . import utils as trigger_funcs_utils

 class TriggerFuncDeleteTestCase(BaseTestGenerator):
@@ -29,49 +25,27 @@ class TriggerFuncDeleteTestCase(BaseTestGenerator):
          dict(url='/browser/trigger_function/obj/'))
     ]
 
-    def setUp(self):
+        function_info = funcs_utils.create_trigger_function(

     def tearDown(self):
         pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_get.py
index 7eb0d2a..53fdbeb 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_get.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_get.py
@@ -9,15 +9,11 @@
 
 import uuid
 
-from . import utils as trigger_funcs_utils

 class TriggerFuncGetTestCase(BaseTestGenerator):
@@ -29,49 +25,26 @@ class TriggerFuncGetTestCase(BaseTestGenerator):
          dict(url='/browser/trigger_function/obj/'))
     ]
 
-    def setUp(self):
+        function_info = funcs_utils.create_trigger_function(

     def tearDown(self):
         pass
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_put.py
index 63d4c08..f1136d5 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_put.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_trigger_func_put.py
@@ -10,15 +10,11 @@
 import json
 import uuid
 
-from . import utils as trigger_funcs_utils

 class TriggerFuncPutTestCase(BaseTestGenerator):
@@ -30,63 +26,33 @@ class TriggerFuncPutTestCase(BaseTestGenerator):
          dict(url='/browser/trigger_function/obj/'))
     ]
 
-    def setUp(self):
+        function_info = funcs_utils.create_trigger_function(

 
     def tearDown(self):
         pass

 

 
 
@@ -61,4 +65,4 @@ FROM
 {% endif %}
 {% if fid %}

---

From: Khushboo Vashi <[email-masked]>
Date: Wed, 13 Jun 2018 17:13:48 +0530

Please ignore my previous patch. Find the attached updated patch.

On Wed, Jun 13, 2018 at 9:32 AM, Khushboo Vashi <
[email-masked]> wrote:

>>>> - The debugger fails to start (though, perhaps that's because the
>>>> plugin doesn't have Ashesh's latest patches in it).
>>>>

 

 
 
-        self.min_ver = 90100
+        self.min_ver = 110000
         self.max_ver = None

 

 
 
 import uuid

 
-    def setUp(self):

     def tearDown(self):
         pass
 
 import uuid

 
-    def setUp(self):

     def tearDown(self):
         pass

 
-    def setUp(self):

 
     def tearDown(self):
         pass
index 4facf60..27038e5 100644

+                " RETURNS integer LANGUAGE 'sql' STABLE" \

 

@@ -61,4 +65,4 @@ FROM
 {% endif %}
 {% if fid %}

---

From: Dave Page <[email-masked]>
Date: Wed, 13 Jun 2018 15:03:32 +0100

Thanks, patch applied.

On Wed, Jun 13, 2018 at 12:43 PM, Khushboo Vashi <
[email-masked]> wrote:

>>> The following change is allowing the creation of procedures in
>>> postgresql versions less then 11 and also GreenPlum
>>>
>>> In the tests for trigger functions we are not consistent on the naming
>>> of the utils , in some places we call it funcs_utils in others
>>> trigger_funcs_utils.
>>>
>> Fixed.
>>
>>> Thanks
>>> ​
>>> Victoria & Joao
>>>
>>> Thanks,
>> Khushboo
>>

---

From: Victoria Henry <[email-masked]>
Date: Wed, 13 Jun 2018 11:00:41 -0400

Hi,

Khushboo, thanks for making some of the changes we mentioned in our
previous email.
But when you say that they share logic and the code is

if self.node_type == 'procedure':

50+ lines of code

else:

50+ lines of code

This tells us that they share some logic but there is a huge difference
between them. This breaks the Single Responsibility Principle, that says
that each class should have only one responsibility on the context of the
application. Even if we completely ignore SOLID principles the clear
opportunity for a refactor that would make the code much easier to read was
lost, when last Monday we agreed that we should not lose these
opportunities.

Dave, we don't understand why this change was committed into master, when
there was a clear disagreement.

Sincerely,

Victoria && Joao

On Wed, Jun 13, 2018 at 10:03 AM Dave Page <[email-masked]> wrote:

>>>> Now that the Procedures are a thing in Postgresql maybe they should
>>>> live in their own module.
>>>>

---

From: Dave Page <[email-masked]>
Date: Wed, 13 Jun 2018 16:25:15 +0100

On Wed, Jun 13, 2018 at 4:00 PM, Victoria Henry <[email-masked]> wrote:

> application.

The responsibility here is to handle the objects from pg_proc which are
fundamentally almost identical bar a couple of nuances in the SQL spec.
Looking at the code you quote (or at least that I think you're quoting), I
see there's maybe duplication of 50% - 75% or so of the code in each branch
which probably could be cleaned up.

> Even if we completely ignore SOLID principles the clear opportunity for a
> refactor that would make the code much easier to read was lost, when last
> Monday we agreed that we should not lose these opportunities.
>

Don't mistake not spotting an opportunity with intentionally ignoring it.

> there was a clear disagreement.

I must be missing an email then. I only saw one from you previously, to
which Khushboo addressed all the concerns you raised.