1) Add Reverse Engineered and Modified SQL tests for Foreign Keys. Fixes #4616

2) Fix issue where Validated switch option is inverted for the Foreign Key. Fixes #4412
This commit is contained in:
Akshay Joshi 2019-09-11 15:59:13 +05:30
parent b4a754bdc0
commit 6a931588aa
22 changed files with 491 additions and 11 deletions

View File

@ -20,6 +20,7 @@ Housekeeping
| `Issue #4575 <https://redmine.postgresql.org/issues/4575>`_ - Add Reverse Engineered SQL tests for Schemas.
| `Issue #4576 <https://redmine.postgresql.org/issues/4576>`_ - Add Reverse Engineered SQL tests for Views.
| `Issue #4600 <https://redmine.postgresql.org/issues/4600>`_ - Add Reverse Engineered SQL tests for Rules.
| `Issue #4616 <https://redmine.postgresql.org/issues/4616>`_ - Add Reverse Engineered and Modified SQL tests for Foreign Keys.
| `Issue #4617 <https://redmine.postgresql.org/issues/4617>`_ - Add Reverse Engineered and Modified SQL tests for Foreign Servers.
| `Issue #4618 <https://redmine.postgresql.org/issues/4618>`_ - Add Reverse Engineered and Modified SQL tests for Foreign Tables.
| `Issue #4619 <https://redmine.postgresql.org/issues/4619>`_ - Add Reverse Engineered and Modified SQL tests for FTS Templates.
@ -35,6 +36,7 @@ Bug fixes
| `Issue #3778 <https://redmine.postgresql.org/issues/3778>`_ - Ensure Boolean columns should be editable using keyboard keys.
| `Issue #3936 <https://redmine.postgresql.org/issues/3936>`_ - Further code refactoring to stabilise the Feature Tests.
| `Issue #4381 <https://redmine.postgresql.org/issues/4381>`_ - Fix an issue where oid column should not be pasted when copy/paste row is used on query output containing the oid column.
| `Issue #4412 <https://redmine.postgresql.org/issues/4412>`_ - Fix issue where Validated switch option is inverted for the Foreign Key.
| `Issue #4419 <https://redmine.postgresql.org/issues/4419>`_ - Fix a debugger error when using Python 2.7.
| `Issue #4461 <https://redmine.postgresql.org/issues/4461>`_ - Fix error while importing data to a table using Import/Export dialog and providing "Not null columns" option.
| `Issue #4486 <https://redmine.postgresql.org/issues/4486>`_ - Ensure View should be created with special characters.

View File

@ -796,7 +796,7 @@ define('pgadmin.node.foreign_key', [
return !(_.isUndefined(m.get('oid')) || m.get('convalidated'));
}
// We can't update condeferred of existing foreign key.
return !(m.isNew() || m.get('convalidated'));
return !(m.isNew() || !m.get('convalidated'));
},
},{
id: 'autoindex', label: gettext('Auto FK index?'),

View File

@ -0,0 +1,13 @@
-- Constraint: FKey1_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.test_second_table DROP CONSTRAINT "FKey1_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey1_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH FULL
ON UPDATE NO ACTION
ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
COMMENT ON CONSTRAINT "FKey1_$%{}[]()&*^!@""'`\/#" ON testschema.test_second_table
IS 'Test Comment Update';

View File

@ -0,0 +1,8 @@
ALTER TABLE testschema.test_second_table
RENAME CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" TO "FKey1_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.test_second_table
VALIDATE CONSTRAINT "FKey1_$%{}[]()&*^!@""'`\/#";
COMMENT ON CONSTRAINT "FKey1_$%{}[]()&*^!@""'`\/#" ON testschema.test_second_table
IS 'Test Comment Update';

View File

@ -0,0 +1,10 @@
-- Constraint: FKey_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.test_second_table DROP CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE
NOT VALID;

View File

@ -0,0 +1,6 @@
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE
NOT VALID;

View File

@ -0,0 +1,10 @@
-- Constraint: FKey_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.test_second_table DROP CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE RESTRICT
ON DELETE RESTRICT
NOT VALID;

View File

@ -0,0 +1,6 @@
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE RESTRICT
ON DELETE RESTRICT
NOT VALID;

View File

@ -0,0 +1,10 @@
-- Constraint: FKey_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.test_second_table DROP CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE SET DEFAULT
ON DELETE SET DEFAULT
NOT VALID;

View File

@ -0,0 +1,6 @@
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE SET DEFAULT
ON DELETE SET DEFAULT
NOT VALID;

View File

@ -0,0 +1,10 @@
-- Constraint: FKey_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.test_second_table DROP CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE SET NULL
ON DELETE SET NULL
NOT VALID;

View File

@ -0,0 +1,6 @@
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE SET NULL
ON DELETE SET NULL
NOT VALID;

View File

@ -0,0 +1,10 @@
-- Constraint: FKey_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.test_second_table DROP CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;

View File

@ -0,0 +1,6 @@
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID;

View File

@ -0,0 +1,14 @@
-- Constraint: FKey_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.test_second_table DROP CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH FULL
ON UPDATE NO ACTION
ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED
NOT VALID;
COMMENT ON CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" ON testschema.test_second_table
IS 'Test Comment';

View File

@ -0,0 +1,13 @@
ALTER TABLE testschema.test_second_table
ADD CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" FOREIGN KEY (so_id)
REFERENCES testschema.test_first_table (id) MATCH FULL
ON UPDATE NO ACTION
ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED
NOT VALID;
COMMENT ON CONSTRAINT "FKey_$%{}[]()&*^!@""'`\/#" ON testschema.test_second_table
IS 'Test Comment';
CREATE INDEX "fki_FKey_$%{}[]()&*^!@""'`\/#"
ON testschema.test_second_table(so_id);

View File

@ -0,0 +1,293 @@
{
"scenarios": [
{
"type": "create",
"name": "Create First Table for Foreign Key",
"endpoint": "NODE-table.obj",
"sql_endpoint": "NODE-table.sql_id",
"data": {
"name": "test_first_table",
"columns": [{
"name": "id",
"cltype": "integer",
"is_primary_key": true
}, {
"name": "customer_id",
"cltype": "integer"
}],
"is_partitioned": false,
"schema": "testschema",
"spcname": "pg_default",
"primary_key":[{
"columns": [{
"column": "id"
}],
"include": []
}]
},
"store_object_id": true
}, {
"type": "create",
"name": "Create Second Table for Foreign Key",
"endpoint": "NODE-table.obj",
"sql_endpoint": "NODE-table.sql_id",
"data": {
"name": "test_second_table",
"columns": [{
"name": "item_id",
"cltype": "integer",
"is_primary_key": true
}, {
"name": "so_id",
"cltype": "integer",
"is_primary_key": true
}, {
"name": "product_id",
"cltype": "integer"
}, {
"name": "qty",
"cltype": "integer"
}],
"is_partitioned": false,
"schema": "testschema",
"spcname": "pg_default",
"primary_key": [{
"columns": [{
"column": "item_id"
},{
"column": "so_id"
}],
"include": []
}]
},
"store_object_id": true
}, {
"type": "create",
"name": "Create Foreign Key Constraint with all options",
"endpoint": "NODE-foreign_key.obj",
"sql_endpoint": "NODE-foreign_key.sql_id",
"msql_endpoint": "NODE-foreign_key.msql",
"data": {
"name":"FKey_$%{}[]()&*^!@\"'`\\/#",
"comment":"Test Comment",
"condeferrable":true,
"condeferred":true,
"confmatchtype":true,
"convalidated":false,
"columns":[{
"local_column":"so_id",
"references":"<test_first_table>",
"referenced":"id"
}],
"confupdtype":"a",
"confdeltype":"a",
"autoindex":true,
"coveringindex":"fki_FKey_$%{}[]()&*^!@\"'`\\/#"
},
"expected_sql_file": "create_foreign_key_with_all_options.sql",
"expected_msql_file": "create_foreign_key_with_all_options_msql.sql",
"preprocess_data": true
}, {
"type": "alter",
"name": "Alter name, comment and validate option",
"endpoint": "NODE-foreign_key.obj_id",
"sql_endpoint": "NODE-foreign_key.sql_id",
"msql_endpoint": "NODE-foreign_key.msql_id",
"data": {
"name":"FKey1_$%{}[]()&*^!@\"'`\\/#",
"comment":"Test Comment Update",
"convalidated": true
},
"expected_sql_file": "alter_name_comment_validation.sql",
"expected_msql_file": "alter_name_comment_validation_msql.sql"
}, {
"type": "delete",
"name": "Drop Foreign Key",
"endpoint": "NODE-foreign_key.delete_id",
"data": {
"name": "FKey1_$%{}[]()&*^!@\"'`\\/#"
}
}, {
"type": "create",
"name": "Create Foreign Key Constraint with match type simple",
"endpoint": "NODE-foreign_key.obj",
"sql_endpoint": "NODE-foreign_key.sql_id",
"msql_endpoint": "NODE-foreign_key.msql",
"data": {
"name":"FKey_$%{}[]()&*^!@\"'`\\/#",
"condeferrable":false,
"condeferred":false,
"confmatchtype":false,
"convalidated":false,
"columns":[{
"local_column":"so_id",
"references":"<test_first_table>",
"referenced":"id"
}],
"confupdtype":"a",
"confdeltype":"a",
"autoindex":false,
"coveringindex":""
},
"expected_sql_file": "create_foreign_key_simple.sql",
"expected_msql_file": "create_foreign_key_simple_msql.sql",
"preprocess_data": true
}, {
"type": "delete",
"name": "Drop Foreign Key",
"endpoint": "NODE-foreign_key.delete_id",
"data": {
"name": "FKey_$%{}[]()&*^!@\"'`\\/#"
}
}, {
"type": "create",
"name": "Create Foreign Key Constraint with RESTRICT action",
"endpoint": "NODE-foreign_key.obj",
"sql_endpoint": "NODE-foreign_key.sql_id",
"msql_endpoint": "NODE-foreign_key.msql",
"data": {
"name":"FKey_$%{}[]()&*^!@\"'`\\/#",
"condeferrable":false,
"condeferred":false,
"confmatchtype":false,
"convalidated":false,
"columns":[{
"local_column":"so_id",
"references":"<test_first_table>",
"referenced":"id"
}],
"confupdtype":"r",
"confdeltype":"r",
"autoindex":false,
"coveringindex":""
},
"expected_sql_file": "create_foreign_key_restrict.sql",
"expected_msql_file": "create_foreign_key_restrict_msql.sql",
"preprocess_data": true
}, {
"type": "delete",
"name": "Drop Foreign Key",
"endpoint": "NODE-foreign_key.delete_id",
"data": {
"name": "FKey_$%{}[]()&*^!@\"'`\\/#"
}
}, {
"type": "create",
"name": "Create Foreign Key Constraint with CASCADE action",
"endpoint": "NODE-foreign_key.obj",
"sql_endpoint": "NODE-foreign_key.sql_id",
"msql_endpoint": "NODE-foreign_key.msql",
"data": {
"name":"FKey_$%{}[]()&*^!@\"'`\\/#",
"condeferrable":false,
"condeferred":false,
"confmatchtype":false,
"convalidated":false,
"columns":[{
"local_column":"so_id",
"references":"<test_first_table>",
"referenced":"id"
}],
"confupdtype":"c",
"confdeltype":"c",
"autoindex":false,
"coveringindex":""
},
"expected_sql_file": "create_foreign_key_cascade.sql",
"expected_msql_file": "create_foreign_key_cascade_msql.sql",
"preprocess_data": true
}, {
"type": "delete",
"name": "Drop Foreign Key",
"endpoint": "NODE-foreign_key.delete_id",
"data": {
"name": "FKey_$%{}[]()&*^!@\"'`\\/#"
}
}, {
"type": "create",
"name": "Create Foreign Key Constraint with SET NULL action",
"endpoint": "NODE-foreign_key.obj",
"sql_endpoint": "NODE-foreign_key.sql_id",
"msql_endpoint": "NODE-foreign_key.msql",
"data": {
"name":"FKey_$%{}[]()&*^!@\"'`\\/#",
"condeferrable":false,
"condeferred":false,
"confmatchtype":false,
"convalidated":false,
"columns":[{
"local_column":"so_id",
"references":"<test_first_table>",
"referenced":"id"
}],
"confupdtype":"n",
"confdeltype":"n",
"autoindex":false,
"coveringindex":""
},
"expected_sql_file": "create_foreign_key_setnull.sql",
"expected_msql_file": "create_foreign_key_setnull_msql.sql",
"preprocess_data": true
}, {
"type": "delete",
"name": "Drop Foreign Key",
"endpoint": "NODE-foreign_key.delete_id",
"data": {
"name": "FKey_$%{}[]()&*^!@\"'`\\/#"
}
}, {
"type": "create",
"name": "Create Foreign Key Constraint with SET DEFAULT action",
"endpoint": "NODE-foreign_key.obj",
"sql_endpoint": "NODE-foreign_key.sql_id",
"msql_endpoint": "NODE-foreign_key.msql",
"data": {
"name":"FKey_$%{}[]()&*^!@\"'`\\/#",
"condeferrable":false,
"condeferred":false,
"confmatchtype":false,
"convalidated":false,
"columns":[{
"local_column":"so_id",
"references":"<test_first_table>",
"referenced":"id"
}],
"confupdtype":"d",
"confdeltype":"d",
"autoindex":false,
"coveringindex":""
},
"expected_sql_file": "create_foreign_key_setdefault.sql",
"expected_msql_file": "create_foreign_key_setdefault_msql.sql",
"preprocess_data": true
}, {
"type": "delete",
"name": "Drop Foreign Key",
"endpoint": "NODE-foreign_key.delete_id",
"data": {
"name": "FKey_$%{}[]()&*^!@\"'`\\/#"
}
}, {
"type": "delete",
"name": "Drop Foreign Key",
"endpoint": "NODE-foreign_key.delete_id",
"data": {
"name": "FKey_$%{}[]()&*^!@\"'`\\/#"
}
}, {
"type": "delete",
"name": "Drop First Table",
"endpoint": "NODE-table.delete_id",
"data": {
"name": "test_first_table"
}
}, {
"type": "delete",
"name": "Drop Second Table",
"endpoint": "NODE-table.delete_id",
"data": {
"name": "test_second_table"
}
}
]
}

View File

@ -16,7 +16,7 @@ SELECT ct.oid,
nr.nspname as refnsp,
cr.relname as reftab,
description as comment,
NOT convalidated as convalidated
convalidated
FROM pg_constraint ct
JOIN pg_class cl ON cl.oid=conrelid
JOIN pg_namespace nl ON nl.oid=cl.relnamespace
@ -28,4 +28,4 @@ conrelid = {{tid}}::oid
{% if cid %}
AND ct.oid = {{cid}}::oid
{% endif %}
ORDER BY conname
ORDER BY conname

View File

@ -22,11 +22,12 @@ ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
DEFERRABLE{% if data.condeferred %}
INITIALLY DEFERRED{% endif%}
{% endif%}
{% if data.convalidated %}
{% if not data.convalidated %}
NOT VALID{% endif%};
{% if data.comment and data.name %}
COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }}
IS {{ data.comment|qtLiteral }};
{% endif %}
{% endif %}

View File

@ -1,5 +1,5 @@
SELECT
FALSE as convalidated,
convalidated,
ct.oid,
conname as name,
condeferrable,
@ -29,4 +29,4 @@ conrelid = {{tid}}::oid
{% if cid %}
AND ct.oid = {{cid}}::oid
{% endif %}
ORDER BY conname
ORDER BY conname

View File

@ -4,15 +4,17 @@
{% if data.name != o_data.name %}
ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
RENAME CONSTRAINT {{ conn|qtIdent(o_data.name) }} TO {{ conn|qtIdent(data.name) }};
{% endif %}
{# ==== To update foreign key validate ==== #}
{% if 'convalidated' in data and o_data.convalidated != data.convalidated and not data.convalidated %}
{% if 'convalidated' in data and o_data.convalidated != data.convalidated and data.convalidated %}
ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
VALIDATE CONSTRAINT {{ conn|qtIdent(data.name) }};
{% endif %}
{# ==== To update foreign key comments ==== #}
{% if data.comment is defined and data.comment != o_data.comment %}
COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }}
IS {{ data.comment|qtLiteral }};
{% endif %}
{% endif %}
{% endif %}

View File

@ -80,6 +80,7 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator):
# Status of the test case
self.final_test_status = True
self.parent_ids = dict()
self.all_object_ids = dict()
# Added line break after scenario name
print("")
@ -128,6 +129,7 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator):
# Clear the parent ids stored for one json file.
self.parent_ids.clear()
self.all_object_ids.clear()
# Check the final status of the test case
self.assertEqual(self.final_test_status, True)
@ -214,6 +216,11 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator):
# Check precondition for schema
self.check_schema_precondition(scenario)
# Preprocessed data to replace any place holder if available
if 'preprocess_data' in scenario and \
scenario['preprocess_data'] and 'data' in scenario:
scenario['data'] = self.preprocess_data(scenario['data'])
# If msql_endpoint exists then validate the modified sql
if 'msql_endpoint' in scenario\
and scenario['msql_endpoint']:
@ -248,7 +255,9 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator):
# Store the object id based on endpoints
if 'store_object_id' in scenario:
self.store_object_ids(object_id, scenario['endpoint'])
self.store_object_ids(object_id,
scenario['data']['name'],
scenario['endpoint'])
# Compare the reverse engineering SQL
if not self.check_re_sql(scenario, object_id):
@ -577,10 +586,11 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator):
return sql
def store_object_ids(self, object_id, endpoint):
def store_object_ids(self, object_id, object_name, endpoint):
"""
This functions will store the object id based on endpoints
:param object_id: Object id of the created node
:param object_name: Object name
:param endpoint:
:return:
"""
@ -590,3 +600,47 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator):
self.parent_ids['fid'] = object_id
elif endpoint.__contains__("NODE-foreign_server"):
self.parent_ids['fsid'] = object_id
# Store object id with object name
self.all_object_ids[object_name] = object_id
def preprocess_data(self, data):
"""
This function iterate through data and check for any place holder
starts with '<' and ends with '>' and replace with respective object
ids.
:param data: Data
:return:
"""
if isinstance(data, dict):
for key, val in data.items():
if isinstance(val, dict) or isinstance(val, list):
data[key] = self.preprocess_data(val)
else:
data[key] = self.replace_placeholder_with_id(val)
elif isinstance(data, list):
ret_list = []
for item in data:
if isinstance(item, dict) or isinstance(item, list):
ret_list.append(self.preprocess_data(item))
else:
ret_list.append(self.replace_placeholder_with_id(item))
return ret_list
return data
def replace_placeholder_with_id(self, value):
"""
This function is used to replace the place holder with id.
:param value:
:return:
"""
if isinstance(value, str) and \
value.startswith('<') and value.endswith('>'):
# Remove < and > from the string
temp_value = value[1:-1]
# Find the place holder OID in dictionary
if temp_value in self.all_object_ids:
return self.all_object_ids[temp_value]
return value