Fix a number of debugger execution issues. Fixes #3191

1. EPAS packages' function/procedure does not honour INOUT arguments, it converts INOUT to OUT.

2.  Packages' functions and procedures are not getting listed in their respected nodes in some scenarios like procedure having INOUT argument and function with void return type

3. The Reverse engineering SQL is not correct for Packages' functions/procedures

4. In case of INOUT argument, debugger asks for mendatory input which should not.

5. Re-executing a procedure whilst direct debugging doesn't work.
This commit is contained in:
Khushboo Vashi 2018-07-17 12:51:24 +01:00 committed by Dave Page
parent 39b41d7b24
commit 8869fd6eb3
36 changed files with 176 additions and 113 deletions

View File

@ -16,6 +16,7 @@ Features
Bug fixes Bug fixes
********* *********
| `Bug #3191 <https://redmine.postgresql.org/issues/3191>`_ - Fix a number of debugger execution issues.
| `Bug #3294 <https://redmine.postgresql.org/issues/3294>`_ - Infrastructure (and changes to the Query Tool) for realtime preference handling. | `Bug #3294 <https://redmine.postgresql.org/issues/3294>`_ - Infrastructure (and changes to the Query Tool) for realtime preference handling.
| `Bug #3309 <https://redmine.postgresql.org/issues/3309>`_ - Fix Directory format support for backups. | `Bug #3309 <https://redmine.postgresql.org/issues/3309>`_ - Fix Directory format support for backups.
| `Bug #3316 <https://redmine.postgresql.org/issues/3316>`_ - Support running on systems without a system tray. | `Bug #3316 <https://redmine.postgresql.org/issues/3316>`_ - Support running on systems without a system tray.

View File

@ -391,32 +391,13 @@ class EdbFuncView(PGChildNodeView, DataTypeReader):
proargmodenames = {'i': 'IN', 'o': 'OUT', 'b': 'INOUT', proargmodenames = {'i': 'IN', 'o': 'OUT', 'b': 'INOUT',
'v': 'VARIADIC', 't': 'TABLE'} 'v': 'VARIADIC', 't': 'TABLE'}
# The proargtypes doesn't give OUT params, so we need to fetch # EPAS explicitly converts OUT to INOUT, So we always have proargtypes
# those from database explicitly, below code is written for this
# purpose.
#
# proallargtypes gives all the Function's argument including OUT,
# but we have not used that column; as the data type of this
# column (i.e. oid[]) is not supported by oidvectortypes(oidvector)
# function which we have used to fetch the datatypes
# of the other parameters.
proargmodes_fltrd = copy.deepcopy(proargmodes) proargmodes_fltrd = copy.deepcopy(proargmodes)
proargnames_fltrd = [] proargnames_fltrd = []
cnt = 0 cnt = 0
for m in proargmodes: for m in proargmodes:
if m == 'o': # Out Mode if m in ['v', 'o']: # Out / Variadic Mode
SQL = render_template("/".join([self.sql_template_path,
'get_out_types.sql']),
out_arg_oid=proallargtypes[cnt])
status, out_arg_type = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=out_arg_type)
# Insert out parameter datatype
proargtypes.insert(cnt, out_arg_type)
proargdefaultvals.insert(cnt, '')
elif m == 'v': # Variadic Mode
proargdefaultvals.insert(cnt, '') proargdefaultvals.insert(cnt, '')
elif m == 't': # Table Mode elif m == 't': # Table Mode
proargmodes_fltrd.remove(m) proargmodes_fltrd.remove(m)
@ -532,8 +513,7 @@ class EdbFuncView(PGChildNodeView, DataTypeReader):
""" """
SQL = render_template( SQL = render_template(
"/".join([self.sql_template_path, 'get_body.sql']), "/".join([self.sql_template_path, 'get_body.sql']),
scid=scid, edbfnid=edbfnid)
pkgid=pkgid)
status, res = self.conn.execute_dict(SQL) status, res = self.conn.execute_dict(SQL)
if not status: if not status:
@ -543,7 +523,7 @@ class EdbFuncView(PGChildNodeView, DataTypeReader):
gettext("Could not find the function in the database.") gettext("Could not find the function in the database.")
) )
body = self.get_inner(res['rows'][0]['pkgbodysrc']) body = res['rows'][0]['funcdef']
if body is None: if body is None:
body = '' body = ''

View File

@ -12,6 +12,6 @@ JOIN
pg_namespace nsp ON nsp.oid=pr.pronamespace pg_namespace nsp ON nsp.oid=pr.pronamespace
AND nsp.nspname={{ nspname|qtLiteral }} AND nsp.nspname={{ nspname|qtLiteral }}
WHERE WHERE
proisagg = FALSE pr.prokind IN ('f', 'w')
AND typname NOT IN ('trigger', 'event_trigger') AND typname NOT IN ('trigger', 'event_trigger')
AND pr.proname = {{ name|qtLiteral }}; AND pr.proname = {{ name|qtLiteral }};

View File

@ -5,7 +5,7 @@ SELECT pg_proc.oid,
proargnames AS argnames, proargnames AS argnames,
pronargdefaults, pronargdefaults,
oidvectortypes(proargtypes) AS proargtypenames, oidvectortypes(proargtypes) AS proargtypenames,
proargmodes, proargdeclaredmodes AS proargmodes,
proargnames, proargnames,
pg_get_expr(proargdefaults, 'pg_catalog.pg_class'::regclass) AS proargdefaultvals, pg_get_expr(proargdefaults, 'pg_catalog.pg_class'::regclass) AS proargdefaultvals,
pg_get_userbyid(proowner) AS funcowner, pg_get_userbyid(proowner) AS funcowner,
@ -17,7 +17,7 @@ SELECT pg_proc.oid,
WHEN proaccess = '-' THEN 'Private' WHEN proaccess = '-' THEN 'Private'
ELSE 'Unknown' END AS visibility ELSE 'Unknown' END AS visibility
FROM pg_proc, pg_namespace, pg_language lng FROM pg_proc, pg_namespace, pg_language lng
WHERE format_type(prorettype, NULL) != 'void' WHERE pr.prokind IN ('f', 'w')
AND pronamespace = {{pkgid}}::oid AND pronamespace = {{pkgid}}::oid
AND pg_proc.pronamespace = pg_namespace.oid AND pg_proc.pronamespace = pg_namespace.oid
AND lng.oid=prolang AND lng.oid=prolang

View File

@ -1,5 +0,0 @@
SELECT pg_catalog.edb_get_packagebodydef(nsp.oid) AS pkgbodysrc
FROM pg_namespace nsp
LEFT OUTER JOIN pg_description des ON (des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE nspparent = {{scid}}::oid
AND nsp.oid = {{pkgid}}::oid;

View File

@ -1,11 +0,0 @@
SELECT pg_proc.oid,
pg_proc.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pg_proc.oid), '') || ')' AS name,
pg_get_userbyid(proowner) AS funcowner
FROM pg_proc, pg_namespace
WHERE format_type(prorettype, NULL) != 'void'
{% if fnid %}
AND pg_proc.oid = {{ fnid|qtLiteral }}
{% endif %}
AND pronamespace = {{ pkgid|qtLiteral }}::oid
AND pg_proc.pronamespace = pg_namespace.oid
ORDER BY pg_proc.proname

View File

@ -1,6 +0,0 @@
SELECT pg_catalog.edb_get_packagebodydef(nsp.oid) AS pkgbodysrc
FROM pg_namespace nsp
LEFT OUTER JOIN pg_description des ON (des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE nspparent = {{scid}}::oid
AND nsp.oid = {{pkgid}}::oid
AND nspobjecttype = 0;

View File

@ -1,10 +0,0 @@
SELECT pg_proc.oid,
pg_proc.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pg_proc.oid), '') || ')' AS name,
pg_get_userbyid(proowner) AS funcowner
FROM pg_proc, pg_namespace
WHERE format_type(prorettype, NULL) != 'void'
{% if fnid %}
AND pg_proc.oid = {{ fnid|qtLiteral }}
{% endif %}
AND pronamespace = {{pkgid|qtLiteral}}::oid
AND pg_proc.pronamespace = pg_namespace.oid

View File

@ -0,0 +1 @@
SELECT pg_get_functiondef({{edbfnid}}::oid) AS funcdef;

View File

@ -2,7 +2,7 @@ SELECT pg_proc.oid,
pg_proc.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pg_proc.oid), '') || ')' AS name, pg_proc.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pg_proc.oid), '') || ')' AS name,
pg_get_userbyid(proowner) AS funcowner pg_get_userbyid(proowner) AS funcowner
FROM pg_proc, pg_namespace FROM pg_proc, pg_namespace
WHERE format_type(prorettype, NULL) = 'void' WHERE protype = '0'::char
{% if fnid %} {% if fnid %}
AND pg_proc.oid = {{ fnid|qtLiteral }} AND pg_proc.oid = {{ fnid|qtLiteral }}
{% endif %} {% endif %}

View File

@ -5,7 +5,7 @@ SELECT pg_proc.oid,
proargnames AS argnames, proargnames AS argnames,
pronargdefaults, pronargdefaults,
oidvectortypes(proargtypes) AS proargtypenames, oidvectortypes(proargtypes) AS proargtypenames,
proargmodes, proargdeclaredmodes AS proargmodes,
proargnames, proargnames,
pg_get_expr(proargdefaults, 'pg_catalog.pg_class'::regclass) AS proargdefaultvals, pg_get_expr(proargdefaults, 'pg_catalog.pg_class'::regclass) AS proargdefaultvals,
pg_get_userbyid(proowner) AS funcowner, pg_get_userbyid(proowner) AS funcowner,
@ -17,7 +17,7 @@ SELECT pg_proc.oid,
WHEN proaccess = '-' THEN 'Private' WHEN proaccess = '-' THEN 'Private'
ELSE 'Unknown' END AS visibility ELSE 'Unknown' END AS visibility
FROM pg_proc, pg_namespace, pg_language lng FROM pg_proc, pg_namespace, pg_language lng
WHERE format_type(prorettype, NULL) = 'void' WHERE protype = '0'::char
AND pronamespace = {{pkgid}}::oid AND pronamespace = {{pkgid}}::oid
AND pg_proc.pronamespace = pg_namespace.oid AND pg_proc.pronamespace = pg_namespace.oid
AND lng.oid=prolang AND lng.oid=prolang

View File

@ -12,6 +12,6 @@ JOIN
pg_namespace nsp ON nsp.oid=pr.pronamespace pg_namespace nsp ON nsp.oid=pr.pronamespace
AND nsp.nspname={{ nspname|qtLiteral }} AND nsp.nspname={{ nspname|qtLiteral }}
WHERE WHERE
proisagg = FALSE pr.prokind = 'p'
AND typname NOT IN ('trigger', 'event_trigger') AND typname NOT IN ('trigger', 'event_trigger')
AND pr.proname = {{ name|qtLiteral }}; AND pr.proname = {{ name|qtLiteral }};

View File

@ -5,7 +5,7 @@ SELECT pg_proc.oid,
proargnames AS argnames, proargnames AS argnames,
pronargdefaults, pronargdefaults,
oidvectortypes(proargtypes) AS proargtypenames, oidvectortypes(proargtypes) AS proargtypenames,
proargmodes, proargdeclaredmodes AS proargmodes,
proargnames, proargnames,
pg_get_expr(proargdefaults, 'pg_catalog.pg_class'::regclass) AS proargdefaultvals, pg_get_expr(proargdefaults, 'pg_catalog.pg_class'::regclass) AS proargdefaultvals,
pg_get_userbyid(proowner) AS funcowner, pg_get_userbyid(proowner) AS funcowner,
@ -17,7 +17,7 @@ SELECT pg_proc.oid,
WHEN proaccess = '-' THEN 'Private' WHEN proaccess = '-' THEN 'Private'
ELSE 'Unknown' END AS visibility ELSE 'Unknown' END AS visibility
FROM pg_proc, pg_namespace, pg_language lng FROM pg_proc, pg_namespace, pg_language lng
WHERE format_type(prorettype, NULL) != 'void' WHERE pr.prokind = 'p'
AND pronamespace = {{pkgid}}::oid AND pronamespace = {{pkgid}}::oid
AND pg_proc.pronamespace = pg_namespace.oid AND pg_proc.pronamespace = pg_namespace.oid
AND lng.oid=prolang AND lng.oid=prolang

View File

@ -1,5 +0,0 @@
SELECT pg_catalog.edb_get_packagebodydef(nsp.oid) AS pkgbodysrc
FROM pg_namespace nsp
LEFT OUTER JOIN pg_description des ON (des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE nspparent = {{scid}}::oid
AND nsp.oid = {{pkgid}}::oid;

View File

@ -1,3 +0,0 @@
SELECT proname AS name
FROM pg_proc
WHERE oid = {{edbfnid}}::oid

View File

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

View File

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

View File

@ -1,6 +0,0 @@
SELECT pg_catalog.edb_get_packagebodydef(nsp.oid) AS pkgbodysrc
FROM pg_namespace nsp
LEFT OUTER JOIN pg_description des ON (des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE nspparent = {{scid}}::oid
AND nsp.oid = {{pkgid}}::oid
AND nspobjecttype = 0;

View File

@ -1,3 +0,0 @@
SELECT proname AS name
FROM pg_proc
WHERE oid = {{edbfnid}}::oid

View File

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

View File

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

View File

@ -0,0 +1 @@
SELECT pg_get_functiondef({{edbfnid}}::oid) AS funcdef;

View File

@ -2,7 +2,7 @@ SELECT pg_proc.oid,
pg_proc.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pg_proc.oid), '') || ')' AS name, pg_proc.proname || '(' || COALESCE(pg_catalog.pg_get_function_identity_arguments(pg_proc.oid), '') || ')' AS name,
pg_get_userbyid(proowner) AS funcowner pg_get_userbyid(proowner) AS funcowner
FROM pg_proc, pg_namespace FROM pg_proc, pg_namespace
WHERE format_type(prorettype, NULL) = 'void' WHERE protype = '1'::char
{% if fnid %} {% if fnid %}
AND pg_proc.oid = {{ fnid|qtLiteral }} AND pg_proc.oid = {{ fnid|qtLiteral }}
{% endif %} {% endif %}

View File

@ -17,7 +17,7 @@ SELECT pg_proc.oid,
WHEN proaccess = '-' THEN 'Private' WHEN proaccess = '-' THEN 'Private'
ELSE 'Unknown' END AS visibility ELSE 'Unknown' END AS visibility
FROM pg_proc, pg_namespace, pg_language lng FROM pg_proc, pg_namespace, pg_language lng
WHERE format_type(prorettype, NULL) = 'void' WHERE protype = '1'::char
AND pronamespace = {{pkgid}}::oid AND pronamespace = {{pkgid}}::oid
AND pg_proc.pronamespace = pg_namespace.oid AND pg_proc.pronamespace = pg_namespace.oid
AND lng.oid=prolang AND lng.oid=prolang

View File

@ -0,0 +1,16 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.utils.route import BaseTestGenerator
class PackageEDBFuncsTestGenerator(BaseTestGenerator):
def runTest(self):
return

View File

@ -0,0 +1,138 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import uuid
import json
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
class PackageEDBFuncsGetTestCase(BaseTestGenerator):
""" This class will fetch functions/procedures of package
under test schema. """
skip_on_database = ['gpdb', 'pg']
scenarios = [
# Fetching default URL for package node.
('Fetch Package Functions/Procedures URL', dict(
url='/browser/{0}/nodes/'))
]
def setUp(self):
super(PackageEDBFuncsGetTestCase, self).setUp()
schema_info = parent_node_dict["schema"][-1]
self.schema_id = schema_info["schema_id"]
self.schema_name = schema_info["schema_name"]
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.pkg_name = "pkg_%s" % str(uuid.uuid4())[1:8]
self.proc_name = "proc_%s" % str(uuid.uuid4())[1:8]
self.func_name = "func_%s" % str(uuid.uuid4())[1:8]
self.server_id = schema_info["server_id"]
self.db_id = schema_info["db_id"]
server_con = server_utils.connect_server(self, self.server_id)
connection = utils.get_db_connection(self.db_name,
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode'])
pg_cursor = connection.cursor()
query = """
CREATE OR REPLACE PACKAGE %s.%s
IS
emp_name character varying(10);
PROCEDURE %s(INOUT p_empno numeric);
FUNCTION %s() RETURN integer;
END %s;
CREATE OR REPLACE PACKAGE BODY %s.%s
IS
v_counter integer;
PROCEDURE %s(INOUT p_empno numeric) IS
BEGIN
SELECT ename INTO emp_name FROM emp WHERE empno = p_empno;
v_counter := v_counter + 1;
END;
FUNCTION %s() RETURN integer IS
BEGIN
RETURN v_counter;
END;
END %s;""" % (self.schema_name, self.pkg_name, self.proc_name,
self.func_name, self.pkg_name, self.schema_name,
self.pkg_name, self.proc_name, self.func_name,
self.pkg_name)
pg_cursor.execute(query)
connection.commit()
# Get 'oid' from newly created package
pg_cursor.execute("SELECT oid FROM pg_namespace"
" WHERE nspname='%s'" %
self.pkg_name)
self.package_id = pg_cursor.fetchone()[0]
connection.close()
def runTest(self):
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to database.")
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.")
# Fetch Package function
url = self.url.format('edbfunc') + str(
utils.SERVER_GROUP) + '/' + str(self.server_id) + '/' + str(
self.db_id) + '/' + str(self.schema_id) + '/' + str(
self.package_id) + "/"
response = self.tester.get(url,
content_type='html/json')
response_data = json.loads(response.data.decode('utf-8'))
self.assertEquals(response.status_code, 200)
self.assertEquals(len(response_data['data']), 1)
self.assertEquals(response_data['data'][0]['label'],
self.func_name + '()')
self.assertEquals(response_data['data'][0]['_type'], 'edbfunc')
# Fetch Package procedure
url = self.url.format('edbproc') + str(
utils.SERVER_GROUP) + '/' + str(self.server_id) + '/' + str(
self.db_id) + '/' + str(self.schema_id) + '/' + str(
self.package_id) + "/"
response = self.tester.get(url,
content_type='html/json')
response_data = json.loads(response.data.decode('utf-8'))
self.assertEquals(response.status_code, 200)
self.assertEquals(len(response_data['data']), 1)
self.assertIn(self.proc_name, response_data['data'][0]['label'])
self.assertIn("INOUT", response_data['data'][0]['label'])
self.assertEquals(response_data['data'][0]['_type'], 'edbproc')
def tearDown(self):
"""This function disconnect the test database."""
database_utils.disconnect_database(self, self.server_id,
self.db_id)

View File

@ -650,7 +650,6 @@ define([
// Start pooling again // Start pooling again
pgTools.DirectDebug.polling_timeout_idle = false; pgTools.DirectDebug.polling_timeout_idle = false;
pgTools.DirectDebug.is_polling_required = true; pgTools.DirectDebug.is_polling_required = true;
self.poll_end_execution_result(trans_id);
self.poll_result(trans_id); self.poll_result(trans_id);
if (restart_dbg) { if (restart_dbg) {

View File

@ -26,7 +26,11 @@ SELECT
pg_catalog.generate_series(0, pg_catalog.array_upper(proargtypes, 1)) s(i)), ',') pg_catalog.generate_series(0, pg_catalog.array_upper(proargtypes, 1)) s(i)), ',')
END AS proargtypes, END AS proargtypes,
pg_catalog.array_to_string(p.proargnames, ',') AS proargnames, pg_catalog.array_to_string(p.proargnames, ',') AS proargnames,
{% if is_ppas_database %}
pg_catalog.array_to_string(proargdeclaredmodes, ',') AS proargmodes,
{% else %}
pg_catalog.array_to_string(proargmodes, ',') AS proargmodes, pg_catalog.array_to_string(proargmodes, ',') AS proargmodes,
{% endif %}
{% if is_ppas_database %} {% if is_ppas_database %}
CASE WHEN n.nspparent <> 0 THEN n.oid ELSE 0 END AS pkg, CASE WHEN n.nspparent <> 0 THEN n.oid ELSE 0 END AS pkg,