From 4dbe0588321d8c391f0adbbe84f2d34d554ab71b Mon Sep 17 00:00:00 2001 From: Akshay Joshi Date: Fri, 20 May 2016 17:15:52 +0530 Subject: [PATCH] Implementation of Table, Column, Index, Trigger, Constraints and Rule Node. - Table, Column, Index and Trigger (Author:- Murtuza Zabuawala) - Constraints Primary Key/Unique/Check/Foreign Key/Exclusion with integration into Table node (Author:- Harshal Dhumal) - Rule (Author:- Surinder Kumar) - Vacuum Control (Initial patch by Surinder Kumar and further enhancement by Murtuza) --- .../databases/schemas/tables/__init__.py | 2809 +++++++++++++++++ .../schemas/tables/column/__init__.py | 906 ++++++ .../tables/column/static/img/coll-column.png | Bin 0 -> 400 bytes .../tables/column/static/img/column.png | Bin 0 -> 435 bytes .../column/templates/column/js/column.js | 586 ++++ .../templates/column/macros/privilege.macros | 13 + .../templates/column/macros/security.macros | 6 + .../schemas/tables/constraints/__init__.py | 133 + .../constraints/check_constraint/__init__.py | 833 +++++ .../static/img/check-constraints-bad.png | Bin 0 -> 579 bytes .../static/img/check-constraints.png | Bin 0 -> 406 bytes .../check_constraint/css/check_constraint.css | 15 + .../check_constraint/js/check_constraint.js | 216 ++ .../exclusion_constraint/__init__.py | 820 +++++ .../static/img/exclusion_constraint.png | Bin 0 -> 725 bytes .../js/exclusion_constraint.js | 915 ++++++ .../constraints/foreign_key/__init__.py | 1072 +++++++ .../foreign_key/static/img/foreign_key.png | Bin 0 -> 299 bytes .../static/img/foreign_key_no_validate.png | Bin 0 -> 618 bytes .../templates/foreign_key/css/foreign_key.css | 12 + .../templates/foreign_key/js/foreign_key.js | 1072 +++++++ .../constraints/index_constraint/__init__.py | 878 ++++++ .../static/img/primary_key.png | Bin 0 -> 443 bytes .../static/img/unique_constraint.png | Bin 0 -> 422 bytes .../index_constraint/js/index_constraint.js | 536 ++++ .../static/img/coll-constraints.png | Bin 0 -> 314 bytes .../templates/constraints/js/constraints.js | 54 + .../schemas/tables/constraints/type.py | 42 + .../schemas/tables/indexes/__init__.py | 874 +++++ .../tables/indexes/static/img/coll-index.png | Bin 0 -> 468 bytes .../tables/indexes/static/img/index.png | Bin 0 -> 562 bytes .../indexes/templates/index/js/index.js | 409 +++ .../schemas/tables/rules/__init__.py | 497 +++ .../tables/rules/static/img/coll-rule.png | Bin 0 -> 357 bytes .../schemas/tables/rules/static/img/rule.png | Bin 0 -> 373 bytes .../tables/rules/templates/rules/css/rule.css | 16 + .../tables/rules/templates/rules/js/rules.js | 266 ++ .../schemas/tables/static/img/coll-table.png | Bin 0 -> 555 bytes .../tables/static/img/table-repl-sm.png | Bin 0 -> 675 bytes .../schemas/tables/static/img/table-repl.png | Bin 0 -> 839 bytes .../schemas/tables/static/img/table.png | Bin 0 -> 593 bytes .../check_constraint/sql/9.1_plus/alter.sql | 4 + .../check_constraint/sql/9.1_plus/create.sql | 4 + .../check_constraint/sql/9.1_plus/delete.sql | 3 + .../sql/9.1_plus/get_name.sql | 4 + .../check_constraint/sql/9.1_plus/get_oid.sql | 7 + .../sql/9.1_plus/get_oid_with_transaction.sql | 5 + .../sql/9.1_plus/get_parent.sql | 7 + .../check_constraint/sql/9.1_plus/nodes.sql | 6 + .../sql/9.1_plus/properties.sql | 13 + .../check_constraint/sql/9.1_plus/update.sql | 4 + .../check_constraint/sql/9.2_plus/alter.sql | 4 + .../check_constraint/sql/9.2_plus/create.sql | 6 + .../check_constraint/sql/9.2_plus/delete.sql | 3 + .../sql/9.2_plus/get_name.sql | 5 + .../check_constraint/sql/9.2_plus/get_oid.sql | 8 + .../sql/9.2_plus/get_oid_with_transaction.sql | 6 + .../sql/9.2_plus/get_parent.sql | 7 + .../check_constraint/sql/9.2_plus/nodes.sql | 7 + .../sql/9.2_plus/properties.sql | 14 + .../check_constraint/sql/9.2_plus/update.sql | 13 + .../sql/9.2_plus/validate.sql | 2 + .../templates/column/macros/privilege.macros | 13 + .../templates/column/macros/security.macros | 6 + .../templates/column/sql/9.1_plus/acl.sql | 34 + .../templates/column/sql/9.1_plus/create.sql | 38 + .../templates/column/sql/9.1_plus/delete.sql | 1 + .../templates/column/sql/9.1_plus/depend.sql | 9 + .../column/sql/9.1_plus/edit_mode_types.sql | 5 + .../column/sql/9.1_plus/get_collations.sql | 7 + .../sql/9.1_plus/get_inherited_tables.sql | 12 + .../column/sql/9.1_plus/get_parent.sql | 5 + .../column/sql/9.1_plus/get_position.sql | 4 + .../column/sql/9.1_plus/get_types.sql | 14 + .../column/sql/9.1_plus/is_referenced.sql | 5 + .../templates/column/sql/9.1_plus/nodes.sql | 18 + .../column/sql/9.1_plus/properties.sql | 45 + .../templates/column/sql/9.1_plus/update.sql | 107 + .../templates/column/sql/9.2_plus/acl.sql | 34 + .../templates/column/sql/9.2_plus/create.sql | 38 + .../templates/column/sql/9.2_plus/delete.sql | 1 + .../templates/column/sql/9.2_plus/depend.sql | 9 + .../column/sql/9.2_plus/edit_mode_types.sql | 5 + .../column/sql/9.2_plus/get_collations.sql | 7 + .../sql/9.2_plus/get_inherited_tables.sql | 12 + .../column/sql/9.2_plus/get_parent.sql | 5 + .../column/sql/9.2_plus/get_position.sql | 4 + .../column/sql/9.2_plus/get_types.sql | 14 + .../column/sql/9.2_plus/is_referenced.sql | 5 + .../templates/column/sql/9.2_plus/nodes.sql | 18 + .../column/sql/9.2_plus/properties.sql | 45 + .../templates/column/sql/9.2_plus/update.sql | 105 + .../sql/9.1_plus/alter.sql | 4 + .../sql/9.1_plus/begin.sql | 1 + .../sql/9.1_plus/create.sql | 12 + .../sql/9.1_plus/delete.sql | 3 + .../exclusion_constraint/sql/9.1_plus/end.sql | 1 + .../sql/9.1_plus/get_access_methods.sql | 6 + .../sql/9.1_plus/get_constraint_cols.sql | 22 + .../sql/9.1_plus/get_name.sql | 3 + .../sql/9.1_plus/get_oid.sql | 4 + .../sql/9.1_plus/get_oid_with_transaction.sql | 6 + .../sql/9.1_plus/get_oper_class.sql | 7 + .../sql/9.1_plus/get_operator.sql | 29 + .../sql/9.1_plus/get_parent.sql | 7 + .../sql/9.1_plus/nodes.sql | 7 + .../sql/9.1_plus/properties.sql | 30 + .../sql/9.1_plus/update.sql | 22 + .../sql/9.2_plus/alter.sql | 4 + .../sql/9.2_plus/begin.sql | 1 + .../sql/9.2_plus/create.sql | 12 + .../sql/9.2_plus/delete.sql | 3 + .../exclusion_constraint/sql/9.2_plus/end.sql | 1 + .../sql/9.2_plus/get_access_methods.sql | 6 + .../sql/9.2_plus/get_constraint_cols.sql | 22 + .../sql/9.2_plus/get_name.sql | 3 + .../sql/9.2_plus/get_oid.sql | 4 + .../sql/9.2_plus/get_oid_with_transaction.sql | 6 + .../sql/9.2_plus/get_oper_class.sql | 7 + .../sql/9.2_plus/get_operator.sql | 30 + .../sql/9.2_plus/get_parent.sql | 7 + .../sql/9.2_plus/nodes.sql | 7 + .../sql/9.2_plus/properties.sql | 30 + .../sql/9.2_plus/update.sql | 22 + .../templates/foreign_key/sql/alter.sql | 4 + .../templates/foreign_key/sql/begin.sql | 1 + .../templates/foreign_key/sql/create.sql | 27 + .../foreign_key/sql/create_index.sql | 5 + .../templates/foreign_key/sql/delete.sql | 3 + .../tables/templates/foreign_key/sql/end.sql | 1 + .../templates/foreign_key/sql/get_cols.sql | 7 + .../foreign_key/sql/get_constraint_cols.sql | 13 + .../foreign_key/sql/get_constraints.sql | 37 + .../templates/foreign_key/sql/get_name.sql | 3 + .../templates/foreign_key/sql/get_oid.sql | 5 + .../sql/get_oid_with_transaction.sql | 6 + .../templates/foreign_key/sql/get_parent.sql | 7 + .../templates/foreign_key/sql/nodes.sql | 7 + .../templates/foreign_key/sql/properties.sql | 31 + .../templates/foreign_key/sql/update.sql | 18 + .../templates/foreign_key/sql/validate.sql | 2 + .../templates/index/sql/9.1_plus/alter.sql | 11 + .../index/sql/9.1_plus/backend_support.sql | 9 + .../index/sql/9.1_plus/column_details.sql | 30 + .../templates/index/sql/9.1_plus/create.sql | 12 + .../templates/index/sql/9.1_plus/delete.sql | 1 + .../templates/index/sql/9.1_plus/get_am.sql | 3 + .../index/sql/9.1_plus/get_collations.sql | 7 + .../templates/index/sql/9.1_plus/get_oid.sql | 7 + .../index/sql/9.1_plus/get_op_class.sql | 5 + .../index/sql/9.1_plus/get_parent.sql | 5 + .../templates/index/sql/9.1_plus/nodes.sql | 12 + .../index/sql/9.1_plus/properties.sql | 22 + .../templates/index/sql/9.1_plus/update.sql | 24 + .../templates/index_constraint/sql/alter.sql | 4 + .../templates/index_constraint/sql/begin.sql | 1 + .../templates/index_constraint/sql/create.sql | 12 + .../templates/index_constraint/sql/delete.sql | 3 + .../templates/index_constraint/sql/end.sql | 1 + .../sql/get_constraint_cols.sql | 7 + .../index_constraint/sql/get_indices.sql | 3 + .../index_constraint/sql/get_name.sql | 15 + .../index_constraint/sql/get_oid.sql | 4 + .../sql/get_oid_with_transaction.sql | 5 + .../index_constraint/sql/get_parent.sql | 7 + .../templates/index_constraint/sql/nodes.sql | 14 + .../index_constraint/sql/properties.sql | 29 + .../templates/index_constraint/sql/update.sql | 22 + .../templates/rules/sql/backend_support.sql | 9 + .../tables/templates/rules/sql/create.sql | 27 + .../tables/templates/rules/sql/delete.sql | 16 + .../tables/templates/rules/sql/properties.sql | 27 + .../tables/templates/rules/sql/rule_id.sql | 9 + .../tables/templates/rules/sql/update.sql | 33 + .../tables/templates/table/js/table.js | 954 ++++++ .../templates/table/sql/9.1_plus/acl.sql | 46 + .../table/sql/9.1_plus/backend_support.sql | 18 + .../templates/table/sql/9.1_plus/create.sql | 157 + .../templates/table/sql/9.1_plus/delete.sql | 1 + .../templates/table/sql/9.1_plus/depend.sql | 9 + .../sql/9.1_plus/enable_disable_trigger.sql | 2 + .../sql/9.1_plus/get_columns_for_table.sql | 16 + .../table/sql/9.1_plus/get_inherits.sql | 16 + .../table/sql/9.1_plus/get_oftype.sql | 6 + .../templates/table/sql/9.1_plus/get_oid.sql | 5 + .../table/sql/9.1_plus/get_relations.sql | 6 + .../9.1_plus/get_tables_for_constraints.sql | 8 + .../9.1_plus/get_types_where_condition.sql | 10 + .../templates/table/sql/9.1_plus/nodes.sql | 6 + .../table/sql/9.1_plus/properties.sql | 65 + .../table/sql/9.1_plus/reset_stats.sql | 1 + .../templates/table/sql/9.1_plus/sql.sql | 1 + .../templates/table/sql/9.1_plus/truncate.sql | 1 + .../templates/table/sql/9.1_plus/update.sql | 201 ++ .../templates/table/sql/9.5_plus/acl.sql | 46 + .../table/sql/9.5_plus/backend_support.sql | 18 + .../templates/table/sql/9.5_plus/create.sql | 157 + .../templates/table/sql/9.5_plus/delete.sql | 1 + .../templates/table/sql/9.5_plus/depend.sql | 9 + .../sql/9.5_plus/enable_disable_trigger.sql | 2 + .../sql/9.5_plus/get_columns_for_table.sql | 16 + .../table/sql/9.5_plus/get_inherits.sql | 16 + .../table/sql/9.5_plus/get_oftype.sql | 6 + .../templates/table/sql/9.5_plus/get_oid.sql | 5 + .../table/sql/9.5_plus/get_relations.sql | 6 + .../9.5_plus/get_tables_for_constraints.sql | 8 + .../9.5_plus/get_types_where_condition.sql | 10 + .../templates/table/sql/9.5_plus/nodes.sql | 6 + .../table/sql/9.5_plus/properties.sql | 65 + .../table/sql/9.5_plus/reset_stats.sql | 1 + .../templates/table/sql/9.5_plus/sql.sql | 1 + .../templates/table/sql/9.5_plus/truncate.sql | 1 + .../templates/table/sql/9.5_plus/update.sql | 201 ++ .../table/sql/macros/constraints.macro | 103 + .../templates/trigger/sql/9.1_plus/alter.sql | 9 + .../trigger/sql/9.1_plus/backend_support.sql | 9 + .../templates/trigger/sql/9.1_plus/create.sql | 27 + .../templates/trigger/sql/9.1_plus/delete.sql | 1 + .../sql/9.1_plus/enable_disable_trigger.sql | 2 + .../trigger/sql/9.1_plus/get_columns.sql | 6 + .../trigger/sql/9.1_plus/get_oid.sql | 5 + .../trigger/sql/9.1_plus/get_parent.sql | 5 + .../sql/9.1_plus/get_triggerfunctions.sql | 11 + .../templates/trigger/sql/9.1_plus/nodes.sql | 5 + .../trigger/sql/9.1_plus/properties.sql | 23 + .../templates/trigger/sql/9.1_plus/update.sql | 8 + .../schemas/tables/triggers/__init__.py | 944 ++++++ .../triggers/static/img/coll-trigger.png | Bin 0 -> 350 bytes .../triggers/static/img/trigger-bad.png | Bin 0 -> 610 bytes .../tables/triggers/static/img/trigger.png | Bin 0 -> 324 bytes .../templates/trigger/css/trigger.css | 20 + .../triggers/templates/trigger/js/trigger.js | 547 ++++ .../datatype/sql/9.1_plus/get_types.sql | 3 +- .../templates/macros/schemas/privilege.macros | 4 + .../schemas/templates/schema/js/schema.js | 250 ++ .../vacuum_settings/sql/vacuum_defaults.sql | 2 + .../vacuum_settings/vacuum_fields.json | 39 + .../servers/databases/schemas/utils.py | 157 +- 238 files changed, 18920 insertions(+), 3 deletions(-) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/img/coll-column.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/img/column.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/js/column.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/macros/privilege.macros create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/macros/security.macros create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/img/check-constraints-bad.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/img/check-constraints.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/css/check_constraint.css create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/js/check_constraint.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/img/exclusion_constraint.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key_no_validate.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/css/foreign_key.css create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/js/foreign_key.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/primary_key.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/templates/index_constraint/js/index_constraint.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/static/img/coll-constraints.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/templates/constraints/js/constraints.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/type.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/img/coll-index.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/img/index.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/img/coll-rule.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/img/rule.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/css/rule.css create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/templates/rules/js/rules.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/coll-table.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table-repl-sm.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table-repl.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/alter.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_name.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_oid_with_transaction.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/alter.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_name.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_oid_with_transaction.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/validate.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/macros/privilege.macros create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/macros/security.macros create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/acl.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/depend.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/edit_mode_types.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_collations.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_inherited_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_position.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_types.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/is_referenced.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/acl.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/depend.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/edit_mode_types.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_collations.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_inherited_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_position.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_types.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/is_referenced.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/alter.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/begin.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/end.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_access_methods.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_constraint_cols.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_name.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oid_with_transaction.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oper_class.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_operator.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/alter.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/begin.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/end.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_access_methods.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_constraint_cols.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_name.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oid_with_transaction.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oper_class.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_operator.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/alter.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/begin.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/create_index.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/end.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_cols.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_constraint_cols.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_constraints.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_name.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_oid_with_transaction.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/validate.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/alter.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/backend_support.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/column_details.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_am.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_collations.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_op_class.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/alter.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/begin.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/end.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_constraint_cols.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_indices.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_name.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid_with_transaction.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/backend_support.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/rule_id.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/acl.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/backend_support.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/depend.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/enable_disable_trigger.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_columns_for_table.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_inherits.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_oftype.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_relations.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_tables_for_constraints.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_types_where_condition.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/reset_stats.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/sql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/truncate.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/acl.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/backend_support.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/depend.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/enable_disable_trigger.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_columns_for_table.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_inherits.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_oftype.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_relations.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_tables_for_constraints.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_types_where_condition.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/reset_stats.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/sql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/truncate.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/macros/constraints.macro create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/alter.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/backend_support.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/enable_disable_trigger.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_columns.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_oid.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_parent.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_triggerfunctions.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/nodes.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/properties.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/coll-trigger.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/trigger-bad.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/trigger.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/templates/trigger/css/trigger.css create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/templates/trigger/js/trigger.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/templates/vacuum_settings/sql/vacuum_defaults.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/templates/vacuum_settings/vacuum_fields.json diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py new file mode 100644 index 000000000..fb751cafe --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py @@ -0,0 +1,2809 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Table Node """ + +import json +import re +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.server_groups.servers.databases.schemas.utils \ + import SchemaChildModule, DataTypeReader, VacuumSettings, \ + trigger_definition, parse_rule_definition +import pgadmin.browser.server_groups.servers.databases as database +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \ + parse_priv_to_db +from functools import wraps + + +class TableModule(SchemaChildModule): + """ + class TableModule(SchemaChildModule) + + A module class for Table node derived from SchemaChildModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the Table and it's base module. + + * get_nodes(gid, sid, did, scid, tid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for schema, when any of the server node is + initialized. + """ + NODE_TYPE = 'table' + COLLECTION_LABEL = gettext("Tables") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the TableModule and it's base module. + + Args: + *args: + **kwargs: + """ + super(TableModule, self).__init__(*args, **kwargs) + self.max_ver = None + self.min_ver = None + + def get_nodes(self, gid, sid, did, scid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(scid) + + @property + def script_load(self): + """ + Load the module script for database, when any of the database node is + initialized. + """ + return database.DatabaseModule.NODE_TYPE + + +blueprint = TableModule(__name__) + + +class TableView(PGChildNodeView, DataTypeReader, VacuumSettings): + """ + This class is responsible for generating routes for Table node + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the TableView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * list() + - This function is used to list all the Table nodes within that + collection. + + * nodes() + - This function will used to create all the child node within that + collection, Here it will create all the Table node. + + * properties(gid, sid, did, scid, tid) + - This function will show the properties of the selected Table node + + * create(gid, sid, did, scid) + - This function will create the new Table object + + * update(gid, sid, did, scid, tid) + - This function will update the data for the selected Table node + + * delete(gid, sid, scid, tid): + - This function will drop the Table object + + * truncate(gid, sid, scid, tid): + - This function will truncate table object + + * set_trigger(gid, sid, scid, tid): + - This function will enable/disable trigger(s) on table object + + * reset(gid, sid, scid, tid): + - This function will reset table object statistics + + * msql(gid, sid, did, scid, tid) + - This function is used to return modified SQL for the selected + Table node + + * get_sql(data, scid, tid) + - This function will generate sql from model data + + * sql(gid, sid, did, scid, tid): + - This function will generate sql to show it in sql pane for the + selected Table node. + + * dependency(gid, sid, did, scid, tid): + - This function will generate dependency list show it in dependency + pane for the selected Table node. + + * dependent(gid, sid, did, scid, tid): + - This function will generate dependent list to show it in dependent + pane for the selected node. + + * _formatter(data, tid) + - It will return formatted output of query result + as per client model format + + * get_types(self, gid, sid, did, scid) + - This function will return list of types available for columns node + via AJAX response + + * get_oftype(self, gid, sid, did, scid, tid) + - This function will return list of types available for table node + via AJAX response + + * get_inherits(self, gid, sid, did, scid, tid) + - This function will return list of tables availablefor inheritance + via AJAX response + + * get_relations(self, gid, sid, did, scid, tid) + - This function will return list of tables available for like/relation + via AJAX response + + * get_columns(gid, sid, did, scid, foid=None): + - Returns the Table Columns. + + * get_table_vacuum(gid, sid, did, scid=None, tid=None): + - Fetch the default values for table auto-vacuum + + * get_toast_table_vacuum(gid, sid, did, scid=None, tid=None) + - Fetch the default values for toast table auto-vacuum + + * _columns_formatter(tid, data): + - It will return formatted output of query result + as per client model format for column node + + * _index_constraints_formatter(self, tid, data): + - It will return formatted output of query result + as per client model format for index constraint node + + * _cltype_formatter(self, type): + - We need to remove [] from type and append it + after length/precision so we will set flag for + sql template + + * _parse_format_columns(self, data, mode=None): + - This function will parse and return formatted list of columns + added by user + + * get_index_constraint_sql(self, tid, data): + - This function will generate modified sql for index constraints + (Primary Key & Unique) + + * select_sql(gid, sid, did, scid, foid): + - Returns sql for Script + + * insert_sql(gid, sid, did, scid, foid): + - Returns sql for Script + + * update_sql(gid, sid, did, scid, foid): + - Returns sql for Script + + * delete_sql(gid, sid, did, scid, foid): + - Returns sql for Script +""" + + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'} + ] + ids = [ + {'type': 'int', 'id': 'tid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'delete': [{'delete': 'delete'}], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'get_oftype': [{'get': 'get_oftype'}, {'get': 'get_oftype'}], + 'get_inherits': [{'get': 'get_inherits'}, {'get': 'get_inherits'}], + 'get_relations': [{'get': 'get_relations'}, {'get': 'get_relations'}], + 'truncate': [{'put': 'truncate'}], + 'reset': [{'delete': 'reset'}], + 'set_trigger': [{'put': 'enable_disable_triggers'}], + 'get_types': [{'get': 'types'}, {'get': 'types'}], + 'get_columns': [{'get': 'get_columns'}, {'get': 'get_columns'}], + 'get_table_vacuum': [{}, {'get': 'get_table_vacuum'}], + 'get_toast_table_vacuum': [{}, {'get': 'get_toast_table_vacuum'}], + 'all_tables': [{}, {'get': 'get_all_tables'}], + 'get_access_methods': [{}, {'get': 'get_access_methods'}], + 'get_oper_class': [{}, {'get': 'get_oper_class'}], + 'get_operator': [{}, {'get': 'get_operator'}], + 'select_sql': [{'get': 'select_sql'}], + 'insert_sql': [{'get': 'insert_sql'}], + 'update_sql': [{'get': 'update_sql'}], + 'delete_sql': [{'get': 'delete_sql'}] + + }) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + driver = get_driver(PG_DEFAULT_DRIVER) + self.manager = driver.connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + self.qtIdent = driver.qtIdent + # We need datlastsysoid to check if current table is system table + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + # We need datlastsysoid to check if current index is system index + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + + # we will set template path for sql scripts + ver = self.manager.version + # Template for Column node + if ver >= 90500: + self.template_path = 'table/sql/9.5_plus' + else: + self.template_path = 'table/sql/9.1_plus' + + # Template for Column ,check constraint and exclusion constraint node + if ver >= 90200: + self.column_template_path = 'column/sql/9.2_plus' + self.check_constraint_template_path = 'check_constraint/sql/9.2_plus' + self.exclusion_constraint_template_path = 'exclusion_constraint/sql/9.2_plus' + else: + self.column_template_path = 'column/sql/9.1_plus' + self.check_constraint_template_path = 'check_constraint/sql/9.1_plus' + self.exclusion_constraint_template_path = 'exclusion_constraint/sql/9.1_plus' + + # Template for PK & Unique constraint node + self.index_constraint_template_path = 'index_constraint/sql' + + # Template for foreign key constraint node + self.foreign_key_template_path = 'foreign_key/sql' + + # Template for index node + self.index_template_path = 'index/sql/9.1_plus' + + # Template for trigger node + self.trigger_template_path = 'trigger/sql/9.1_plus' + + # Template for rules node + self.rules_template_path = 'rules/sql' + + # Supported ACL for table + self.acl = ['a', 'r', 'w', 'd', 'D', 'x', 't'] + + # Supported ACL for columns + self.column_acl = ['a', 'r', 'w', 'x'] + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid): + """ + This function is used to list all the table nodes within that collection. + + Args: + gid: Server group ID + sid: Server ID + did: Database ID + scid: Schema ID + + Returns: + JSON of available table nodes + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid): + """ + This function is used to list all the table nodes within that collection. + + Args: + gid: Server group ID + sid: Server ID + did: Database ID + scid: Schema ID + + Returns: + JSON of available table nodes + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), + scid=scid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + scid, + row['name'], + icon="icon-table", + tigger_count=row['triggercount'], + has_enable_triggers=row['has_enable_triggers'] + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def get_all_tables(self, gid, sid, did, scid, tid=None): + """ + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + + Returns: + Returns the lits of tables required for constraints. + """ + try: + SQL = render_template("/".join([self.template_path, + 'get_tables_for_constraints.sql']), + show_sysobj=self.blueprint.show_system_objects) + + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + data=res['rows'], + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_table_vacuum(self, gid, sid, did, scid=None, tid=None): + """ + Fetch the default values for table auto-vacuum + fields, return an array of + - label + - name + - setting + values + """ + res = self.get_vacuum_table_settings(self.conn) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def get_toast_table_vacuum(self, gid, sid, did, scid=None, tid=None): + """ + Fetch the default values for toast table auto-vacuum + fields, return an array of + - label + - name + - setting + values + """ + res = self.get_vacuum_toast_settings(self.conn) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def get_access_methods(self, gid, sid, did, scid, tid=None): + """ + This function returns access methods. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + res = [{'label': '', 'value': ''}] + sql = render_template( + "/".join([self.exclusion_constraint_template_path, + 'get_access_methods.sql'])) + status, rest = self.conn.execute_2darray(sql) + + if not status: + return internal_server_error(errormsg=rest) + + for row in rest['rows']: + res.append( + {'label': row['amname'], 'value': row['amname']} + ) + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def get_oper_class(self, gid, sid, did, scid, tid=None): + """ + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + data = request.args if request.args else None + try: + if data and 'indextype' in data: + SQL = render_template( + "/".join([self.exclusion_constraint_template_path, + 'get_oper_class.sql']), + indextype=data['indextype']) + + status, res = self.conn.execute_2darray(SQL) + + if not status: + return internal_server_error(errormsg=res) + result = [] + for row in res['rows']: + result.append([row['opcname'], row['opcname']]) + return make_json_response( + data=result, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_operator(self, gid, sid, did, scid, tid=None): + """ + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + data = request.args if request.args else None + try: + if data and 'col_type' in data: + SQL = render_template( + "/".join([self.exclusion_constraint_template_path, + 'get_operator.sql']), + type=data['col_type'], + show_sysobj=self.blueprint.show_system_objects) + + status, res = self.conn.execute_2darray(SQL) + + if not status: + return internal_server_error(errormsg=res) + result = [] + for row in res['rows']: + result.append([row['oprname'], row['oprname']]) + return make_json_response( + data=result, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def _columns_formatter(self, tid, data): + """ + Args: + tid: Table OID + data: dict of query result + + Returns: + It will return formatted output of query result + as per client model format for column node + """ + for column in data['columns']: + + # We need to format variables according to client js collection + if 'attoptions' in column and column['attoptions'] is not None: + spcoptions = [] + for spcoption in column['attoptions']: + k, v = spcoption.split('=') + spcoptions.append({'name': k, 'value': v}) + + column['attoptions'] = spcoptions + + # Need to format security labels according to client js collection + if 'seclabels' in column and column['seclabels'] is not None: + seclabels = [] + for seclbls in column['seclabels']: + k, v = seclbls.split('=') + seclabels.append({'provider': k, 'label': v}) + + column['seclabels'] = seclabels + + if 'attnum' in column and column['attnum'] is not None and \ + column['attnum'] > 0: + # We need to parse & convert ACL coming from database to json format + SQL = render_template("/".join([self.column_template_path, 'acl.sql']), + tid=tid, clid=column['attnum']) + status, acl = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=acl) + + # We will set get privileges from acl sql so we don't need + # it from properties sql + column['attacl'] = [] + + for row in acl['rows']: + priv = parse_priv_from_db(row) + column.setdefault(row['deftype'], []).append(priv) + + # we are receiving request when in edit mode + # we will send filtered types related to current type + present_type = column['cltype'] + + type_id = column['atttypid'] + + fulltype = self.get_full_type( + column['typnspname'], column['typname'], + column['isdup'], column['attndims'], column['atttypmod'] + ) + + import re + # If we have length & precision both + matchObj = re.search(r'(\d+),(\d+)', fulltype) + if matchObj: + column['attlen'] = matchObj.group(1) + column['attprecision'] = matchObj.group(2) + else: + # If we have length only + matchObj = re.search(r'(\d+)', fulltype) + if matchObj: + column['attlen'] = matchObj.group(1) + column['attprecision'] = None + else: + column['attlen'] = None + column['attprecision'] = None + + SQL = render_template("/".join([self.column_template_path, + 'is_referenced.sql']), + tid=tid, clid=column['attnum']) + + status, is_reference = self.conn.execute_scalar(SQL) + + edit_types_list = list() + # We will need present type in edit mode + edit_types_list.append(present_type) + + if int(is_reference) == 0: + SQL = render_template("/".join([self.column_template_path, + 'edit_mode_types.sql']), + type_id=type_id) + status, rset = self.conn.execute_2darray(SQL) + + for row in rset['rows']: + edit_types_list.append(row['typname']) + else: + edit_types_list.append(present_type) + + column['edit_types'] = edit_types_list + + # Manual Data type formatting + # If data type has () with them then we need to remove them + # eg bit(1) because we need to match the name with combobox + isArray = False + if column['cltype'].endswith('[]'): + isArray = True + column['cltype'] = column['cltype'].rstrip('[]') + + idx = column['cltype'].find('(') + if idx and column['cltype'].endswith(')'): + column['cltype'] = column['cltype'][:idx] + + if isArray: + column['cltype'] += "[]" + + if 'indkey' in column: + # Current column + attnum = str(column['attnum']) + + # Single/List of primary key column(s) + indkey = str(column['indkey']) + + # We will check if column is in primary column(s) + if attnum in indkey.split(" "): + column['is_primary_key'] = True + else: + column['is_primary_key'] = False + + return data + + def _index_constraints_formatter(self, tid, data): + """ + Args: + tid: Table OID + data: dict of query result + + Returns: + It will return formatted output of query result + as per client model format for index constraint node + """ + + # We will fetch all the index constraints for the table + index_constraints = { + 'p': 'primary_key', 'u': 'unique_constraint' + } + + for ctype in index_constraints.keys(): + data[index_constraints[ctype]] = [] + + sql = render_template("/".join([self.index_constraint_template_path, + 'properties.sql']), + tid=tid, + constraint_type=ctype) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + result = row + sql = render_template( + "/".join([self.index_constraint_template_path, + 'get_constraint_cols.sql']), + cid=row['oid'], + colcnt=row['indnatts']) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + for r in res['rows']: + columns.append({"column": r['column'].strip('"')}) + + result['columns'] = columns + + # If not exists then create list and/or append into + # existing list [ Adding into main data dict] + data.setdefault(index_constraints[ctype], []).append(result) + + return data + + def _foreign_key_formatter(self, tid, data): + """ + Args: + tid: Table OID + data: dict of query result + + Returns: + It will return formatted output of query result + as per client model format for foreign key constraint node + """ + + # We will fetch all the index constraints for the table + sql = render_template("/".join([self.foreign_key_template_path, + 'properties.sql']), + tid=tid) + + status, result = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=result) + + for fk in result['rows']: + + sql = render_template("/".join([self.foreign_key_template_path, + 'get_constraint_cols.sql']), + tid=tid, + keys=zip(fk['confkey'], fk['conkey']), + confrelid=fk['confrelid']) + + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + cols = [] + for row in res['rows']: + columns.append({"local_column": row['conattname'], + "references": fk['confrelid'], + "referenced": row['confattname']}) + cols.append(row['conattname']) + + fk['columns'] = columns + + SQL = render_template("/".join([self.foreign_key_template_path, + 'get_parent.sql']), + tid=fk['columns'][0]['references']) + + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + fk['remote_schema'] = rset['rows'][0]['schema'] + fk['remote_table'] = rset['rows'][0]['table'] + + coveringindex = self.search_coveringindex(tid, cols) + + fk['coveringindex'] = coveringindex + if coveringindex: + fk['autoindex'] = True + fk['hasindex'] = True + else: + fk['autoindex'] = False + fk['hasindex'] = False + # If not exists then create list and/or append into + # existing list [ Adding into main data dict] + data.setdefault('foreign_key', []).append(fk) + + return data + + def _check_constraint_formatter(self, tid, data): + """ + Args: + tid: Table OID + data: dict of query result + + Returns: + It will return formatted output of query result + as per client model format for check constraint node + """ + + # We will fetch all the index constraints for the table + SQL = render_template("/".join([self.check_constraint_template_path, + 'properties.sql']), + tid=tid) + + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + # If not exists then create list and/or append into + # existing list [ Adding into main data dict] + + data['check_constraint'] = res['rows'] + + return data + + def _exclusion_constraint_formatter(self, tid, data): + """ + Args: + tid: Table OID + data: dict of query result + + Returns: + It will return formatted output of query result + as per client model format for exclusion constraint node + """ + + # We will fetch all the index constraints for the table + sql = render_template("/".join([self.exclusion_constraint_template_path, + 'properties.sql']), + tid=tid) + + status, result = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=result) + + for ex in result['rows']: + + sql = render_template("/".join([self.exclusion_constraint_template_path, + 'get_constraint_cols.sql']), + cid=ex['oid'], + colcnt=ex['indnatts']) + + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + for row in res['rows']: + if row['options'] & 1: + order = False + nulls_order = True if (row['options'] & 2) else False + else: + order = True + nulls_order = True if (row['options'] & 2) else False + + columns.append({"column": row['coldef'].strip('"'), + "oper_class": row['opcname'], + "order": order, + "nulls_order": nulls_order, + "operator": row['oprname'], + "col_type": row['datatype'] + }) + + ex['columns'] = columns + # If not exists then create list and/or append into + # existing list [ Adding into main data dict] + data.setdefault('exclude_constraint', []).append(ex) + + return data + + def search_coveringindex(self, tid, cols): + """ + + Args: + tid: Table id + cols: column list + + Returns: + + """ + + cols = set(cols) + SQL = render_template("/".join([self.foreign_key_template_path, + 'get_constraints.sql']), + tid=tid) + status, constraints = self.conn.execute_dict(SQL) + + if not status: + raise Exception(constraints) + + for costrnt in constraints['rows']: + + sql = render_template( + "/".join([self.foreign_key_template_path, 'get_cols.sql']), + cid=costrnt['oid'], + colcnt=costrnt['indnatts']) + status, rest = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=rest) + + indexcols = set() + for r in rest['rows']: + indexcols.add(r['column'].strip('"')) + + if len(cols - indexcols) == len(indexcols - cols) == 0: + return costrnt["idxname"] + + return None + + def _formatter(self, scid, tid, data): + """ + Args: + data: dict of query result + scid: schema oid + tid: table oid + + Returns: + It will return formatted output of query result + as per client model format + """ + # Need to format security labels according to client js collection + if 'seclabels' in data and data['seclabels'] is not None: + seclabels = [] + for seclbls in data['seclabels']: + k, v = seclbls.split('=') + seclabels.append({'provider': k, 'label': v}) + + data['seclabels'] = seclabels + + # We need to parse & convert ACL coming from database to json format + SQL = render_template("/".join([self.template_path, 'acl.sql']), + tid=tid, scid=scid) + status, acl = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=acl) + + # We will set get privileges from acl sql so we don't need + # it from properties sql + for row in acl['rows']: + priv = parse_priv_from_db(row) + if row['deftype'] in data: + data[row['deftype']].append(priv) + else: + data[row['deftype']] = [priv] + + # We will add Auto vacuum defaults with out result for grid + data['vacuum_table'] = self.parse_vacuum_data(self.conn, data, 'table') + data['vacuum_toast'] = self.parse_vacuum_data(self.conn, data, 'toast') + + # Fetch columns for the table logic + # + # 1) Check if of_type and inherited tables are present? + # 2) If yes then Fetch all the columns for of_type and inherited tables + # 3) Add columns in columns collection + # 4) Find all the columns for tables and filter out columns which are + # not inherited from any table & format them one by one + + # Get of_type table columns and add it into columns dict + if data['typname']: + SQL = render_template("/".join([self.template_path, + 'get_columns_for_table.sql']), + tname=data['typname']) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + data['columns'] = res['rows'] + + # Get inherited table(s) columns and add it into columns dict + elif data['coll_inherits'] and len(data['coll_inherits']) > 0: + columns = [] + # Return all tables which can be inherited & do not show + # system columns + SQL = render_template("/".join([self.template_path, 'get_inherits.sql']), + show_system_objects=False + ) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + if row['inherits'] in data['coll_inherits']: + # Fetch columns using inherited table OID + SQL = render_template("/".join([self.template_path, + 'get_columns_for_table.sql']), + tid=row['oid']) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + columns.extend(res['rows'][:]) + data['columns'] = columns + + # We will fetch all the columns for the table using + # columns properties.sql, so we need to set template path + SQL = render_template("/".join([self.column_template_path, + 'properties.sql']), + tid=tid, + show_sys_objects=False + ) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + all_columns = res['rows'] + + # Filter inherited columns from all columns + if 'columns' in data and len(data['columns']) > 0 \ + and len(all_columns) > 0: + columns = [] + for row in data['columns']: + for i, col in enumerate(all_columns): + # If both name are same then remove it + # as it is inherited from other table + if col['name'] == row['name']: + # Remove same column from all_columns as + # already have it columns collection + del all_columns[i] + + # If any column is added then update columns collection + if len(all_columns) > 0: + data['columns'] += all_columns + # If no inherited columns found then add all columns + elif len(all_columns) > 0: + data['columns'] = all_columns + + if 'columns' in data and len(data['columns']) > 0: + data = self._columns_formatter(tid, data) + + # Here we will add constraint in our output + data = self._index_constraints_formatter(tid, data) + data = self._foreign_key_formatter(tid, data) + data = self._check_constraint_formatter(tid, data) + data = self._exclusion_constraint_formatter(tid, data) + + return data + + @check_precondition + def properties(self, gid, sid, did, scid, tid): + """ + This function will show the properties of the selected table node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of selected table node + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + data = res['rows'][0] + + data['vacuum_settings_str'] = "" + + if data['table_vacuum_settings_str'] is not None: + data['vacuum_settings_str'] += data[ + 'table_vacuum_settings_str'].replace(',', '\n') + + if data['toast_table_vacuum_settings_str'] is not None: + data['vacuum_settings_str'] += '\n' + '\n'.join( + ['toast_' + setting for setting in data[ + 'toast_table_vacuum_settings_str' + ].split(',')] + ) + data['vacuum_settings_str'] = data[ + 'vacuum_settings_str' + ].replace("=", " = ") + + data = self._formatter(scid, tid, data) + + return ajax_response( + response=data, + status=200 + ) + + @check_precondition + def types(self, gid, sid, did, scid, tid=None, clid=None): + """ + Returns: + This function will return list of types available for column node + for node-ajax-control + """ + condition = render_template("/".join([self.template_path, + 'get_types_where_condition.sql']), + show_system_objects=self.blueprint.show_system_objects) + + status, types = self.get_types(self.conn, condition) + + if not status: + return internal_server_error(errormsg=types) + + return make_json_response( + data=types, + status=200 + ) + + @check_precondition + def get_columns(self, gid, sid, did, scid, tid=None): + """ + Returns the Table Columns. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + + Returns: + JSON Array with below parameters. + name: Column Name + ctype: Column Data Type + inherited_from: Parent Table from which the related column + is inheritted. + """ + res = [] + data = request.args if request.args else None + try: + if data and 'tid' in data: + SQL = render_template("/".join([self.template_path, + 'get_columns_for_table.sql']), + tid=data['tid']) + elif data and 'tname' in data: + SQL = render_template("/".join([self.template_path, + 'get_columns_for_table.sql']), + tname=data['tname']) + + if SQL: + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + res = res['rows'] + + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_oftype(self, gid, sid, did, scid, tid=None): + """ + Returns: + This function will return list of types available for table node + for node-ajax-control + """ + res = [{'label': '', 'value': ''}] + try: + SQL = render_template("/".join([self.template_path, + 'get_oftype.sql']), scid=scid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + for row in rset['rows']: + res.append( + {'label': row['typname'], 'value': row['typname'], + 'tid': row['oid'] + } + ) + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_inherits(self, gid, sid, did, scid, tid=None): + """ + Returns: + This function will return list of tables available for inheritance + while creating new table + """ + try: + res = [] + SQL = render_template("/".join([self.template_path, 'get_inherits.sql']), + show_system_objects=self.blueprint.show_system_objects, + tid=tid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + for row in rset['rows']: + res.append( + {'label': row['inherits'], 'value': row['inherits'], + 'tid': row['oid'] + } + ) + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_relations(self, gid, sid, did, scid, tid=None): + """ + Returns: + This function will return list of tables available for like/relation + combobox while creating new table + """ + res = [{'label': '', 'value': ''}] + try: + SQL = render_template("/".join([self.template_path, 'get_relations.sql'])) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + for row in rset['rows']: + res.append( + {'label': row['like_relation'], 'value': row['like_relation']} + ) + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def _cltype_formatter(self, type): + """ + + Args: + data: Type string + + Returns: + We need to remove [] from type and append it + after length/precision so we will set flag for + sql template + """ + if '[]' in type: + type = type.replace('[]', '') + self.hasSqrBracket = True + else: + self.hasSqrBracket = False + + return type + + def _parse_format_columns(self, data, mode=None): + """ + data: + Data coming from client side + + Returns: + This function will parse and return formatted list of columns + added by user + """ + columns = data['columns'] + # 'EDIT' mode + if mode is not None: + for action in ['added', 'changed']: + if action in columns: + final_columns = [] + for c in columns[action]: + if 'inheritedfrom' not in c: + final_columns.append(c) + + for c in final_columns: + if 'attacl' in c: + c['attacl'] = parse_priv_to_db(c['attacl'], self.column_acl) + + # check type for '[]' in it + c['cltype'] = self._cltype_formatter(c['cltype']) + c['hasSqrBracket'] = self.hasSqrBracket + + data['columns'][action] = final_columns + else: + # We need to exclude all the columns which are inherited from other tables + # 'CREATE' mode + final_columns = [] + + for c in columns: + if 'inheritedfrom' not in c: + final_columns.append(c) + + # Now we have all lis of columns which we need + # to include in our create definition, Let's format them + for c in final_columns: + if 'attacl' in c: + c['attacl'] = parse_priv_to_db(c['attacl'], self.column_acl) + + # check type for '[]' in it + c['cltype'] = self._cltype_formatter(c['cltype']) + c['hasSqrBracket'] = self.hasSqrBracket + + data['columns'] = final_columns + + return data + + @check_precondition + def create(self, gid, sid, did, scid): + """ + This function will creates new the table object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + """ + data = request.form if request.form else json.loads(request.data.decode()) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + required_args = [ + 'name' + ] + + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + + # Parse privilege data coming from client according to database format + if 'relacl' in data: + data['relacl'] = parse_priv_to_db(data['relacl'], self.acl) + + # Parse & format columns + data = self._parse_format_columns(data) + + # 'coll_inherits' is Array but it comes as string from browser + # We will convert it again to list + if 'coll_inherits' in data and \ + isinstance(data['coll_inherits'], str): + data['coll_inherits'] = json.loads(data['coll_inherits']) + + if 'foreign_key' in data: + for c in data['foreign_key']: + SQL = render_template("/".join([self.foreign_key_template_path, + 'get_parent.sql']), + tid=c['columns'][0]['references']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + c['remote_schema'] = rset['rows'][0]['schema'] + c['remote_table'] = rset['rows'][0]['table'] + + try: + SQL = render_template("/".join([self.template_path, + 'create.sql']), + data=data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # we need oid to to add object in tree at browser + SQL = render_template("/".join([self.template_path, + 'get_oid.sql']), scid=scid, data=data) + status, tid = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=tid) + + return jsonify( + node=self.blueprint.generate_browser_node( + tid, + scid, + data['name'], + icon="icon-table" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, tid): + """ + This function will update an existing table object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + data = request.form if request.form else json.loads( + request.data.decode() + ) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + try: + SQL = self.get_sql(scid, tid, data) + + if SQL and SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Table updated", + data={ + 'id': tid, + 'scid': scid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': tid, + 'scid': scid, + 'did': did + } + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid): + """ + This function will deletes the table object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + # Below will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + + try: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + data = res['rows'][0] + + SQL = render_template("/".join([self.template_path, + 'delete.sql']), + data=data, cascade=cascade, + conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Table dropped"), + data={ + 'id': tid, + 'scid': scid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def truncate(self, gid, sid, did, scid, tid): + """ + This function will truncate the table object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + # Below will decide if it's simple drop or drop with cascade call + data = request.form if request.form else json.loads(request.data.decode()) + # Convert str 'true' to boolean type + is_cascade = json.loads(data['cascade']) + + try: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + data = res['rows'][0] + + SQL = render_template("/".join([self.template_path, + 'truncate.sql']), + data=data, cascade=is_cascade) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Table truncated"), + data={ + 'id': tid, + 'scid': scid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def enable_disable_triggers(self, gid, sid, did, scid, tid): + """ + This function will enable/disable trigger(s) on the table object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + # Below will decide if it's simple drop or drop with cascade call + data = request.form if request.form else json.loads(request.data.decode()) + # Convert str 'true' to boolean type + is_enable = json.loads(data['enable']) + + try: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + data = res['rows'][0] + # TODO:// + # Find SQL which can enable all or disable all triggers + SQL = render_template("/".join([self.template_path, + 'enable_disable_trigger.sql']), + data=data, is_enable_trigger=is_enable) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Trigger(s) has been enabled") if is_enable + else gettext("Trigger(s) has been disabled"), + data={ + 'id': tid, + 'scid': scid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def reset(self, gid, sid, did, scid, tid): + """ + This function will reset statistics of table + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + try: + SQL = render_template("/".join([self.template_path, + 'reset_stats.sql']), + tid=tid) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Table statistics has been reset"), + data={ + 'id': tid, + 'scid': scid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, tid=None): + """ + This function will create modified sql for table object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + data = dict() + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + try: + SQL = self.get_sql(scid, tid, data) + SQL = re.sub('\n{2,}', '\n', SQL) + SQL = SQL.strip('\n') + return make_json_response( + data=SQL, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_index_constraint_sql(self, tid, data): + """ + Args: + tid: Table ID + data: data dict coming from the client + + Returns: + This function will generate modified sql for index constraints + (Primary Key & Unique) + """ + sql = [] + # We will fetch all the index constraints for the table + index_constraints = { + 'p': 'primary_key', 'u': 'unique_constraint' + } + + for ctype in index_constraints.keys(): + # Check if constraint is in data + # If yes then we need to check for add/change/delete + if index_constraints[ctype] in data: + constraint = data[index_constraints[ctype]] + # If constraint(s) is/are deleted + if 'deleted' in constraint: + for c in constraint['deleted']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + # Sql for drop + sql.append( + render_template("/".join( + [self.index_constraint_template_path, + 'delete.sql']), + data=c, conn=self.conn).strip('\n') + ) + + if 'changed' in constraint: + for c in constraint['changed']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + properties_sql = render_template("/".join( + [self.index_constraint_template_path, 'properties.sql']), + tid=tid, cid=c['oid'], constraint_type=ctype) + status, res = self.conn.execute_dict(properties_sql) + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + # Sql to update object + sql.append( + render_template("/".join([ + self.index_constraint_template_path, + 'update.sql']), data=c, o_data=old_data, + conn=self.conn).strip('\n') + ) + + if 'added' in constraint: + for c in constraint['added']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + # Sql to add object + if self.validate_constrains(index_constraints[ctype], c): + sql.append( + render_template( + "/".join([self.index_constraint_template_path, + 'create.sql']), + data=c, conn=self.conn, + constraint_name='PRIMARY KEY' + if ctype == 'p' else 'UNIQUE' + ).strip('\n') + ) + # sql to update comments + sql.append( + render_template( + "/".join([self.index_constraint_template_path, + 'alter.sql']), + data=c, conn=self.conn + ).strip('\n') + ) + else: + sql.append( + gettext( + '-- incomplete definition for {0} constraint'.format(index_constraints[ctype]) + ) + ) + if len(sql) > 0: + # Join all the sql(s) as single string + return '\n\n'.join(sql) + else: + return None + + def get_foreign_key_sql(self, tid, data): + """ + Args: + tid: Table ID + data: data dict coming from the client + + Returns: + This function will generate modified sql for foreign key + """ + sql = [] + # Check if constraint is in data + # If yes then we need to check for add/change/delete + if 'foreign_key' in data: + constraint = data['foreign_key'] + # If constraint(s) is/are deleted + if 'deleted' in constraint: + for c in constraint['deleted']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + # Sql for drop + sql.append( + render_template("/".join( + [self.foreign_key_template_path, + 'delete.sql']), + data=c, conn=self.conn).strip('\n') + ) + + if 'changed' in constraint: + for c in constraint['changed']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + properties_sql = render_template("/".join( + [self.foreign_key_template_path, 'properties.sql']), + tid=tid, cid=c['oid']) + status, res = self.conn.execute_dict(properties_sql) + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + # Sql to update object + sql.append( + render_template("/".join([ + self.foreign_key_template_path, + 'update.sql']), data=c, o_data=old_data, + conn=self.conn).strip('\n') + ) + + if not self.validate_constrains('foreign_key', c): + sql.append( + gettext( + '-- incomplete definition for foreign_key constraint' + ) + ) + return '\n\n'.join(sql) + + cols = [] + for col in c['columns']: + cols.append(col['local_column']) + + coveringindex = self.search_coveringindex(tid, cols) + + if coveringindex is None and 'autoindex' in c and c['autoindex'] and\ + ('coveringindex' in c and + c['coveringindex'] != ''): + sql.append(render_template( + "/".join([self.foreign_key_template_path, 'create_index.sql']), + data=c, conn=self.conn).strip('\n') + ) + + if 'added' in constraint: + for c in constraint['added']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + # Sql to add object + # Columns + + if not self.validate_constrains('foreign_key', c): + sql.append( + gettext( + '-- incomplete definition for foreign_key constraint' + ) + ) + return '\n\n'.join(sql) + + SQL = render_template("/".join([self.foreign_key_template_path, + 'get_parent.sql']), + tid=c['columns'][0]['references']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + c['remote_schema'] = rset['rows'][0]['schema'] + c['remote_table'] = rset['rows'][0]['table'] + + sql.append( + render_template( + "/".join([self.foreign_key_template_path, + 'create.sql']), + data=c, conn=self.conn + ).strip('\n') + ) + # sql to update comments + sql.append( + render_template( + "/".join([self.foreign_key_template_path, + 'alter.sql']), + data=c, conn=self.conn + ).strip('\n') + ) + + if c['autoindex']: + sql.append( + render_template( + "/".join([self.foreign_key_template_path, + 'create_index.sql']), + data=c, conn=self.conn).strip('\n') + ) + + if len(sql) > 0: + # Join all the sql(s) as single string + return '\n\n'.join(sql) + else: + return None + + def get_check_constraint_sql(self, tid, data): + """ + Args: + tid: Table ID + data: data dict coming from the client + + Returns: + This function will generate modified sql for check constraint + """ + sql = [] + # Check if constraint is in data + # If yes then we need to check for add/change/delete + if 'check_constraint' in data: + constraint = data['check_constraint'] + # If constraint(s) is/are deleted + if 'deleted' in constraint: + for c in constraint['deleted']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + # Sql for drop + sql.append( + render_template("/".join( + [self.check_constraint_template_path, + 'delete.sql']), + data=c, conn=self.conn).strip('\n') + ) + + if 'changed' in constraint: + for c in constraint['changed']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + properties_sql = render_template("/".join( + [self.check_constraint_template_path, 'properties.sql']), + tid=tid, cid=c['oid']) + status, res = self.conn.execute_dict(properties_sql) + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + # Sql to update object + sql.append( + render_template("/".join([ + self.check_constraint_template_path, + 'update.sql']), data=c, o_data=old_data, + conn=self.conn).strip('\n') + ) + + if 'added' in constraint: + for c in constraint['added']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + if not self.validate_constrains('check_constraint', c): + sql.append( + gettext( + '-- incomplete definition for check_constraint' + ) + ) + return '\n\n'.join(sql) + + sql.append( + render_template( + "/".join([self.check_constraint_template_path, + 'create.sql']), + data=c, conn=self.conn + ).strip('\n') + ) + # sql to update comments + sql.append( + render_template( + "/".join([self.check_constraint_template_path, + 'alter.sql']), + data=c, conn=self.conn + ).strip('\n') + ) + + if len(sql) > 0: + # Join all the sql(s) as single string + return '\n\n'.join(sql) + else: + return None + + def get_exclusion_constraint_sql(self, tid, data): + """ + Args: + tid: Table ID + data: data dict coming from the client + + Returns: + This function will generate modified sql for exclusion constraint + """ + sql = [] + # Check if constraint is in data + # If yes then we need to check for add/change/delete + if 'exclude_constraint' in data: + constraint = data['exclude_constraint'] + # If constraint(s) is/are deleted + if 'deleted' in constraint: + for c in constraint['deleted']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + # Sql for drop + sql.append( + render_template("/".join( + [self.exclusion_constraint_template_path, + 'delete.sql']), + data=c, conn=self.conn).strip('\n') + ) + + if 'changed' in constraint: + for c in constraint['changed']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + properties_sql = render_template("/".join( + [self.exclusion_constraint_template_path, 'properties.sql']), + tid=tid, cid=c['oid']) + status, res = self.conn.execute_dict(properties_sql) + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + # Sql to update object + sql.append( + render_template("/".join([ + self.exclusion_constraint_template_path, + 'update.sql']), data=c, o_data=old_data, + conn=self.conn).strip('\n') + ) + + if 'added' in constraint: + for c in constraint['added']: + c['schema'] = data['schema'] + c['table'] = data['name'] + + if not self.validate_constrains('exclude_constraint', c): + sql.append( + gettext( + '-- incomplete definition for exclusion_constraint' + ) + ) + return '\n\n'.join(sql) + + sql.append( + render_template( + "/".join([self.exclusion_constraint_template_path, + 'create.sql']), + data=c, conn=self.conn + ).strip('\n') + ) + # sql to update comments + sql.append( + render_template( + "/".join([self.exclusion_constraint_template_path, + 'alter.sql']), + data=c, conn=self.conn + ).strip('\n') + ) + + if len(sql) > 0: + # Join all the sql(s) as single string + return '\n\n'.join(sql) + else: + return None + + def get_sql(self, scid, tid, data): + """ + This function will generate create/update sql from model data + coming from client + """ + if tid is not None: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + old_data = self._formatter(scid, tid, old_data) + + # We will convert privileges coming from client required + if 'relacl' in data: + for mode in ['added', 'changed', 'deleted']: + if mode in data['relacl']: + data['relacl'][mode] = parse_priv_to_db( + data['relacl'][mode], self.acl + ) + + # If name if not present + if 'name' not in data: + data['name'] = old_data['name'] + # If name if not present + if 'schema' not in data: + data['schema'] = old_data['schema'] + + # Filter out new tables from list, we will send complete list + # and not newly added tables in the list from client + # so we will filter new tables here + if 'coll_inherits' in data: + p_len = len(old_data['coll_inherits']) + c_len = len(data['coll_inherits']) + # If table(s) added + if c_len > p_len: + data['coll_inherits_added'] = list( + set(data['coll_inherits']) - set(old_data['coll_inherits']) + ) + # If table(s)removed + elif c_len < p_len: + data['coll_inherits_removed'] = list( + set(old_data['coll_inherits']) - set(data['coll_inherits']) + ) + # Safe side verification,In case it happens.. + # If user removes and adds same number of table + # eg removed one table and added one new table + elif c_len == p_len: + data['coll_inherits_added'] = list( + set(data['coll_inherits']) - set(old_data['coll_inherits']) + ) + data['coll_inherits_removed'] = list( + set(old_data['coll_inherits']) - set(data['coll_inherits']) + ) + + SQL = render_template("/".join([self.template_path, 'update.sql']), + o_data=old_data, data=data, conn=self.conn) + # Removes training new lines + SQL = SQL.strip('\n') + '\n\n' + + # Parse/Format columns & create sql + if 'columns' in data: + # Parse the data coming from client + data = self._parse_format_columns(data, mode='edit') + + columns = data['columns'] + column_sql = '\n' + + # If column(s) is/are deleted + if 'deleted' in columns: + for c in columns['deleted']: + c['schema'] = data['schema'] + c['table'] = data['name'] + # Sql for drop column + if 'inheritedfrom' not in c: + column_sql += render_template("/".join( + [self.column_template_path, 'delete.sql']), + data=c, conn=self.conn).strip('\n') + '\n\n' + + # If column(s) is/are changed + # Here we will be needing previous properties of column + # so that we can compare & update it + if 'changed' in columns: + for c in columns['changed']: + c['schema'] = data['schema'] + c['table'] = data['name'] + if 'attacl' in c: + c['attacl'] = parse_priv_to_db(c['attacl'], + self.column_acl) + + properties_sql = render_template("/".join([self.column_template_path, + 'properties.sql']), + tid=tid, + clid=c['attnum'], + show_sys_objects=self.blueprint.show_system_objects + ) + + status, res = self.conn.execute_dict(properties_sql) + if not status: + return internal_server_error(errormsg=res) + old_data = res['rows'][0] + + old_data['cltype'] = self._cltype_formatter(old_data['cltype']) + old_data['hasSqrBracket'] = self.hasSqrBracket + + # Sql for alter column + if 'inheritedfrom' not in c: + column_sql += render_template("/".join( + [self.column_template_path, 'update.sql']), + data=c, o_data=old_data, conn=self.conn).strip('\n') + '\n\n' + + # If column(s) is/are added + if 'added' in columns: + for c in columns['added']: + c['schema'] = data['schema'] + c['table'] = data['name'] + # Sql for create column + if 'attacl' in c: + c['attacl'] = parse_priv_to_db(c['attacl'], + self.column_acl) + if 'inheritedfrom' not in c: + column_sql += render_template("/".join( + [self.column_template_path, 'create.sql']), + data=c, conn=self.conn).strip('\n') + '\n\n' + + # Combine all the SQL together + SQL += column_sql.strip('\n') + + # Check if index constraints are added/changed/deleted + index_constraint_sql = self.get_index_constraint_sql(tid, data) + # If we have index constraint sql then ad it in main sql + if index_constraint_sql is not None: + SQL += '\n' + index_constraint_sql + + # Check if foreign key(s) is/are added/changed/deleted + foreign_key_sql = self.get_foreign_key_sql(tid, data) + # If we have foreign key sql then ad it in main sql + if foreign_key_sql is not None: + SQL += '\n' + foreign_key_sql + + # Check if check constraint(s) is/are added/changed/deleted + check_constraint_sql = self.get_check_constraint_sql(tid, data) + # If we have check constraint sql then ad it in main sql + if check_constraint_sql is not None: + SQL += '\n' + check_constraint_sql + + # Check if exclusion constraint(s) is/are added/changed/deleted + exclusion_constraint_sql = self.get_exclusion_constraint_sql(tid, data) + # If we have check constraint sql then ad it in main sql + if exclusion_constraint_sql is not None: + SQL += '\n' + exclusion_constraint_sql + + else: + required_args = [ + 'name' + ] + + for arg in required_args: + if arg not in data: + return gettext('-- incomplete definition') + + # validate constraint data. + for key in ['primary_key', 'unique_constraint', + 'foreign_key', 'check_constraint', + 'exclude_constraint']: + if key in data and len(data[key]) > 0: + for constraint in data[key]: + if not self.validate_constrains(key, constraint): + return gettext('-- incomplete definition for {0}'.format(key)) + + # We will convert privileges coming from client required + # in server side format + if 'relacl' in data: + data['relacl'] = parse_priv_to_db(data['relacl'], self.acl) + + # Parse & format columns + data = self._parse_format_columns(data) + + if 'foreign_key' in data: + for c in data['foreign_key']: + SQL = render_template("/".join([self.foreign_key_template_path, + 'get_parent.sql']), + tid=c['columns'][0]['references']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + c['remote_schema'] = rset['rows'][0]['schema'] + c['remote_table'] = rset['rows'][0]['table'] + + # If the request for new object which do not have did + SQL = render_template("/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn) + SQL = re.sub('\n{2,}', '\n', SQL) + SQL = SQL.strip('\n') + + return SQL + + def validate_constrains(self, key, data): + + if key == 'primary_key' or key == 'unique_constraint': + if 'columns' in data and len(data['columns']) > 0: + return True + else: + return False + elif key == 'foreign_key': + for arg in ['columns']: + if arg not in data: + return False + elif isinstance(data[arg], list) and len(data[arg]) < 1: + return False + + if data['autoindex'] and ('coveringindex' not in data or + data['coveringindex'] == ''): + return False + + return True + + elif key == 'check_constraint': + for arg in ['consrc']: + if arg not in data or data[arg] == '': + return False + return True + + elif key == 'exclude_constraint': + pass + + return True + + @check_precondition + def dependents(self, gid, sid, did, scid, tid): + """ + This function get the dependents and return ajax response + for the table node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + # Specific condition for column which we need to append + where = "WHERE dep.refobjid={0}::OID".format(tid) + + dependents_result = self.get_dependents( + self.conn, tid + ) + + # Specific sql to run againt column to fetch dependents + SQL = render_template("/".join([self.template_path, + 'depend.sql']), where=where) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + ref_name = row['refname'] + if ref_name is None: + continue + + dep_type = '' + dep_str = row['deptype'] + if dep_str == 'a': + dep_type = 'auto' + elif dep_str == 'n': + dep_type = 'normal' + elif dep_str == 'i': + dep_type = 'internal' + + dependents_result.append({'type': 'sequence', 'name': ref_name, 'field': dep_type}) + + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid): + """ + This function get the dependencies and return ajax response + for the table node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + """ + dependencies_result = self.get_dependencies( + self.conn, tid + ) + + return ajax_response( + response=dependencies_result, + status=200 + ) + + @check_precondition + def sql(self, gid, sid, did, scid, tid): + """ + This function will creates reverse engineered sql for + the table object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + main_sql = [] + + """ + ##################################### + # 1) Reverse engineered sql for TABLE + ##################################### + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + + # Table & Schema declaration so that we can use them in child nodes + schema = data['schema'] + table = data['name'] + + data = self._formatter(scid, tid, data) + + # Now we have all lis of columns which we need + # to include in our create definition, Let's format them + if 'columns' in data: + for c in data['columns']: + if 'attacl' in c: + c['attacl'] = parse_priv_to_db(c['attacl'], self.column_acl) + + # check type for '[]' in it + c['cltype'] = self._cltype_formatter(c['cltype']) + c['hasSqrBracket'] = self.hasSqrBracket + + sql_header = "-- Table: {0}\n\n-- ".format(self.qtIdent(self.conn, + data['schema'], + data['name'])) + sql_header += render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn) + + sql_header = sql_header.strip('\n') + sql_header += '\n' + + # Add into main sql + main_sql.append(sql_header) + + # Parse privilege data + if 'relacl' in data: + data['relacl'] = parse_priv_to_db(data['relacl'], self.acl) + + # If the request for new object which do not have did + table_sql = render_template("/".join([self.template_path, + 'create.sql']), + data=data, conn=self.conn) + + # Add into main sql + table_sql = re.sub('\n{2,}', '\n\n', table_sql) + main_sql.append(table_sql.strip('\n')) + + """ + ###################################### + # 2) Reverse engineered sql for INDEX + ###################################### + """ + + SQL = render_template("/".join([self.index_template_path, + 'nodes.sql']), tid=tid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + + SQL = render_template("/".join([self.index_template_path, + 'properties.sql']), + tid=tid, idx=row['oid'], + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while creating sql + data['schema'] = schema + data['table'] = table + # We also need to fecth columns of index + SQL = render_template("/".join([self.index_template_path, + 'column_details.sql']), + idx=row['oid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + # 'attdef' comes with quotes from query so we need to strip them + # 'options' we need true/false to render switch ASC(false)/DESC(true) + columns = [] + cols = [] + for row in rset['rows']: + # We need all data as collection for ColumnsModel + cols_data = { + 'colname': row['attdef'].strip('"'), + 'collspcname': row['collnspname'], + 'op_class': row['opcname'], + } + if row['options'][0] == 'DESC': + cols_data['sort_order'] = True + columns.append(cols_data) + + # We need same data as string to display in properties window + # If multiple column then separate it by colon + cols_str = row['attdef'] + if row['collnspname']: + cols_str += ' COLLATE ' + row['collnspname'] + if row['opcname']: + cols_str += ' ' + row['opcname'] + if row['options'][0] == 'DESC': + cols_str += ' DESC' + cols.append(cols_str) + + # Push as collection + data['columns'] = columns + # Push as string + data['cols'] = ', '.join(cols) + + sql_header = "\n-- Index: {0}\n\n-- ".format(data['name']) + sql_header += render_template("/".join([self.index_template_path, + 'delete.sql']), + data=data, conn=self.conn) + + index_sql = render_template("/".join([self.index_template_path, + 'create.sql']), + data=data, conn=self.conn) + index_sql += "\n" + index_sql += render_template("/".join([self.index_template_path, + 'alter.sql']), + data=data, conn=self.conn) + + # Add into main sql + index_sql = re.sub('\n{2,}', '\n\n', index_sql) + main_sql.append(sql_header + '\n\n' + index_sql.strip('\n')) + + """ + ######################################## + # 3) Reverse engineered sql for TRIGGERS + ######################################## + """ + SQL = render_template("/".join([self.trigger_template_path, + 'nodes.sql']), tid=tid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + trigger_sql = '' + + SQL = render_template("/".join([self.trigger_template_path, + 'properties.sql']), + tid=tid, trid=row['oid'], + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while creating sql + data['schema'] = schema + data['table'] = table + + if data['tgnargs'] > 1: + # We know that trigger has more than 1 arguments, let's join them + data['tgargs'] = ', '.join(data['tgargs']) + + if len(data['tgattr']) > 1: + columns = ', '.join(data['tgattr'].split(' ')) + + SQL = render_template("/".join([self.trigger_template_path, + 'get_columns.sql']), + tid=tid, clist=columns) + + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + # 'tgattr' contains list of columns from table used in trigger + columns = [] + + for row in rset['rows']: + columns.append({'column': row['name']}) + + data['columns'] = columns + + data = trigger_definition(data) + + sql_header = "\n-- Trigger: {0}\n\n-- ".format(data['name']) + sql_header += render_template("/".join([self.trigger_template_path, + 'delete.sql']), + data=data, conn=self.conn) + + # If the request for new object which do not have did + trigger_sql = render_template("/".join([self.trigger_template_path, + 'create.sql']), + data=data, conn=self.conn) + + trigger_sql = sql_header + '\n\n' + trigger_sql.strip('\n') + + # If trigger is disabled then add sql code for the same + if not data['is_enable_trigger']: + trigger_sql += '\n\n' + trigger_sql += render_template("/".join([ + self.trigger_template_path, + 'enable_disable_trigger.sql']), + data=data, conn=self.conn) + + # Add into main sql + trigger_sql = re.sub('\n{2,}', '\n\n', trigger_sql) + main_sql.append(trigger_sql) + + """ + ##################################### + # 4) Reverse engineered sql for RULES + ##################################### + """ + + SQL = render_template("/".join( + [self.rules_template_path, 'properties.sql']), tid=tid) + + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + rules_sql = '\n' + SQL = render_template("/".join( + [self.rules_template_path, 'properties.sql'] + ), rid=row['oid'], datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + res_data = parse_rule_definition(res) + rules_sql += render_template("/".join( + [self.rules_template_path, 'create.sql']), + data=res_data, display_comments=True) + + # Add into main sql + rules_sql = re.sub('\n{2,}', '\n\n', rules_sql) + main_sql.append(rules_sql) + + sql = '\n'.join(main_sql) + + return ajax_response(response=sql.strip('\n')) + + @check_precondition + def select_sql(self, gid, sid, did, scid, tid): + """ + SELECT script sql for the object + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + + Returns: + SELECT Script sql for the object + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data = self._formatter(scid, tid, data) + + columns = [] + + # Now we have all list of columns which we need + if 'columns' in data: + for c in data['columns']: + columns.append(self.qtIdent(self.conn, c['attname'])) + + if len(columns) > 0: + columns = ", ".join(columns) + else: + columns = '*' + + sql = "SELECT {0}\n\tFROM {1};".format( + columns, + self.qtIdent(self.conn, data['schema'], data['name']) + ) + return ajax_response(response=sql) + + @check_precondition + def insert_sql(self, gid, sid, did, scid, tid): + """ + INSERT script sql for the object + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + + Returns: + INSERT Script sql for the object + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data = self._formatter(scid, tid, data) + + columns = [] + values = [] + + # Now we have all list of columns which we need + if 'columns' in data: + for c in data['columns']: + columns.append(self.qtIdent(self.conn, c['attname'])) + values.append('?') + + if len(columns) > 0: + columns = ", ".join(columns) + values = ", ".join(values) + sql = "INSERT INTO {0}(\n\t{1})\n\tVALUES ({2});".format( + self.qtIdent(self.conn, data['schema'], data['name']), + columns, values + ) + else: + sql = gettext('-- Please create column(s) first...') + + return ajax_response(response=sql) + + @check_precondition + def update_sql(self, gid, sid, did, scid, tid): + """ + UPDATE script sql for the object + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + + Returns: + UPDATE Script sql for the object + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data = self._formatter(scid, tid, data) + + columns = [] + + # Now we have all list of columns which we need + if 'columns' in data: + for c in data['columns']: + columns.append(self.qtIdent(self.conn, c['attname'])) + + if len(columns) > 0: + if len(columns) == 1: + columns = columns[0] + columns += "=?" + else: + columns = "=?, ".join(columns) + + sql = "UPDATE {0}\n\tSET {1}\n\tWHERE ;".format( + self.qtIdent(self.conn, data['schema'], data['name']), + columns + ) + else: + sql = gettext('-- Please create column(s) first...') + + return ajax_response(response=sql) + + @check_precondition + def delete_sql(self, gid, sid, did, scid, tid): + """ + DELETE script sql for the object + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + + Returns: + DELETE Script sql for the object + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + + sql = "DELETE FROM {0}\n\tWHERE ;".format( + self.qtIdent(self.conn, data['schema'], data['name']) + ) + + return ajax_response(response=sql) + + +TableView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/__init__.py new file mode 100644 index 000000000..5bcb456ab --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/__init__.py @@ -0,0 +1,906 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Column Node """ + +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.server_groups.servers.databases.schemas.utils \ + import DataTypeReader +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases as database +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \ + parse_priv_to_db +from functools import wraps +import json + + +class ColumnsModule(CollectionNodeModule): + """ + class ColumnsModule(CollectionNodeModule) + + A module class for Column node derived from CollectionNodeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the Column and it's base module. + + * get_nodes(gid, sid, did, scid, tid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for schema, when any of the server node is + initialized. + """ + + NODE_TYPE = 'column' + COLLECTION_LABEL = gettext("Columns") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the ColumnModule and it's base module. + + Args: + *args: + **kwargs: + """ + self.min_ver = None + self.max_ver = None + super(ColumnsModule, self).__init__(*args, **kwargs) + + def get_nodes(self, gid, sid, did, scid, **kwargs): + """ + Generate the collection node + """ + assert('tid' in kwargs or 'vid' in kwargs) + yield self.generate_browser_collection_node( + kwargs['tid'] if 'tid' in kwargs else kwargs['vid'] + ) + + @property + def script_load(self): + """ + Load the module script for server, when any of the server-group node is + initialized. + """ + return database.DatabaseModule.NODE_TYPE + + @property + def node_inode(self): + """ + Load the module node as a leaf node + """ + return False + + +blueprint = ColumnsModule(__name__) + + +class ColumnsView(PGChildNodeView, DataTypeReader): + """ + This class is responsible for generating routes for Column node + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the ColumnView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * list() + - This function is used to list all the Column nodes within that + collection. + + * nodes() + - This function will used to create all the child node within that + collection, Here it will create all the Column node. + + * properties(gid, sid, did, scid, tid, clid) + - This function will show the properties of the selected Column node + + * create(gid, sid, did, scid, tid) + - This function will create the new Column object + + * update(gid, sid, did, scid, tid, clid) + - This function will update the data for the selected Column node + + * delete(self, gid, sid, scid, tid, clid): + - This function will drop the Column object + + * msql(gid, sid, did, scid, tid, clid) + - This function is used to return modified SQL for the selected + Column node + + * get_sql(data, scid, tid) + - This function will generate sql from model data + + * sql(gid, sid, did, scid): + - This function will generate sql to show it in sql pane for the + selected Column node. + + * dependency(gid, sid, did, scid): + - This function will generate dependency list show it in dependency + pane for the selected Column node. + + * dependent(gid, sid, did, scid): + - This function will generate dependent list to show it in dependent + pane for the selected Column node. + """ + + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [ + # Here we specify type as any because table + # are also has '-' in them if they are system table + {'type': 'string', 'id': 'clid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + }) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + driver = get_driver(PG_DEFAULT_DRIVER) + self.manager = driver.connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + self.qtIdent = driver.qtIdent + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + ver = self.manager.version + # we will set template path for sql scripts + if ver >= 90200: + self.template_path = 'column/sql/9.2_plus' + else: + self.template_path = 'column/sql/9.1_plus' + # Allowed ACL for column 'Select/Update/Insert/References' + self.acl = ['a', 'r', 'w', 'x'] + + # We need parent's name eg table name and schema name + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=kwargs['tid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + self.schema = row['schema'] + self.table = row['table'] + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid, tid): + """ + This function is used to list all the schema nodes within that collection. + + Args: + gid: Server group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of available column nodes + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), tid=tid, + show_sys_objects=self.blueprint.show_system_objects) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, tid): + """ + This function will used to create all the child node within that collection. + Here it will create all the schema node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of available schema child nodes + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), tid=tid, + show_sys_objects=self.blueprint.show_system_objects) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon="icon-column", + datatype=row['datatype'] # We need datatype somewhere in + )) # exclusion constraint. + + return make_json_response( + data=res, + status=200 + ) + + def _formatter(self, scid, tid, clid, data): + """ + Args: + scid: schema oid + tid: table oid + clid: position of column in table + data: dict of query result + + Returns: + It will return formatted output of collections + """ + # To check if column is primary key + if 'attnum' in data and 'indkey' in data: + # Current column + attnum = str(data['attnum']) + + # Single/List of primary key column(s) + indkey = str(data['indkey']) + + # We will check if column is in primary column(s) + if attnum in indkey.split(" "): + data['is_pk'] = True + else: + data['is_pk'] = False + + # Find length & precision of column data type + fulltype = self.get_full_type( + data['typnspname'], data['typname'], + data['isdup'], data['attndims'], data['atttypmod'] + ) + + import re + # If we have length & precision both + matchObj = re.search(r'(\d+),(\d+)', fulltype) + if matchObj: + data['attlen'] = matchObj.group(1) + data['attprecision'] = matchObj.group(2) + else: + # If we have length only + matchObj = re.search(r'(\d+)', fulltype) + if matchObj: + data['attlen'] = matchObj.group(1) + data['attprecision'] = None + else: + data['attlen'] = None + data['attprecision'] = None + + # We need to fetch inherited tables for each table + SQL = render_template("/".join([self.template_path, + 'get_inherited_tables.sql']), + tid=tid) + status, inh_res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=inh_res) + for row in inh_res['rows']: + if row['attrname'] == data['name']: + data['is_inherited'] = True + data['tbls_inherited'] = row['inhrelname'] + + # We need to format variables according to client js collection + if 'attoptions' in data and data['attoptions'] is not None: + spcoptions = [] + for spcoption in data['attoptions']: + k, v = spcoption.split('=') + spcoptions.append({'name': k, 'value': v}) + + data['attoptions'] = spcoptions + + # Need to format security labels according to client js collection + if 'seclabels' in data and data['seclabels'] is not None: + seclabels = [] + for seclbls in data['seclabels']: + k, v = seclbls.split('=') + seclabels.append({'provider': k, 'label': v}) + + data['seclabels'] = seclabels + + # We need to parse & convert ACL coming from database to json format + SQL = render_template("/".join([self.template_path, 'acl.sql']), + tid=tid, clid=clid) + status, acl = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=acl) + + # We will set get privileges from acl sql so we don't need + # it from properties sql + data['attacl'] = [] + + for row in acl['rows']: + priv = parse_priv_from_db(row) + data.setdefault(row['deftype'], []).append(priv) + + # we are receiving request when in edit mode + # we will send filtered types related to current type + present_type = data['cltype'] + type_id = data['atttypid'] + + SQL = render_template("/".join([self.template_path, + 'is_referenced.sql']), + tid=tid, clid=clid) + + status, is_reference = self.conn.execute_scalar(SQL) + + edit_types_list = list() + # We will need present type in edit mode + edit_types_list.append(present_type) + + if int(is_reference) == 0: + SQL = render_template("/".join([self.template_path, + 'edit_mode_types.sql']), + type_id=type_id) + status, rset = self.conn.execute_2darray(SQL) + + for row in rset['rows']: + edit_types_list.append(row['typname']) + else: + edit_types_list.append(present_type) + + data['edit_types'] = edit_types_list + + # Manual Data type formatting + # If data type has () with them then we need to remove them + # eg bit(1) because we need to match the name with combobox + isArray = False + if data['cltype'].endswith('[]'): + isArray = True + data['cltype'] = data['cltype'].rstrip('[]') + + idx = data['cltype'].find('(') + if idx and data['cltype'].endswith(')'): + data['cltype'] = data['cltype'][:idx] + + if isArray: + data['cltype'] += "[]" + + return data + + @check_precondition + def properties(self, gid, sid, did, scid, tid, clid): + """ + This function will show the properties of the selected schema node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + scid: Schema ID + tid: Table ID + clid: Column ID + + Returns: + JSON of selected schema node + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), tid=tid, clid=clid + , show_sys_objects=self.blueprint.show_system_objects) + + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + # Making copy of output for future use + data = dict(res['rows'][0]) + data = self._formatter(scid, tid, clid, data) + + return ajax_response( + response=data, + status=200 + ) + + def _cltype_formatter(self, type): + """ + + Args: + data: Type string + + Returns: + We need to remove [] from type and append it + after length/precision so we will set flag for + sql template + """ + if '[]' in type: + type = type.replace('[]', '') + self.hasSqrBracket = True + else: + self.hasSqrBracket = False + + return type + + @check_precondition + def create(self, gid, sid, did, scid, tid): + """ + This function will creates new the schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + data = request.form if request.form else json.loads( + request.data.decode() + ) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + required_args = { + 'name': 'Name', + 'cltype': 'Type' + } + + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % + required_args[arg] + ) + ) + + # Parse privilege data coming from client according to database format + if 'attacl' in data: + data['attacl'] = parse_priv_to_db(data['attacl'], self.acl) + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + # check type for '[]' in it + data['cltype'] = self._cltype_formatter(data['cltype']) + data['hasSqrBracket'] = self.hasSqrBracket + + try: + SQL = render_template("/".join([self.template_path, + 'create.sql']), + data=data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # we need oid to to add object in tree at browser + SQL = render_template("/".join([self.template_path, + 'get_position.sql']), + tid=tid, data=data) + status, clid = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=tid) + + return jsonify( + node=self.blueprint.generate_browser_node( + clid, + scid, + data['name'], + icon="icon-column" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, clid): + """ + This function will updates existing the schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + clid: Column ID + """ + # We will first fetch the column name for current request + # so that we create template for dropping column + try: + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), tid=tid, clid=clid + , show_sys_objects=self.blueprint.show_system_objects) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = dict(res['rows'][0]) + # We will add table & schema as well + data['schema'] = self.schema + data['table'] = self.table + + SQL = render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Column is dropped"), + data={ + 'id': clid, + 'tid': tid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, tid, clid): + """ + This function will updates existing the schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + clid: Column ID + """ + data = request.form if request.form else json.loads(request.data.decode()) + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + # check type for '[]' in it + if 'cltype' in data: + data['cltype'] = self._cltype_formatter(data['cltype']) + data['hasSqrBracket'] = self.hasSqrBracket + + try: + SQL = self.get_sql(scid, tid, clid, data) + if SQL and SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Column updated", + data={ + 'id': clid, + 'tid': tid, + 'scid': scid + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': clid, + 'tid': tid, + 'scid': scid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + + @check_precondition + def msql(self, gid, sid, did, scid, tid, clid=None): + """ + This function will generates modified sql for schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + clid: Column ID (When working with existing column) + """ + data = dict() + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + # check type for '[]' in it + if 'cltype' in data: + data['cltype'] = self._cltype_formatter(data['cltype']) + data['hasSqrBracket'] = self.hasSqrBracket + + try: + SQL = self.get_sql(scid, tid, clid, data) + + if SQL and SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_sql(self, scid, tid, clid, data): + """ + This function will genrate sql from model data + """ + if clid is not None: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), tid=tid, clid=clid + , show_sys_objects=self.blueprint.show_system_objects) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + old_data = dict(res['rows'][0]) + # We will add table & schema as well + old_data = self._formatter(scid, tid, clid, old_data) + + # If name is not present in data then + # we will fetch it from old data, we also need schema & table name + if 'name' not in data: + data['name'] = old_data['name'] + + # Convert acl coming from client in db parsing format + key = 'attacl' + if key in data and data[key] is not None: + if 'added' in data[key]: + data[key]['added'] = parse_priv_to_db( + data[key]['added'], self.acl + ) + if 'changed' in data[key]: + data[key]['changed'] = parse_priv_to_db( + data[key]['changed'], self.acl + ) + if 'deleted' in data[key]: + data[key]['deleted'] = parse_priv_to_db( + data[key]['deleted'], self.acl + ) + + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data, conn=self.conn + ) + else: + required_args = [ + 'name', + 'cltype' + ] + + for arg in required_args: + if arg not in data: + return gettext('-- incomplete definition') + + # We will convert privileges coming from client required + # in server side format + if 'attacl' in data: + data['attacl'] = parse_priv_to_db(data['attacl'], + self.acl) + # If the request for new object which do not have did + SQL = render_template("/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn) + return SQL + + @check_precondition + def sql(self, gid, sid, did, scid, tid, clid): + """ + This function will generates reverse engineered sql for schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + clid: Column ID + """ + try: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), tid=tid, clid=clid + , show_sys_objects=self.blueprint.show_system_objects) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = dict(res['rows'][0]) + # We do not want to display length as -1 in create query + if 'attlen' in data and data['attlen'] == -1: + data['attlen'] = '' + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + # check type for '[]' in it + if 'cltype' in data: + data['cltype'] = self._cltype_formatter(data['cltype']) + data['hasSqrBracket'] = self.hasSqrBracket + + # We will add table & schema as well + data = self._formatter(scid, tid, clid, data) + + SQL = self.get_sql(scid, tid, None, data) + + sql_header = "-- Column: {0}\n\n-- ".format(self.qtIdent(self.conn, + data['schema'], + data['table'], + data['name'])) + sql_header += render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn) + SQL = sql_header + '\n\n' + SQL + + return SQL + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, tid, clid): + """ + This function get the dependents and return ajax response + for the column node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + clid: Column ID + """ + # Specific condition for column which we need to append + where = "WHERE dep.refobjid={0}::OID AND dep.refobjsubid={1}".format( + tid, clid + ) + + dependents_result = self.get_dependents( + self.conn, clid, where=where + ) + + # Specific sql to run againt column to fetch dependents + SQL = render_template("/".join([self.template_path, + 'depend.sql']), where=where) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + ref_name = row['refname'] + if ref_name is None: + continue + + dep_type = '' + dep_str = row['deptype'] + if dep_str == 'a': + dep_type = 'auto' + elif dep_str == 'n': + dep_type = 'normal' + elif dep_str == 'i': + dep_type = 'internal' + + dependents_result.append({'type': 'sequence', 'name': ref_name, 'field': dep_type}) + + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid, clid): + """ + This function get the dependencies and return ajax response + for the column node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + clid: Column ID + + """ + # Specific condition for column which we need to append + dependencies_result = self.get_dependencies( + self.conn, clid + ) + + return ajax_response( + response=dependencies_result, + status=200 + ) + + +ColumnsView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/img/coll-column.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/img/coll-column.png new file mode 100644 index 0000000000000000000000000000000000000000..89d758834d4176c1df2548db10b46b1f6b2e4ec5 GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}cz{ocE09*4`h3Ev&s(m&yL({S;_3G90=g%KJ zc<}o5>kl73eEaro$BrEvHf-3qapU*z-`~D{TatgM6KFJJNswPKgTu2MX+REVfk$L9 zkoEv$x0Bg+Kt_S5i(`ny<=FG?VhsvBt`}W4E@ZQg__p6qm@nby;qrG1j0_I@c^(;r zuhP)2X?)FK#IZ0 zz|cU~&`8(7FvQ5f%EZ{p#6;V`)XKoXVy3DbiiX_$l+3hBhz0{oum+H7D+4o#hEvl+ R*8nvzc)I$ztaD0e0s#BYr!@co literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/img/column.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/static/img/column.png new file mode 100644 index 0000000000000000000000000000000000000000..bd9f81df98fe27d81ade5144d66b3b09b96123c1 GIT binary patch literal 435 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}X@F0NE09*4`h3Ev&s(m&yLCXv$t$X?M<;s;SSFBjEcJ12r>({?} z_3FWc2M-@UeDvti>eZ{)tXZ>Z)20s}KD>VY`qQURpFe;8`t|F#Z{K$8*s)>5hK(CH ze*gac?c2BS-o49eKe!ZVF=I)PUoeBivm0qZ4rhT!WHFHT0Ash4*>*risi%u$h{WaE z^B0Ah6a-om4Rm-iuV3+XfEpM)UHx3vIVCg! E0NRqnZ~y=R literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/js/column.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/js/column.js new file mode 100644 index 000000000..5f8edc69e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/js/column.js @@ -0,0 +1,586 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', + 'backform', 'alertify', 'pgadmin.browser.collection'], +function($, _, S, pgAdmin, pgBrowser, Backform, alertify) { + + if (!pgBrowser.Nodes['coll-column']) { + var databases = pgAdmin.Browser.Nodes['coll-column'] = + pgAdmin.Browser.Collection.extend({ + node: 'column', + label: '{{ _('Columns') }}', + type: 'coll-column', + sqlAlterHelp: 'sql-altertable.html', + sqlCreateHelp: 'sql-altertable.html', + columns: ['name', 'atttypid', 'description'] + }); + }; + + // Switch Cell for Primary Key selection + var SwitchDepCell = Backgrid.BooleanCell.extend({ + initialize: function() { + Backgrid.BooleanCell.prototype.initialize.apply(this, arguments); + Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments); + }, + dependentChanged: function () { + var model = this.model, + column = this.column, + editable = this.column.get("editable"), + input = this.$el.find('input[type=checkbox]').first(); + + is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable; + if (is_editable) { + this.$el.addClass("editable"); + input.prop('disabled', false); + } else { + this.$el.removeClass("editable"); + input.prop('disabled', true); + } + + this.delegateEvents(); + return this; + }, + remove: Backgrid.Extension.DependentCell.prototype.remove + }); + + // This Node model will be used for variable control for column + var VariablesModel = Backform.VariablesModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: null, + value: null + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'select2', + type: 'text', disabled: false, node: 'column', + options: [['n_distinct', 'n_distinct'], + ['n_distinct_inherited','n_distinct_inherited']], + select2: {placeholder: "Select variable"}, + cellHeaderClasses:'width_percent_50' + },{ + id: 'value', label: '{{ _('Value') }}', + type: 'text', disabled: false, + cellHeaderClasses:'width_percent_50' + }], + validate: function() { + var err = {}, + errmsg = null; + + if (_.isUndefined(this.get('value')) || + _.isNull(this.get('value')) || + String(this.get('value')).replace(/^\s+|\s+$/g, '') == '') { + errmsg = '{{ _('Please provide input for variable.')}}'; + this.errorModel.set('value', errmsg); + return errmsg; + } else { + this.errorModel.unset('value'); + } + return null; + } + }); + + if (!pgBrowser.Nodes['column']) { + pgAdmin.Browser.Nodes['column'] = pgAdmin.Browser.Node.extend({ + parent_type: ['table', 'view', 'mview'], + collection_type: ['coll-table', 'coll-view', 'coll-mview'], + type: 'column', + label: '{{ _('Column') }}', + hasSQL: true, + canDrop: function(itemData, item, data){ + if (pgBrowser.Nodes['schema'].canChildDrop.apply(this, [itemData, item, data])) { + var t = pgBrowser.tree, i = item, d = itemData, parents = []; + // To iterate over tree to check parent node + while (i) { + parents.push(d._type); + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // Check if menu is allowed ? + if(_.indexOf(parents, 'catalog') > -1 || + _.indexOf(parents, 'view') > -1 || + _.indexOf(parents, 'mview') > -1) { + return false; + } else if(_.indexOf(parents, 'table') > -1) { + return true; + } + } else { + return false; + } + }, + hasDepends: true, + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_column_on_coll', node: 'coll-column', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Column...') }}', + icon: 'wcTabIcon icon-column', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_column', node: 'column', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Column...') }}', + icon: 'wcTabIcon icon-column', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_column_onTable', node: 'table', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Column...') }}', + icon: 'wcTabIcon icon-column', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_column_onView', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Column...') }}', + icon: 'wcTabIcon icon-column', data: {action: 'create', check: true}, + enable: 'canCreate' + } + ]); + }, + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + attowner: undefined, + atttypid: undefined, + attnum: undefined, + cltype: undefined, + collspcname: undefined, + attacl: undefined, + description: undefined, + parent_tbl: undefined, + min_val: undefined, + max_val: undefined, + edit_types: undefined, + is_primary_key: false, + inheritedfrom: undefined, + attstattarget:undefined + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', + type: 'text', disabled: 'inSchemaWithColumnCheck', + cellHeaderClasses:'width_percent_30', + editable: 'editable_check_for_table' + },{ + // Need to show this field only when creating new table [in SubNode control] + id: 'is_primary_key', label: '{{ _('Is primary key?') }}', cell: SwitchDepCell, + type: 'switch', deps:['name'], + options: { + onText: 'Yes', offText: 'No', onColor: 'success', + offColor: 'primary', size: 'small'}, + cellHeaderClasses:'width_percent_5', + visible: function(m) { + return _.isUndefined(m.top.node_info['table'] || m.top.node_info['view'] || m.top.node_info['mview']); + }, + disabled: function(m){ + // If primary key already exist then disable. + if (m.top && !_.isUndefined(m.top.get('oid')) && + m.top.get('primary_key').length > 0 && + !_.isUndefined(m.top.get('primary_key').first().get('oid'))) { + return true; + } + + var name = m.get('name'); + + if(!m.inSchemaWithColumnCheck.apply(this, [m]) && + (_.isUndefined(name) || _.isNull(name) || name == '')) { + return true; + } + return false; + }, + editable: function(m){ + var name = m.get('name'); + // If HeaderCell then allow True + if(m instanceof Backbone.Collection) { + return true; + } + // If primary key already exist then disable. + if (m.top && !_.isUndefined(m.top.get('oid')) && + m.top.get('primary_key').length > 0 && + !_.isUndefined(m.top.get('primary_key').first().get('oid'))) { + + return false; + } + + if(!m.inSchemaWithColumnCheck.apply(this, [m]) && + !_.isUndefined(name) && !_.isNull(name) && name !== '') { + return true; + } + return false; + // Set to false if no condition is met + m.set('is_primary_key', false); + } + },{ + id: 'attnum', label:'{{ _('Position') }}', cell: 'string', + type: 'text', disabled: 'inSchema', mode: ['properties'] + },{ + id: 'cltype', label:'{{ _('Data type') }}', cell: 'node-ajax-options', + type: 'text', disabled: 'inSchemaWithColumnCheck', + control: 'node-ajax-options', url: 'get_types', node: 'table', + cellHeaderClasses:'width_percent_30', first_empty: true, + select2: { allowClear: false }, group: '{{ _('Definition') }}', + transform: function(data, cell) { + /* 'transform' function will be called by control, and cell both. + * The way, we use the transform in cell, and control is different. + * Because - options are shared using 'column' object in backgrid, + * hence - the cell is passed as second parameter, while the control + * uses (this) as a object. + */ + var control = cell || this, + m = control.model; + + /* We need different data in create mode & in edit mode + * if we are in create mode then return data as it is + * if we are in edit mode then we need to filter data + */ + control.model.datatypes = data; + var edit_types = m.get('edit_types'), + result = []; + + // If called from Table, We will check if in edit mode + // then send edit_types only + if( !_.isUndefined(m.top) && !m.top.isNew() ) { + _.each(data, function(t) { + if (_.indexOf(edit_types, t.value) != -1) { + result.push(t); + } + }); + // There may be case that user adds new column in existing collection + // we will not have edit types then + return result.length > 0 ? result : data; + } + + // If called from Column + if(m.isNew()) { + return data; + } else { + //edit mode + _.each(data, function(t) { + if (_.indexOf(edit_types, t.value) != -1) { + result.push(t); + } + }); + + return result; + } + }, + editable: 'editable_check_for_table' + },{ + // Need to show this field only when creating new table [in SubNode control] + id: 'inheritedfrom', label: '{{ _('Inherited from table') }}', + type: 'text', disabled: true, editable: false, + cellHeaderClasses:'width_percent_30', + visible: function(m) { + return _.isUndefined(m.top.node_info['table'] || m.top.node_info['view'] || m.top.node_info['mview']); + } + },{ + id: 'attlen', label:'{{ _('Length') }}', cell: 'string', + deps: ['cltype'], type: 'int', group: '{{ _('Definition') }}', + disabled: function(m) { + var of_type = m.get('cltype'), + flag = true; + _.each(m.datatypes, function(o) { + if ( of_type == o.value ) { + if(o.length) + { + m.set('min_val', o.min_val, {silent: true}); + m.set('max_val', o.max_val, {silent: true}); + flag = false; + } + } + }); + + flag && setTimeout(function() { + m.set('attlen', null); + },10); + + return flag; + } + },{ + id: 'attprecision', label:'{{ _('Precision') }}', cell: 'string', + deps: ['cltype'], type: 'int', group: '{{ _('Definition') }}', + disabled: function(m) { + var of_type = m.get('cltype'), + flag = true; + _.each(m.datatypes, function(o) { + if ( of_type == o.value ) { + if(o.precision) + { + m.set('min_val', o.min_val, {silent: true}); + m.set('max_val', o.max_val, {silent: true}); + flag = false; + } + } + }); + + flag && setTimeout(function() { + m.set('attprecision', null); + },10); + return flag; + } + },{ + id: 'collspcname', label:'{{ _('Collation') }}', cell: 'string', + type: 'text', control: 'node-ajax-options', url: 'get_collations', + group: '{{ _('Definition') }}', node: 'collation', + deps: ['cltype'], disabled: function(m) { + var of_type = m.get('cltype'), + flag = true; + _.each(m.datatypes, function(o) { + if ( of_type == o.value ) { + if(o.is_collatable) + { + flag = false; + } + } + }); + if (flag) { + setTimeout(function(){ + m.set('collspcname', ""); + }, 10); + } + return flag; + } + },{ + id: 'defval', label:'{{ _('Default Value') }}', cell: 'string', + type: 'text', disabled: 'inSchemaWithColumnCheck', + group: '{{ _('Definition') }}' + },{ + id: 'attnotnull', label:'{{ _('Not NULL?') }}', cell: 'string', + type: 'switch', disabled: 'inSchemaWithColumnCheck', + group: '{{ _('Definition') }}' + },{ + id: 'attstattarget', label:'{{ _('Statistics') }}', cell: 'string', + type: 'text', disabled: 'inSchemaWithColumnCheck', mode: ['properties', 'edit'], + group: '{{ _('Definition') }}' + },{ + id: 'attstorage', label:'{{ _('Storage') }}', group: '{{ _('Definition') }}', + type: 'text', mode: ['properties', 'edit'], + cell: 'string', disabled: 'inSchemaWithColumnCheck', first_empty: true, + control: 'select2', select2: { placeholder: "Select storage", + allowClear: false, + width: "100%" + }, + options: [ + {label: "PLAIN", value: "p"}, + {label: "MAIN", value: "m"}, + {label: "EXTERNAL", value: "e"}, + {label: "EXTENDED", value: "x"}, + ] + },{ + id: 'is_pk', label:'{{ _('Primary key?') }}', + type: 'switch', disabled: true, mode: ['properties'] + },{ + id: 'is_fk', label:'{{ _('Foreign key?') }}', + type: 'switch', disabled: true, mode: ['properties'] + },{ + id: 'is_inherited', label:'{{ _('Inherited?') }}', + type: 'switch', disabled: true, mode: ['properties'] + },{ + id: 'tbls_inherited', label:'{{ _('Inherited from table(s)') }}', + type: 'text', disabled: true, mode: ['properties'], deps: ['is_inherited'], + visible: function(m) { + if (!_.isUndefined(m.get('is_inherited')) && m.get('is_inherited')) { + return true; + } else { + return false; + } + } + },{ + id: 'is_sys_column', label:'{{ _('System Column?') }}', cell: 'string', + type: 'switch', disabled: true, mode: ['properties'] + },{ + id: 'description', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + disabled: 'inSchema' + },{ + id: 'attacl', label: 'Privileges', type: 'collection', + group: '{{ _('Security') }}', control: 'unique-col-collection', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({ + privileges: ['a','r','w','x']}), + mode: ['edit'], canAdd: true, canDelete: true, + uniqueCol : ['grantee'] + },{ + id: 'attoptions', label: 'Variables', type: 'collection', + group: '{{ _('Security') }}', control: 'unique-col-collection', + model: VariablesModel, uniqueCol : ['name'], + mode: ['edit', 'create'], canAdd: true, canEdit: false, + canDelete: true + },{ + id: 'seclabels', label: '{{ _('Security Labels') }}', + model: pgAdmin.Browser.SecurityModel, + editable: false, type: 'collection', + group: '{{ _('Security') }}', mode: ['edit', 'create'], + min_version: 90100, canAdd: true, + canEdit: false, canDelete: true, control: 'unique-col-collection' + } + ], + validate: function(keys) { + var err = {}, + changedAttrs = this.changed, + msg = undefined; + + // Nothing to validate + if (keys && keys.length == 0) { + this.errorModel.clear(); + return null; + } else { + this.errorModel.clear(); + } + + if (_.isUndefined(this.get('name')) + || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') { + msg = '{{ _('Column name can not be empty.') }}'; + this.errorModel.set('name', msg); + return msg; + } + + if (_.isUndefined(this.get('cltype')) + || String(this.get('cltype')).replace(/^\s+|\s+$/g, '') == '') { + msg = '{{ _('Column type can not be empty.') }}'; + this.errorModel.set('cltype', msg); + return msg; + } + + if (!_.isUndefined(this.get('cltype')) + && !_.isUndefined(this.get('attlen')) + && !_.isNull(this.get('attlen')) + && this.get('attlen') !== '') { + // Validation for Length field + if (this.get('attlen') < this.get('min_val')) + msg = '{{ _('Length should not be less than: ') }}' + this.get('min_val'); + if (this.get('attlen') > this.get('max_val')) + msg = '{{ _('Length should not be greater than: ') }}' + this.get('max_val'); + // If we have any error set then throw it to user + if(msg) { + this.errorModel.set('attlen', msg) + return msg; + } + } + + if (!_.isUndefined(this.get('cltype')) + && !_.isUndefined(this.get('attprecision')) + && !_.isNull(this.get('attprecision')) + && this.get('attprecision') !== '') { + // Validation for precision field + if (this.get('attprecision') < this.get('min_val')) + msg = '{{ _('Precision should not be less than: ') }}' + this.get('min_val'); + if (this.get('attprecision') > this.get('max_val')) + msg = '{{ _('Precision should not be greater than: ') }}' + this.get('max_val'); + // If we have any error set then throw it to user + if(msg) { + this.errorModel.set('attprecision', msg) + return msg; + } + } + + return null; + }, + isInhertedColumn: function() { + }, + // We will check if we are under schema node & in 'create' mode + inSchema: function() { + if(this.node_info && 'catalog' in this.node_info) + { + return true; + } + return false; + }, + // We will check if we are under schema node & in 'create' mode + inSchemaWithModelCheck: function(m) { + if(this.node_info && 'schema' in this.node_info) + { + // We will disable control if it's in 'edit' mode + if (m.isNew()) { + return false; + } else { + return true; + } + } + return true; + }, + // Checks weather to enable/disable control + inSchemaWithColumnCheck: function(m) { + var node_info = this.node_info || m.node_info || m.top.node_info; + + // disable all fields if column is listed under view or mview + if ('view' in node_info || 'mview' in node_info) { + if (this && _.has(this, 'name') && (this.name != 'defval')) { + return true; + } + } + + if(node_info && 'schema' in node_info) + { + // We will disable control if it's system columns + // inheritedfrom check is useful when we use this schema in table node + // inheritedfrom has value then we should disable it + if(!_.isUndefined(m.get('inheritedfrom'))) { + return true; + } + // ie: it's position is less then 1 + if (m.isNew()) { + return false; + } + // if we are in edit mode + if (!_.isUndefined(m.get('attnum')) && m.get('attnum') > 0 ) { + return false; + } else { + return true; + } + } + return true; + }, + editable_check_for_table: function(arg) { + if (arg instanceof Backbone.Collection) { + return !arg.model.prototype.inSchemaWithColumnCheck.apply( + this, [arg.top] + ); + } else { + return !arg.inSchemaWithColumnCheck.apply( + this, [arg] + ); + } + } + }), + // Below function will enable right click menu for creating column + canCreate: function(itemData, item, data) { + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData, parents = []; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to create table + if (_.indexOf(['schema'], d._type) > -1) { + return true; + } + else if (_.indexOf(['view', 'coll-view', + 'mview', + 'coll-mview'], d._type) > -1) { + parents.push(d._type); + break; + } + parents.push(d._type); + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // If node is under catalog then do not allow 'create' menu + if (_.indexOf(parents, 'catalog') > -1 || + _.indexOf(parents, 'coll-view') > -1 || + _.indexOf(parents, 'coll-mview') > -1 || + _.indexOf(parents, 'mview') > -1 || + _.indexOf(parents, 'view') > -1) { + return false; + } else { + return true; + } + } + }); + } + + return pgBrowser.Nodes['column']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/macros/privilege.macros b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/macros/privilege.macros new file mode 100644 index 000000000..7eafd60f0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/macros/privilege.macros @@ -0,0 +1,13 @@ +{% macro APPLY(conn, schema_name, table_object, column_object, role, privs, with_grant_privs) -%} +{% if privs %} +GRANT {% for p in privs %}{% if loop.index != 1 %}, {% endif %}{{p}}({{conn|qtIdent(column_object)}}){% endfor %} + ON {{ conn|qtIdent(schema_name, table_object) }} TO {{ conn|qtIdent(role) }}; +{% endif %} +{% if with_grant_privs %} +GRANT {% for p in with_grant_privs %}{% if loop.index != 1 %}, {% endif %}{{p}}({{conn|qtIdent(column_object)}}){% endfor %} + ON {{ conn|qtIdent(schema_name, table_object) }} TO {{ conn|qtIdent(role) }} WITH GRANT OPTION; +{% endif %} +{%- endmacro %} +{% macro RESETALL(conn, schema_name, table_object, column_object, role) -%} +REVOKE ALL({{ conn|qtIdent(column_object) }}) ON {{ conn|qtIdent(schema_name, table_object) }} FROM {{ conn|qtIdent(role) }}; +{%- endmacro %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/macros/security.macros b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/macros/security.macros new file mode 100644 index 000000000..39587c32b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/column/templates/column/macros/security.macros @@ -0,0 +1,6 @@ +{% macro APPLY(conn, type, schema_name, parent_object, child_object, provider, label) -%} +SECURITY LABEL FOR {{ conn|qtIdent(provider) }} ON {{ type }} {{ conn|qtIdent(schema_name, parent_object, child_object) }} IS {{ label|qtLiteral }}; +{%- endmacro %} +{% macro DROP(conn, type, schema_name, parent_object, child_object, provider) -%} +SECURITY LABEL FOR {{ conn|qtIdent(provider) }} ON {{ type }} {{ conn|qtIdent(schema_name, parent_object, child_object) }} IS NULL; +{%- endmacro %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/__init__.py new file mode 100644 index 000000000..475c71f58 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/__init__.py @@ -0,0 +1,133 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements Constraint Node""" + +from flask.ext.babel import gettext +from flask import render_template, make_response +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases as database +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response +from .type import ConstraintRegistry + + +class ConstraintsModule(CollectionNodeModule): + """ + class ConstraintsModule(CollectionNodeModule) + + A module class for Constraint node derived from CollectionNodeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the ConstraintsModule and it's base module. + + * get_nodes(gid, sid, did) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for constraint node, when any of the database node is + initialized. + """ + + NODE_TYPE = 'constraints' + COLLECTION_LABEL = gettext("Constraints") + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + super(ConstraintsModule, self).__init__(*args, **kwargs) + + def get_nodes(self, gid, sid, did, scid, tid): + """ + Generate the collection node + """ + yield self.generate_browser_collection_node(tid) + + @property + def script_load(self): + """ + Load the module script for constraints, when any of the table node is + initialized. + """ + return database.DatabaseModule.NODE_TYPE + +blueprint = ConstraintsModule(__name__) + + +@blueprint.route('/nodes//////') +def nodes(**kwargs): + """ + Returns all constraint as a tree node. + + Args: + **kwargs: + + Returns: + + """ + + cmd = {"cmd": "nodes"} + res = [] + for name in ConstraintRegistry.registry: + module = (ConstraintRegistry.registry[name])['nodeview'] + view = module(**cmd) + res = res + view.get_nodes(**kwargs) + + return make_json_response( + data=res, + status=200 + ) + + +@blueprint.route('/obj//////') +def proplist(**kwargs): + """ + Returns all constraint with properties. + Args: + **kwargs: + + Returns: + + """ + + cmd = {"cmd": "obj"} + res = [] + for name in ConstraintRegistry.registry: + module = (ConstraintRegistry.registry[name])['nodeview'] + view = module(**cmd) + res = res + view.get_node_list(**kwargs) + + return ajax_response( + response=res, + status=200 + ) + + +@blueprint.route('/module.js') +def module_js(): + """ + This property defines whether javascript exists for this node. + + """ + return make_response( + render_template( + "constraints/js/constraints.js", + _=gettext, + constraints=[ + (ConstraintRegistry.registry[n])['blueprint'].NODE_TYPE \ + for n in ConstraintRegistry.registry + ] + ), + 200, {'Content-Type': 'application/x-javascript'} + ) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/__init__.py new file mode 100644 index 000000000..f2eb81bd0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/__init__.py @@ -0,0 +1,833 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements the Check Constraint Module.""" + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext as _ +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases as database +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps +from pgadmin.browser.server_groups.servers.databases.schemas.tables.constraints.type \ + import ConstraintRegistry, ConstraintTypeModule + + +class CheckConstraintModule(CollectionNodeModule): + """ + class CheckConstraintModule(CollectionNodeModule): + + This class represents The Check Constraint Module. + + Methods: + ------- + * __init__(*args, **kwargs) + - Initialize the Check Constraint Module. + + * get_nodes(gid, sid, did, scid) + - Generate the Check Constraint collection node. + + * node_inode(gid, sid, did, scid) + - Returns Check Constraint node as leaf node. + + * script_load() + - Load the module script for the Check Constraint, when any of the + Check node is initialized. + """ + NODE_TYPE = 'check_constraints' + COLLECTION_LABEL = _("Check Constraints") + + def __init__(self, *args, **kwargs): + super(CheckConstraintModule, self).__init__(*args, **kwargs) + self.min_ver = None + self.max_ver = None + + def get_nodes(self, gid, sid, did, scid, doid): + """ + Generate the Check Constraint collection node. + """ + yield self.generate_browser_collection_node(doid) + + @property + def node_inode(self): + """ + Returns Check Constraint node as leaf node. + """ + return False + + @property + def script_load(self): + """ + Load the module script for the Check Constraint, when any of the + Check node is initialized. + """ + return database.DatabaseModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + return [ + render_template( + "check_constraint/css/check_constraint.css", + node_type=self.node_type + ) + ] + + +blueprint = CheckConstraintModule(__name__) + + +class CheckConstraintView(PGChildNodeView): + """ + class CheckConstraintView(PGChildNodeView): + + This class inherits PGChildNodeView to get the different routes for + the module. + + The class is responsible to Create, Read, Update and Delete operations for + the Check Constraint. + + Methods: + ------- + + * module_js(): + - Load JS file (check-constraints.js) for this module. + + * check_precondition(f): + - Works as a decorator. + - Checks database connection status. + - Attach connection object and template path. + + * list(gid, sid, did, scid, doid): + - List the Check Constraints. + + * nodes(gid, sid, did, scid): + - Returns all the Check Constraints to generate Nodes in the browser. + + * properties(gid, sid, did, scid, doid): + - Returns the Check Constraint properties. + + * create(gid, sid, did, scid): + - Creates a new Check Constraint object. + + * update(gid, sid, did, scid, doid): + - Updates the Check Constraint object. + + * delete(gid, sid, did, scid, doid): + - Drops the Check Constraint object. + + * sql(gid, sid, did, scid, doid=None): + - Returns the SQL for the Check Constraint object. + + * msql(gid, sid, did, scid, doid=None): + - Returns the modified SQL. + + * get_sql(gid, sid, data, scid, tid=None): + - Generates the SQL statements to create/update the Check Constraint. + object. + + * dependents(gid, sid, did, scid, tid, cid): + - Returns the dependents for the Check Constraint object. + + * dependencies(gid, sid, did, scid, tid, cid): + - Returns the dependencies for the Check Constraint object. + + * validate_check_constraint(gid, sid, did, scid, tid, cid): + - Validate check constraint. + """ + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [ + {'type': 'int', 'id': 'cid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'delete': [{'delete': 'delete'}], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'validate': [{'get': 'validate_check_constraint'}], + }) + + def module_js(self): + """ + Load JS file (check_constraint.js) for this module. + """ + return make_response( + render_template( + "check_constraint/js/check_constraint.js", + _=_ + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + Works as a decorator. + Checks database connection status. + Attach connection object and template path. + """ + @wraps(f) + def wrap(*args, **kwargs): + self = args[0] + driver = get_driver(PG_DEFAULT_DRIVER) + self.manager = driver.connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + self.qtIdent = driver.qtIdent + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + _("Connection to the server has been lost!") + ) + + ver = self.manager.version + + # we will set template path for sql scripts + if ver >= 90200: + self.template_path = 'check_constraint/sql/9.2_plus' + elif ver >= 90100: + self.template_path = 'check_constraint/sql/9.1_plus' + + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=kwargs['tid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + self.schema = rset['rows'][0]['schema'] + self.table = rset['rows'][0]['table'] + + return f(*args, **kwargs) + + return wrap + + def end_transaction(self): + """ + End database transaction. + Returns: + + """ + SQL = "END;" + self.conn.execute_scalar(SQL) + + @check_precondition + def list(self, gid, sid, did, scid, tid, cid=None): + """ + List the Check Constraints. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check Id + """ + try: + res = self.get_node_list(gid, sid, did, scid, tid, cid) + return ajax_response( + response=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_node_list(self, gid, sid, did, scid, tid, cid=None): + """ + This function returns all check constraints + nodes within that collection as a list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Cehck constraint ID + + Returns: + + """ + SQL = render_template("/".join([self.template_path, 'properties.sql']), + tid=tid) + status, res = self.conn.execute_dict(SQL) + + return res['rows'] + + @check_precondition + def nodes(self, gid, sid, did, scid, tid, cid=None): + """ + Returns all the Check Constraints. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check constraint Id. + """ + try: + res = self.get_nodes(gid, sid, did, scid, tid, cid) + return make_json_response( + data=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_nodes(self, gid, sid, did, scid, tid, cid=None): + """ + This function returns all event check constraint as a list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Check constraint ID + + Returns: + + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), + tid=tid) + status, rset = self.conn.execute_2darray(SQL) + + for row in rset['rows']: + if "convalidated" in row and row["convalidated"]: + icon = "icon-check_constraints_bad" + valid = False + else: + icon = "icon-check_constraints" + valid = True + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon=icon, + valid=valid + )) + return res + + @check_precondition + def properties(self, gid, sid, did, scid, tid, cid): + """ + Returns the Check Constraints property. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Check Id + cid: Check Constraint Id + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, cid=cid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + return ajax_response( + response=data, + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid, tid, cid=None): + """ + This function will create a primary key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Check constraint ID + + Returns: + + """ + required_args = ['consrc'] + + data = request.form if request.form else json.loads(request.data.decode()) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + for arg in required_args: + if arg not in data or data[arg] == '': + return make_json_response( + status=400, + success=0, + errormsg=_( + "Couldn't find the required parameter (%s)." % arg + ) + ) + + data['schema'] = self.schema + data['table'] = self.table + try: + if 'name' not in data or data['name'] == "": + SQL = "BEGIN;" + # Start transaction. + status, res = self.conn.execute_scalar(SQL) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + # The below SQL will execute CREATE DDL only + SQL = render_template( + "/".join([self.template_path, 'create.sql']), + data=data + ) + + status, msg = self.conn.execute_scalar(SQL) + if not status: + self.end_transaction() + return internal_server_error(errormsg=msg) + + if 'name' not in data or data['name'] == "": + sql = render_template( + "/".join([self.template_path, + 'get_oid_with_transaction.sql'], + ), + tid=tid) + + status, res = self.conn.execute_dict(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + self.end_transaction() + + data['name'] = res['rows'][0]['name'] + + else: + sql = render_template("/".join([self.template_path, 'get_oid.sql']), + tid=tid, + name=data['name']) + status, res = self.conn.execute_dict(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + if "convalidated" in res['rows'][0] and res['rows'][0]["convalidated"]: + icon = "icon-check_constraints_bad" + valid = False + else: + icon = "icon-check_constraints" + valid = True + + sql = render_template("/".join([self.template_path, 'alter.sql']), + data=data, + conn=self.conn) + sql = sql.strip('\n').strip(' ') + + if sql != '': + status, result = self.conn.execute_scalar(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=result) + + return jsonify( + node=self.blueprint.generate_browser_node( + res['rows'][0]['oid'], + tid, + data['name'], + icon=icon, + valid=valid + ) + ) + + except Exception as e: + self.end_transaction() + return make_json_response( + status=400, + success=0, + errormsg=e + ) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, cid): + """ + Drops the Check Constraint object. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Check Id + cid: Check Constraint Id + """ + try: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, cid=cid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + + SQL = render_template("/".join([self.template_path, + 'delete.sql']), + data=data) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=_("Check Constraint dropped"), + data={ + 'id': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, tid, cid): + """ + Updates the Check Constraint object. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check Constraint Id + """ + data = request.form if request.form else json.loads(request.data.decode()) + + try: + data['schema'] = self.schema + data['table'] = self.table + + SQL = self.get_sql(gid, sid, data, scid, tid, cid) + SQL = SQL.strip('\n').strip(' ') + if SQL != "": + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + sql = render_template("/".join([self.template_path, 'get_name.sql']), + cid=cid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if "convalidated" in res['rows'][0] and res['rows'][0]["convalidated"]: + icon = 'icon-check_constraints_)bad' + valid = False + else: + icon = 'icon-check_constraints' + valid = True + + return make_json_response( + success=1, + info="Check Constraint updated", + data={ + 'id': cid, + 'tid': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did, + 'icon': icon, + 'val id': valid + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': cid, + 'tid': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def sql(self, gid, sid, did, scid, tid, cid=None): + """ + Returns the SQL for the Check Constraint object. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check Constraint Id + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, cid=cid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data['schema'] = self.schema + data['table'] = self.table + + SQL = render_template("/".join([self.template_path, + 'create.sql']), + data=data) + SQL += "\n" + SQL += render_template( + "/".join([self.template_path, 'alter.sql']), + data=data) + + sql_header = "-- Constraint: {0}\n\n-- ".format(data['name']) + + sql_header += render_template( + "/".join([self.template_path, 'delete.sql']), + data=data) + sql_header += "\n" + + SQL = sql_header + SQL + + return ajax_response(response=SQL) + + @check_precondition + def msql(self, gid, sid, did, scid, tid, cid=None): + """ + Returns the modified SQL. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check Constraint Id + + Returns: + Check Constraint object in json format. + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + data['schema'] = self.schema + data['table'] = self.table + try: + sql = self.get_sql(gid, sid, data, scid, tid, cid) + sql = sql.strip('\n').strip(' ') + + return make_json_response( + data=sql, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_sql(self, gid, sid, data, scid, tid, cid=None): + """ + Generates the SQL statements to create/update the Check Constraint. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check Constraint Id + """ + try: + if cid is not None: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, cid=cid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return False, internal_server_error(errormsg=res) + + old_data = res['rows'][0] + required_args = ['name'] + for arg in required_args: + if arg not in data: + data[arg] = old_data[arg] + + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data, conn=self.conn + ) + else: + required_args = ['consrc'] + + for arg in required_args: + if arg not in data: + return _('-- definition incomplete') + elif isinstance(data[arg], list) and len(data[arg]) < 1: + return _('-- definition incomplete') + + SQL = render_template("/".join([self.template_path, + 'create.sql']), + data=data) + SQL += "\n" + SQL += render_template("/".join([self.template_path, 'alter.sql']), + data=data) + + return SQL + except Exception as e: + return False, internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, tid, cid): + """ + This function get the dependents and return ajax response + for the Check Constraint node. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check Constraint Id + """ + dependents_result = self.get_dependents(self.conn, cid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid, cid): + """ + This function get the dependencies and return ajax response + for the Check Constraint node. + + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check Constraint Id + """ + dependencies_result = self.get_dependencies(self.conn, cid) + return ajax_response( + response=dependencies_result, + status=200 + ) + + @check_precondition + def validate_check_constraint(self, gid, sid, did, scid, tid, cid): + """ + Validate check constraint. + Args: + gid: Server Group Id + sid: Server Id + did: Database Id + scid: Schema Id + tid: Table Id + cid: Check Constraint Id + + Returns: + + """ + data = {} + try: + data['schema'] = self.schema + data['table'] = self.table + sql = render_template("/".join([self.template_path, 'get_name.sql']), cid=cid) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + data['name'] = res + sql = render_template("/".join([self.template_path, 'validate.sql']), data=data) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=_("Check constraint updated."), + data={ + 'id': cid, + 'tid': tid, + 'scid': scid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + +constraint = ConstraintRegistry( + 'check_constraint', CheckConstraintModule, CheckConstraintView + ) +CheckConstraintView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/img/check-constraints-bad.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/img/check-constraints-bad.png new file mode 100644 index 0000000000000000000000000000000000000000..32a045b8fafdc08640d53b2a86b1dcabcb0fe0fd GIT binary patch literal 579 zcmV-J0=)f+P)Px$AW%$HMR1Z9#Q*@REHmdHAm<<;=p`lTDl6$LE15$|nM6yxJ3#6&F}^)Qx|oNbX2U?n+AUOH1!e zOz%xi?@dncPEXKRS?^Cz#amtQQBm+xQt(q#@KaRqR8;U)Rq<9<@mE*YVq(`~V)0vB z+GS<)VPW%PV%=$Jag!JHXlV6qZS`($_HuH3pdDu=8uhpjJ) zvN4agH<6_L$tcPam4X*+~mi`^K{Glc|xhhkN@C%g?4vVBM R$bbL<002ovPDHLkV1min0OSAw literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/img/check-constraints.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/img/check-constraints.png new file mode 100644 index 0000000000000000000000000000000000000000..9d1d2a061c7948168d7b1c2474d769b31709f1cf GIT binary patch literal 406 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}X@F0NE08{VY2nhHc^eMaU%j`d zaI$#+HuEKC{pKEZKYn>h(+aIMH^MjGjcs3}f9Cqyt&fvx7AT*)xv^`L;hf{Hi_iP4 zxgK%&V?q65_4c*;8}G%;JMMY(Kax(GBB{1sVaw}AvZrIGp!P$!N3x%0i@c>zzm|{ T)b!9bKn)C@u6{1-oD!M<=4O^k literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/css/check_constraint.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/css/check_constraint.css new file mode 100644 index 000000000..0691868a7 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/css/check_constraint.css @@ -0,0 +1,15 @@ +.icon-check_bad, .icon-check_constraints_bad { + background-image: url('{{ url_for('NODE-%s.static' % node_type, filename='img/check-constraints-bad.png' )}}') !important; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + +.icon-check, .icon-check_constraints { + background-image: url('{{ url_for('NODE-%s.static' % node_type, filename='img/check-constraints.png' )}}') !important; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/js/check_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/js/check_constraint.js new file mode 100644 index 000000000..afc5ba939 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/templates/check_constraint/js/check_constraint.js @@ -0,0 +1,216 @@ +// Check Constraint Module: Node +define( + [ + 'jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', + 'alertify', 'pgadmin.browser.collection' + ], +function($, _, S, pgAdmin, pgBrowser, Alertify) { + + // Check Constraint Node + if (!pgBrowser.Nodes['check_constraints']) { + pgAdmin.Browser.Nodes['check_constraints'] = pgBrowser.Node.extend({ + type: 'check_constraints', + label: '{{ _('Check') }}', + collection_type: 'coll-constraints', + sqlAlterHelp: 'ddl-alter.html', + sqlCreateHelp: 'ddl-constraints.html', + hasSQL: true, + hasDepends: true, + parent_type: ['table'], + Init: function() { + // Avoid mulitple registration of menus + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_check_constraints_on_coll', node: 'coll-constraints', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 5, label: '{{ _('Check...') }}', + icon: 'wcTabIcon icon-check_constraints', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'validate_check_constraint', node: 'check_constraints', module: this, + applies: ['object', 'context'], callback: 'validate_check_constraint', + category: 'validate', priority: 4, label: '{{ _('Validate check constraint') }}', + icon: 'fa fa-link', enable : 'is_not_valid', data: {action: 'edit', check: true} + } + ]); + + }, + is_not_valid: function(itemData, item, data) { + if (this.canCreate(itemData, item, data)) { + return (itemData && !itemData.valid); + } else { + return false; + } + }, + callbacks: { + validate_check_constraint: function(args) { + var input = args || {}; + obj = this, + t = pgBrowser.tree, + i = input.item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined; + + if (!d) { + return false; + } + var data = d; + $.ajax({ + url: obj.generate_url(i, 'validate', d, true), + type:'GET', + success: function(res) { + if (res.success == 1) { + Alertify.success("{{ _('" + res.info + "') }}"); + t.removeIcon(i); + data.valid = true; + data.icon = 'icon-check_constraints'; + t.addIcon(i, {icon: data.icon}); + setTimeout(function() {t.deselect(i);}, 10); + setTimeout(function() {t.select(i);}, 100); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + msg = S('{{ _(' + err.errormsg + ')}}').value(); + Alertify.error("{{ _('" + err.errormsg + "') }}"); + } + } catch (e) {} + t.unload(i); + } + }); + + return false; + } + }, + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + oid: undefined, + description: undefined, + consrc: undefined, + connoinherit: undefined, + convalidated: true + }, + // Check Constraint Schema + schema: [{ + id: 'name', label: '{{ _('Name') }}', type:'text', cell:'string', + disabled: 'isDisabled' + },{ + id: 'oid', label:'{{ _('OID') }}', cell: 'string', + type: 'text' , mode: ['properties'] + },{ + id: 'comment', label: '{{ _('Comment') }}', type: 'multiline', cell: + 'string', mode: ['properties', 'create', 'edit'], + deps:['name'], disabled:function(m) { + var name = m.get('name'); + if (!(name && name != '')) { + setTimeout(function(){ + m.set('comment', null); + },10); + return true; + } else { + return false; + } + } + },{ + id: 'consrc', label: '{{ _('Check') }}', type: 'multiline', cell: + 'string', group: '{{ _('Definition') }}', mode: ['properties', + 'create', 'edit'], disabled: function(m) { + return ((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); + }, editable: false + },{ + id: 'connoinherit', label: '{{ _('No Inherit') }}', type: + 'switch', cell: 'boolean', group: '{{ _('Definition') }}', mode: + ['properties', 'create', 'edit'], min_version: 90200, + disabled: function(m) { + return ((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); + } + },{ + id: 'convalidated', label: "{{ _("Don't validate") }}", type: 'switch', cell: + 'boolean', group: '{{ _('Definition') }}', min_version: 90200, + disabled: function(m) { + if ((_.isFunction(m.isNew) && !m.isNew()) || + (_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid')))) { + + return !m.get("convalidated"); + } else { + return false; + } + }, + mode: ['properties', 'create', 'edit'] + }], + // Client Side Validation + validate: function() { + var err = {}, + errmsg; + + if (_.isUndefined(this.get('consrc')) || String(this.get('consrc')).replace(/^\s+|\s+$/g, '') == '') { + err['consrc'] = '{{ _('Check can not be empty!') }}'; + errmsg = errmsg || err['consrc']; + } + + this.errorModel.clear().set(err); + + if (_.size(err)) { + this.trigger('on-status', {msg: errmsg}); + return errmsg; + } + + return null; + + }, + isDisabled: function(m){ + if ((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || + (_.isFunction(m.isNew) && !m.isNew())) { + var server = (this.node_info || m.top.node_info).server; + if (server.version < 90200) + { + return true; + } + } + return false; + } + }), + // Below function will enable right click menu for creating check constraint. + canCreate: function(itemData, item, data) { + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData, parents = []; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to c reate table + if (_.indexOf(['schema'], d._type) > -1) + return true; + parents.push(d._type); + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // If node is under catalog then do not allow 'create' menu + if (_.indexOf(parents, 'catalog') > -1) { + return false; + } else { + return true; + } + } + }); + + } + + return pgBrowser.Nodes['check_constraints']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py new file mode 100644 index 000000000..cf599e5ff --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py @@ -0,0 +1,820 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements Exclusion constraint Node""" + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext as _ +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +import pgadmin.browser.server_groups.servers.databases as database +from functools import wraps +from pgadmin.browser.server_groups.servers.databases.schemas.tables.constraints.type \ + import ConstraintRegistry, ConstraintTypeModule + + +class ExclusionConstraintModule(ConstraintTypeModule): + """ + class ForeignKeyConstraintModule(CollectionNodeModule) + + A module class for Exclusion constraint node derived from ConstraintTypeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the ForeignKeyConstraintModule and it's base module. + + * get_nodes(gid, sid, did) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for language, when any of the database node is + initialized. + """ + + NODE_TYPE = 'exclusion_constraint' + COLLECTION_LABEL = _("Foreign Keys") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the ForeignKeyConstraintModule and it's base module. + + Args: + *args: + **kwargs: + + Returns: + + """ + self.min_ver = None + self.max_ver = None + super(ExclusionConstraintModule, self).__init__(*args, **kwargs) + + def get_nodes(self, gid, sid, did, scid, tid): + """ + Generate the collection node + """ + pass + + @property + def node_inode(self): + """ + Override this property to make the node a leaf node. + + Returns: False as this is the leaf node + """ + return False + + @property + def script_load(self): + """ + Load the module script for exclusion_constraint, when any of the table node is + initialized. + + Returns: node type of the server module. + """ + return database.DatabaseModule.NODE_TYPE + +blueprint = ExclusionConstraintModule(__name__) + + +class ExclusionConstraintView(PGChildNodeView): + """ + class ExclusionConstraintView(PGChildNodeView) + + A view class for Exclusion constraint node derived from PGChildNodeView. This class is + responsible for all the stuff related to view like creating, updating Exclusion constraint + node, showing properties, showing sql in sql pane. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the ForeignKeyConstraintView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * end_transaction() + - To end any existing database transaction. + + * list() + - This function returns Exclusion constraint nodes within that + collection as http response. + + * get_list() + - This function is used to list all the language nodes within that collection + and return list of Exclusion constraint nodes. + + * nodes() + - This function returns child node within that collection. + Here return all Exclusion constraint node as http response. + + * get_nodes() + - returns all Exclusion constraint nodes' list. + + * properties() + - This function will show the properties of the selected Exclusion. + + * update() + - This function will update the data for the selected Exclusion. + + * msql() + - This function is used to return modified SQL for the selected Exclusion. + + * get_sql() + - This function will generate sql from model data. + + * sql(): + - This function will generate sql to show it in sql pane for the selected Exclusion. + + * get_access_methods(): + - Returns access methods for exclusion constraint. + + * get_oper_class(): + - Returns operator classes for selected access method. + + * get_operator(): + - Returns operators for selected column. + + """ + + node_type = 'exclusion_constraint' + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [{'type': 'int', 'id': 'exid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'delete': [{'delete': 'delete'}], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'module.js': [{}, {}, {'get': 'module_js'}] + }) + + def module_js(self): + """ + This property defines (if javascript) exists for this node. + Override this property for your own logic. + """ + return make_response( + render_template( + "exclusion_constraint/js/exclusion_constraint.js", + _=_ + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + _( + "Connection to the server has been lost!" + ) + ) + + ver = self.manager.version + + if ver >= 90200: + self.template_path = 'exclusion_constraint/sql/9.2_plus' + elif ver >= 90100: + self.template_path = 'exclusion_constraint/sql/9.1_plus' + + # We need parent's name eg table name and schema name + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=kwargs['tid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + self.schema = row['schema'] + self.table = row['table'] + return f(*args, **kwargs) + + return wrap + + def end_transaction(self): + SQL = render_template( + "/".join([self.template_path, 'end.sql'])) + # End transaction if any. + self.conn.execute_scalar(SQL) + + @check_precondition + def properties(self, gid, sid, did, scid, tid, exid=None): + """ + This function is used to list all the Exclusion constraint + nodes within that collection. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + try: + sql = render_template("/".join([self.template_path, 'properties.sql']), + tid=tid, cid=exid) + + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + result = res['rows'][0] + + sql = render_template( + "/".join([self.template_path, 'get_constraint_cols.sql']), + cid=exid, + colcnt=result['indnatts']) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + for row in res['rows']: + if row['options'] & 1: + order = False + nulls_order = True if (row['options'] & 2) else False + else: + order = True + nulls_order = True if (row['options'] & 2) else False + + columns.append({"column": row['coldef'].strip('"'), + "oper_class": row['opcname'], + "order": order, + "nulls_order": nulls_order, + "operator": row['oprname'], + "col_type": row['datatype'] + }) + + result['columns'] = columns + + return ajax_response( + response=result, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def list(self, gid, sid, did, scid, tid, exid=None): + """ + This function returns all exclusion constraints + nodes within that collection as a http response. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + try: + res = self.get_node_list(gid, sid, did, scid, tid, exid) + return ajax_response( + response=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_node_list(self, gid, sid, did, scid, tid, exid=None): + """ + This function returns all exclusion constraints + nodes within that collection as a list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid) + status, res = self.conn.execute_dict(SQL) + + return res['rows'] + + @check_precondition + def nodes(self, gid, sid, did, scid, tid, exid=None): + """ + This function returns all Exclusion constraint nodes as a + http response. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + try: + res = self.get_nodes(gid, sid, did, scid, tid, exid) + return make_json_response( + data=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_nodes(self, gid, sid, did, scid, tid, exid=None): + """ + This function returns all Exclusion constraint nodes as a list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), + tid=tid) + status, rset = self.conn.execute_2darray(SQL) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon="icon-exclusion_constraint" + )) + return res + + @check_precondition + def create(self, gid, sid, did, scid, tid, exid=None): + """ + This function will create a Exclusion constraint. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + required_args = ['columns'] + + data = request.form if request.form else json.loads(request.data.decode()) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + for arg in required_args: + if arg not in data: + return make_json_response( + status=400, + success=0, + errormsg=_( + "Couldn't find required parameter (%s)." % str(arg) + ) + ) + elif isinstance(data[arg], list) and len(data[arg]) < 1: + return make_json_response( + status=400, + success=0, + errormsg=_( + "Couldn't find required parameter (%s)." % str(arg) + ) + ) + + data['schema'] = self.schema + data['table'] = self.table + try: + if 'name' not in data or data['name'] == "": + SQL = render_template( + "/".join([self.template_path, 'begin.sql'])) + # Start transaction. + status, res = self.conn.execute_scalar(SQL) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + # The below SQL will execute CREATE DDL only + SQL = render_template( + "/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + if 'name' not in data or data['name'] == "": + sql = render_template( + "/".join([self.template_path, + 'get_oid_with_transaction.sql']), + tid=tid) + + status, res = self.conn.execute_dict(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + self.end_transaction() + + data['name'] = res['rows'][0]['name'] + + else: + sql = render_template("/".join([self.template_path, 'get_oid.sql']), name=data['name']) + status, res = self.conn.execute_dict(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + if 'name' in data and data['name'] != '': + sql = render_template("/".join([self.template_path, 'alter.sql']), data=data, conn=self.conn) + sql = sql.strip('\n').strip(' ') + + if sql != '': + status, result = self.conn.execute_scalar(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=result) + + return jsonify( + node=self.blueprint.generate_browser_node( + res['rows'][0]['oid'], + tid, + data['name'], + icon="icon-exclusion_constraint" + ) + ) + + except Exception as e: + self.end_transaction() + + return make_json_response( + status=400, + success=0, + errormsg=e + ) + + @check_precondition + def update(self, gid, sid, did, scid, tid, exid=None): + """ + This function will update the data for the selected + Exclusion constraint. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + data = request.form if request.form else json.loads(request.data.decode()) + + try: + data['schema'] = self.schema + data['table'] = self.table + sql = self.get_sql(data, tid, exid) + sql = sql.strip('\n').strip(' ') + if sql != "": + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + sql = render_template("/".join([self.template_path, 'get_oid.sql']), name=data['name']) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Exclusion constraint updated", + data={ + 'id': res['rows'][0]['oid'], + 'tid': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did, + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': exid, + 'tid': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, exid=None): + """ + This function will delete an existing Exclusion. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + # Below code will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + try: + sql = render_template("/".join([self.template_path, 'get_name.sql']), + cid=exid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data['schema'] = self.schema + data['table'] = self.table + + sql = render_template("/".join([self.template_path, 'delete.sql']), + data=data, + cascade=cascade) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=_("Exclusion constraint dropped."), + data={ + 'id': exid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, tid, exid=None): + """ + This function returns modified SQL for the selected + Exclusion constraint. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + data['schema'] = self.schema + data['table'] = self.table + try: + sql = self.get_sql(data, tid, exid) + sql = sql.strip('\n').strip(' ') + + return make_json_response( + data=sql, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_sql(self, data, tid, exid=None): + """ + This function will generate sql from model data. + + Args: + data: Contains the data of the selected Exclusion constraint. + tid: Table ID. + exid: Exclusion constraint ID + + Returns: + + """ + if exid is not None: + sql = render_template("/".join([self.template_path, 'properties.sql']), + tid=tid, + cid=exid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + required_args = ['name'] + for arg in required_args: + if arg not in data: + data[arg] = old_data[arg] + + sql = render_template("/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data) + else: + required_args = ['columns'] + + for arg in required_args: + if arg not in data: + return _('-- definition incomplete') + elif isinstance(data[arg], list) and len(data[arg]) < 1: + return _('-- definition incomplete') + + sql = render_template("/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn) + sql += "\n" + sql += render_template("/".join([self.template_path, 'alter.sql']), + data=data, conn=self.conn) + + return sql + + @check_precondition + def sql(self, gid, sid, did, scid, tid, exid=None): + """ + This function generates sql to show in the sql pane for the selected + Exclusion constraint. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + exid: Exclusion constraint ID + + Returns: + + """ + try: + SQL = render_template( + "/".join([self.template_path, 'properties.sql']), + tid=tid, conn=self.conn, cid=exid) + status, result = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=result) + + data = result['rows'][0] + data['schema'] = self.schema + data['table'] = self.table + + sql = render_template( + "/".join([self.template_path, 'get_constraint_cols.sql']), + cid=exid, + colcnt=data['indnatts']) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + for row in res['rows']: + if row['options'] & 1: + order = False + nulls_order = True if (row['options'] & 2) else False + else: + order = True + nulls_order = True if (row['options'] & 2) else False + + columns.append({"column": row['coldef'].strip('"'), + "oper_class": row['opcname'], + "order": order, + "nulls_order": nulls_order, + "operator": row['oprname'] + }) + + data['columns'] = columns + + if not data['amname'] or data['amname'] == '': + data['amname'] = 'btree' + + SQL = render_template( + "/".join([self.template_path, 'create.sql']), data=data) + SQL += "\n" + SQL += render_template( + "/".join([self.template_path, 'alter.sql']), + data=data, conn=self.conn) + + sql_header = "-- Constraint: {0}\n\n-- ".format(data['name']) + + sql_header += render_template( + "/".join([self.template_path, 'delete.sql']), + data=data) + sql_header += "\n" + + SQL = sql_header + SQL + + return ajax_response(response=SQL) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + +constraint = ConstraintRegistry( + 'exclusion_constraint', ExclusionConstraintModule, ExclusionConstraintView + ) +ExclusionConstraintView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/img/exclusion_constraint.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/static/img/exclusion_constraint.png new file mode 100644 index 0000000000000000000000000000000000000000..bd62eef6410d9315857d2e6d246770e8b65b8f47 GIT binary patch literal 725 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47y|-)LR^8|wi}= z_isLZP1<@%wQ`Qjk=K%*nNgG09JqAnY+~Zm($d$dsZT2^E~KQ~e)?Lsd6{J1L`ko# z8QYH^XzhAZSorVPuP+-md|tBT-`~HFYif>6nkJPuNuqp?YIOD56Ib^8`rV6+d^K<0 zzkmPUtXQ!pGFqZ!hGg3swe*g4XK!wIaz5qe_HfFSrwbQu^AC`0S|K@Qk7Uk-*~hPM zEhsq<82I=1?;BI6UhV1m_vg>S@bIm*jgk|0NG?2Of9R!T^@7Cpr%znEwc5~Vm%sly zUEQ5NKC5l*c3rqFx8Q{2nrn7@9?A45&&c`3`Z{FOOoV;9Hdxfs~=ES4#-&V>Eak7aXC5R07H*Y2Gbdx45l?XZ+LiQ zWMmY~)Ws(}c=qt=V{riyAu&Nw;pq&V9$ucOPnhyv$&ZhB;@K9Qt1bmBw`i*--4qPDWM%7n?> 0) { + _.each(columns.models, function(m) { + var col = m.get('name'); + if(!_.isUndefined(col) && !_.isNull(col)) { + added_columns_from_tables.push({ + label: col, value: col, image:'icon-column' + }); + col_types.push({name:col, type:m.get('cltype')}); + } + }); + } + // Set the values in to options so that user can select + this.field.set('options', added_columns_from_tables); + self.field.set('col_types', col_types); + }, + remove: function () { + if(self.model.handler) { + tableCols = self.model.top.get('columns'); + this.stopListening(tableCols, 'remove' , this.removeColumn); + this.stopListening(tableCols, 'change:name' , this.resetColOptions); + this.stopListening(tableCols, 'change:cltype' , this.resetColOptions); + + Backform.Select2Control.prototype.remove.apply(this, arguments); + + } else { + Backform.NodeListByNameControl.prototype.remove.apply(this, arguments); + } + }, + template: _.template([ + '
', + ' ', + '
'].join("\n")) + }), + transform: function(rows) { + // This will only get called in case of NodeListByNameControl. + + var that = this, + node = that.field.get('schema_node'), + res = [], + col_types = [], + filter = that.field.get('filter') || function() { return true; }; + + filter = filter.bind(that); + + _.each(rows, function(r) { + if (filter(r)) { + var l = (_.isFunction(node['node_label']) ? + (node['node_label']).apply(node, [r, that.model, that]) : + r.label), + image = (_.isFunction(node['node_image']) ? + (node['node_image']).apply( + node, [r, that.model, that] + ) : + (node['node_image'] || ('icon-' + node.type))); + res.push({ + 'value': r.label, + 'image': image, + 'label': l + }); + col_types.push({name:r.label, type:r.datatype}); + } + }); + self.field.set('col_types', col_types); + return res; + }, + canAdd: function(m) { + return !((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); + }, + select2: { + allowClear: false, width: 'style', + placeholder: 'Select column' + }, first_empty: !self.model.isNew(), + disabled: function(m) { + return !_.isUndefined(self.model.get('oid')); + } + }], + headerDefaults = {column: null}, + + gridCols = ['column', 'oper_class', 'order', 'nulls_order', 'operator']; + + self.headerData = new (Backbone.Model.extend({ + defaults: headerDefaults, + schema: headerSchema + }))({}); + + var headerGroups = Backform.generateViewSchema( + self.field.get('node_info'), self.headerData, 'create', + node, self.field.get('node_data') + ), + fields = []; + + _.each(headerGroups, function(o) { + fields = fields.concat(o.fields); + }); + + self.headerFields = new Backform.Fields(fields); + self.gridSchema = Backform.generateGridColumnsFromModel( + self.field.get('node_info'), self.field.get('model'), 'edit', gridCols, self.field.get('schema_node') + ); + + self.controls = []; + self.listenTo(self.headerData, "change", self.headerDataChanged); + self.listenTo(self.headerData, "select2", self.headerDataChanged); + self.listenTo(self.collection, "add", self.onAddorRemoveColumns); + self.listenTo(self.collection, "remove", self.onAddorRemoveColumns); + }, + + generateHeader: function(data) { + var header = [ + '
', + '
', + '
', + '
', + ' ', + '
', + '
', + '
', + '
', + '
', + '
', + '
',].join("\n") + + _.extend(data, { + column_label: '{{ _('Column')}}', + add_label: '{{ _('ADD')}}' + }); + + var self = this, + headerTmpl = _.template(header), + $header = $(headerTmpl(data)), + controls = this.controls; + + this.headerFields.each(function(field) { + var control = new (field.get("control"))({ + field: field, + model: self.headerData + }); + + $header.find('div[header="' + field.get('name') + '"]').append( + control.render().$el + ); + + controls.push(control); + }); + + // We should not show add but in properties mode + if (data.mode == 'properties') { + $header.find("button.add").remove(); + } + + self.$header = $header; + + return $header; + }, + + events: _.extend( + {}, Backform.UniqueColCollectionControl.prototype.events, + {'click button.add': 'addColumns'} + ), + + showGridControl: function(data) { + + var self = this, + titleTmpl = _.template("
"), + $gridBody = + $("
").append( + titleTmpl({label: data.label}) + ); + + $gridBody.append(self.generateHeader(data)); + + var gridColumns = _.clone(this.gridSchema.columns); + + // Insert Delete Cell into Grid + if (data.disabled == false && data.canDelete) { + gridColumns.unshift({ + name: "pg-backform-delete", label: "", + cell: Backgrid.Extension.DeleteCell, + editable: false, cell_priority: -1 + }); + } + + if (self.grid) { + self.grid.remove(); + self.grid.null; + } + // Initialize a new Grid instance + var grid = self.grid = new Backgrid.Grid({ + columns: gridColumns, + collection: self.collection, + className: "backgrid table-bordered" + }); + self.$grid = grid.render().$el; + + $gridBody.append(self.$grid); + + setTimeout(function() { + self.headerData.set({ + 'column': self.$header.find( + 'div[header="column"] select' + ).val() + }, {silent:true} + ); + }, 10); + + // Render node grid + return $gridBody; + }, + + headerDataChanged: function() { + var self = this, val, + data = this.headerData.toJSON(), + inSelected = false, + checkVars = ['column']; + + if (!self.$header) { + return; + } + + if (self.control_data.canAdd) { + self.collection.each(function(m) { + if (!inSelected) { + _.each(checkVars, function(v) { + if (!inSelected) { + val = m.get(v); + inSelected = (( + (_.isUndefined(val) || _.isNull(val)) && + (_.isUndefined(data[v]) || _.isNull(data[v])) + ) || + (val == data[v])); + } + }); + } + }); + } + else { + inSelected = true; + } + + self.$header.find('button.add').prop('disabled', inSelected); + }, + + addColumns: function(ev) { + ev.preventDefault(); + var self = this, + column = self.headerData.get('column'); + + if (!column || column == '') { + return false; + } + + var coll = self.model.get(self.field.get('name')), + m = new (self.field.get('model'))( + self.headerData.toJSON(), { + silent: true, top: self.model.top, + collection: coll, handler: coll + }), + col_types =self.field.get('col_types') || []; + + for(var i=0; i < col_types.length; i++) { + var col_type = col_types[i]; + if (col_type['name'] == m.get('column')) { + m.set({'col_type':col_type['type']}); + break; + } + } + + coll.add(m); + + var idx = coll.indexOf(m); + + // idx may not be always > -1 because our UniqueColCollection may + // remove 'm' if duplicate value found. + if (idx > -1) { + self.$grid.find('.new').removeClass('new'); + + var newRow = self.grid.body.rows[idx].$el; + + newRow.addClass("new"); + $(newRow).pgMakeVisible('backform-tab'); + } else { + delete m; + } + + return false; + }, + + onAddorRemoveColumns: function() { + var self = this; + + // Wait for collection to be updated before checking for the button to be + // enabled, or not. + setTimeout(function() { + self.collection.trigger('pgadmin:columns:updated', self.collection); + self.headerDataChanged(); + }, 10); + }, + + remove: function() { + /* + * Stop listening the events registered by this control. + */ + this.stopListening(this.headerData, "change", this.headerDataChanged); + this.listenTo(this.headerData, "select2", this.headerDataChanged); + this.listenTo(this.collection, "remove", this.onAddorRemoveColumns); + + // Remove header controls. + _.each(this.controls, function(controls) { + controls.remove(); + }); + + ExclusionConstraintColumnControl.__super__.remove.apply(this, arguments); + + // Remove the header model + delete (this.headerData); + + } + }); + + // Extend the browser's node class for exclusion constraint node + if (!pgBrowser.Nodes['exclusion_constraint']) { + pgAdmin.Browser.Nodes['exclusion_constraint'] = pgBrowser.Node.extend({ + type: 'exclusion_constraint', + label: '{{ _('Exclusion constraint') }}', + collection_type: 'coll-constraints', + sqlAlterHelp: 'ddl-alter.html', + sqlCreateHelp: 'ddl-constraints.html', + hasSQL: true, + parent_type: 'table', + canDrop: true, + canDropCascade: true, + hasDepends: true, + Init: function() { + /* Avoid multiple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_exclusion_constraint_on_coll', node: 'coll-constraints', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Exclusion constraint...') }}', + icon: 'wcTabIcon icon-exclusion_constraint', data: {action: 'create', check: true}, + enable: 'canCreate' + }]); + }, + is_not_valid: function(node) { + return (node && !node.valid); + }, + // Define the model for exclusion constraint node + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + oid: undefined, + comment: undefined, + spcname: "pg_default", + amname: "gist", + fillfactor: undefined, + condeferrable: undefined, + condeferred: undefined, + columns: [] + }, + + // Define the schema for the exclusion constraint node + schema: [{ + id: 'name', label: '{{ _('Name') }}', type: 'text', + mode: ['properties', 'create', 'edit'], editable: true, + },{ + id: 'oid', label:'{{ _('OID') }}', cell: 'string', + type: 'text' , mode: ['properties'] + },{ + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + deps:['name'], disabled:function(m) { + var name = m.get('name'); + if (!(name && name != '')) { + setTimeout(function(){ + m.set('comment', null); + },10); + return true; + } else { + return false; + } + } + },{ + id: 'spcname', label: '{{ _('Tablespace') }}', + type: 'text', group: '{{ _('Definition') }}', + control: 'node-list-by-name', node: 'tablespace', + select2:{allowClear:false}, + filter: function(m) { + // Don't show pg_global tablespace in selection. + if (m.label == "pg_global") return false; + else return true; + } + },{ + id: 'amname', label: '{{ _('Access method') }}', + type: 'text', group: '{{ _('Definition') }}', + url:"get_access_methods", node: 'table', + control: Backform.NodeAjaxOptionsControl.extend({ + // When access method changes we need to clear columns collection + onChange: function() { + Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments); + var self = this, + // current access method + current_am = self.model.get('amname'), + // previous access method + previous_am = self.model.previous('amname'), + column_collection = self.model.get('columns'); + + if (column_collection.length > 0 && current_am != previous_am) { + var msg = '{{ _('Changing access method will clear columns collection') }}'; + Alertify.confirm(msg, function (e) { + // User clicks Ok, lets clear collection + column_collection.reset(); + setTimeout(function() { + column_collection.trigger('pgadmin:columns:updated', column_collection); + }, 10); + + }, function() { + // User clicks Cancel set previous value again in combo box + setTimeout(function(){ + self.model.set('amname', previous_am); + }, 10); + }); + } + } + }), + select2:{allowClear:true}, + disabled: function(m) { + return ((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); + } + },{ + id: 'fillfactor', label: '{{ _('Fill factor') }}', + type: 'int', group: '{{ _('Definition') }}', allowNull: true + },{ + id: 'condeferrable', label: '{{ _('Deferrable') }}', + type: 'switch', group: '{{ _('Definition') }}', deps: ['index'], + disabled: function(m) { + return ((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); + } + },{ + id: 'condeferred', label: '{{ _('Deferred') }}', + type: 'switch', group: '{{ _('Definition') }}', + deps: ['condeferrable'], + disabled: function(m) { + if((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())) { + return true; + } + + // Disable if condeferred is false or unselected. + if(m.get('condeferrable') == true) { + return false; + } else { + setTimeout(function(){ + m.set('condeferred', false); + },10); + return true; + } + } + },{ + id: 'constraint', label:'{{ _('Constraint') }}', cell: 'string', + type: 'multiline', mode: ['create', 'edit'], editable: false, + group: '{{ _('Definition') }}', disabled: function(m) { + return ((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); + } + },{ + id: 'columns', label: '{{ _('Columns') }}', + type: 'collection', group: '{{ _('Columns') }}', disabled: false, + deps:['amname'], canDelete: true, editable: false, + canAdd: function(m) { + // We can't update columns of existing exclusion constraint. + return !((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); + }, + control: ExclusionConstraintColumnControl, + model: ExclusionConstraintColumnModel, + disabled: function(m) { + return ((_.has(m, 'handler') && + !_.isUndefined(m.handler) && + !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); + }, + cell: Backgrid.StringCell.extend({ + initialize: function() { + Backgrid.StringCell.prototype.initialize.apply(this, arguments); + var self = this; + // Do not listen for any event(s) for existing constraint. + if (_.isUndefined(self.model.get('oid'))) { + var tableCols = self.model.top.get('columns'); + + self.listenTo(tableCols, 'remove' , self.removeColumn); + self.listenTo(tableCols, 'change:name', self.resetColOptions); + self.listenTo(tableCols, 'change:cltype', self.removeColumnWithType); + } + + this.model.get('columns').on('pgadmin:columns:updated', function() { + self.render.apply(self); + }); + }, + removeColumnWithType: function(m){ + var self = this, + cols = self.model.get('columns'), + removedCols = cols.where( + {col_type: m.previous('cltype')} + ); + + cols.remove(removedCols); + setTimeout(function () { + self.render(); + }, 10); + + setTimeout(function () { + constraints = self.model.top.get("exclude_constraint"); + var removed = []; + constraints.each(function(constraint) { + if (constraint.get("columns").length == 0) { + removed.push(constraint); + } + }); + constraints.remove(removed); + },100); + }, + removeColumn: function(m){ + var self = this, + removedCols = self.model.get('columns').where( + {column: m.get('name')} + ); + + self.model.get('columns').remove(removedCols); + setTimeout(function () { + self.render(); + }, 10); + + setTimeout(function () { + constraints = self.model.top.get("exclude_constraint"); + var removed = []; + constraints.each(function(constraint) { + if (constraint.get("columns").length == 0) { + removed.push(constraint); + } + }); + constraints.remove(removed); + },100); + }, + resetColOptions : function(m) { + var self = this, + updatedCols = self.model.get('columns').where( + {"column": m.previous('name')} + ); + + if (updatedCols.length > 0) { + /* + * Table column name has changed so update + * column name in foreign key as well. + */ + updatedCols[0].set( + {"column": m.get('name')}); + } + + setTimeout(function () { + self.render(); + }, 10); + }, + formatter: { + fromRaw: function (rawValue, model) { + return rawValue.pluck("column").toString(); + }, + toRaw: function (val, model) { + return val; + } + }, + render: function() { + return Backgrid.StringCell.prototype.render.apply(this, arguments); + }, + remove: function() { + var tableCols = this.model.top.get('columns'), + cols = this.model.get('columns'); + if (cols) { + cols.off('pgadmin:columns:updated'); + } + + this.stopListening(tableCols, 'remove' , self.removeColumn); + this.stopListening(tableCols, 'change:name' , self.resetColOptions); + this.stopListening(tableCols, 'change:cltype' , self.removeColumnWithType); + + Backgrid.StringCell.prototype.remove.apply(this, arguments); + } + }), + }], + validate: function() { + this.errorModel.clear(); + var columns = this.get('columns'); + if ((_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) { + var msg = '{{ _('Please specify columns for Exclude constraint.') }}'; + this.errorModel.set('columns', msg); + return msg; + } + + return null; + } + }), + + canCreate: function(itemData, item, data) { + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData, parents = []; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to create table + if (_.indexOf(['schema'], d._type) > -1) + return true; + parents.push(d._type); + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // If node is under catalog then do not allow 'create' menu + if (_.indexOf(parents, 'catalog') > -1) { + return false; + } else { + return true; + } + } + }); + } + + return pgBrowser.Nodes['exclusion_constraint']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/__init__.py new file mode 100644 index 000000000..ce05031f1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/__init__.py @@ -0,0 +1,1072 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements Foreign key constraint Node""" + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext as _ +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +import pgadmin.browser.server_groups.servers.databases as database +from functools import wraps +from pgadmin.browser.server_groups.servers.databases.schemas.tables.constraints.type \ + import ConstraintRegistry, ConstraintTypeModule + + +class ForeignKeyConstraintModule(ConstraintTypeModule): + """ + class ForeignKeyConstraintModule(CollectionNodeModule) + + A module class for Foreign key constraint node derived from ConstraintTypeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the ForeignKeyConstraintModule and it's base module. + + * get_nodes(gid, sid, did) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for language, when any of the database node is + initialized. + """ + + NODE_TYPE = 'foreign_key' + COLLECTION_LABEL = _("Foreign Keys") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the ForeignKeyConstraintModule and it's base module. + + Args: + *args: + **kwargs: + + Returns: + + """ + self.min_ver = None + self.max_ver = None + super(ForeignKeyConstraintModule, self).__init__(*args, **kwargs) + + def get_nodes(self, gid, sid, did, scid, tid): + """ + Generate the collection node + """ + pass + + @property + def node_inode(self): + """ + Override this property to make the node a leaf node. + + Returns: False as this is the leaf node + """ + return False + + @property + def script_load(self): + """ + Load the module script for foreign_key, when any of the table node is + initialized. + + Returns: node type of the server module. + """ + return database.DatabaseModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=_ + ), + render_template( + "foreign_key/css/foreign_key.css", + node_type=self.node_type, + _=_ + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + +blueprint = ForeignKeyConstraintModule(__name__) + + +class ForeignKeyConstraintView(PGChildNodeView): + """ + class ForeignKeyConstraintView(PGChildNodeView) + + A view class for Foreign key constraint node derived from PGChildNodeView. This class is + responsible for all the stuff related to view like creating, updating Foreign key constraint + node, showing properties, showing sql in sql pane. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the ForeignKeyConstraintView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * list() + - This function returns foreign key constraint nodes within that + collection as http response. + + * get_list() + - This function is used to list all the language nodes within that collection + and return list of foreign key constraint nodes. + + * nodes() + - This function returns child node within that collection. + Here return all foreign key constraint node as http response. + + * get_nodes() + - returns all foreign key constraint nodes' list. + + * properties() + - This function will show the properties of the selected foreign key. + + * update() + - This function will update the data for the selected foreign key. + + * msql() + - This function is used to return modified SQL for the selected foreign key. + + * get_sql() + - This function will generate sql from model data. + + * sql(): + - This function will generate sql to show it in sql pane for the selected foreign key. + + * get_indices(): + - This function returns indices for current table. + + """ + + node_type = 'foreign_key' + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [{'type': 'int', 'id': 'fkid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'delete': [{'delete': 'delete'}], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'indices': [{}, {'get': 'get_indices'}], + 'validate': [{'get': 'validate_foreign_key'}], + 'get_coveringindex': [{}, {'get': 'get_coveringindex'}] + }) + + def module_js(self): + """ + This property defines (if javascript) exists for this node. + Override this property for your own logic. + """ + return make_response( + render_template( + "foreign_key/js/foreign_key.js", + _=_ + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + _( + "Connection to the server has been lost!" + ) + ) + + self.template_path = 'foreign_key/sql' + # We need parent's name eg table name and schema name + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=kwargs['tid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + self.schema = row['schema'] + self.table = row['table'] + return f(*args, **kwargs) + + return wrap + + def end_transaction(self): + SQL = render_template( + "/".join([self.template_path, 'end.sql'])) + # End transaction if any. + self.conn.execute_scalar(SQL) + + @check_precondition + def properties(self, gid, sid, did, scid, tid, fkid=None): + """ + This function is used to list all the foreign key + nodes within that collection. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + try: + sql = render_template("/".join([self.template_path, 'properties.sql']), + tid=tid, cid=fkid) + + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + result = res['rows'][0] + + sql = render_template("/".join([self.template_path, + 'get_constraint_cols.sql']), + tid=tid, + keys=zip(result['confkey'], result['conkey']), + confrelid=result['confrelid']) + + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + cols = [] + for row in res['rows']: + columns.append({"local_column": row['conattname'], + "references": result['confrelid'], + "referenced": row['confattname']}) + cols.append(row['conattname']) + + result['columns'] = columns + + if fkid: + coveringindex = self.search_coveringindex(tid, cols) + result['coveringindex'] = coveringindex + if coveringindex: + result['autoindex'] = True + result['hasindex'] = True + else: + result['autoindex'] = False + result['hasindex'] = False + + return ajax_response( + response=result, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def list(self, gid, sid, did, scid, tid, fkid=None): + """ + This function returns all foreign keys + nodes within that collection as a http response. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + try: + res = self.get_node_list(gid, sid, did, scid, tid, fkid) + return ajax_response( + response=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_node_list(self, gid, sid, did, scid, tid, fkid=None): + """ + This function returns all foreign keys + nodes within that collection as a list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid) + status, res = self.conn.execute_dict(SQL) + + return res['rows'] + + @check_precondition + def nodes(self, gid, sid, did, scid, tid, fkid=None): + """ + This function returns all event trigger nodes as a + http response. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + try: + res = self.get_nodes(gid, sid, did, scid, tid, fkid) + return make_json_response( + data=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_nodes(self, gid, sid, did, scid, tid, fkid=None): + """ + This function returns all event trigger nodes as a list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), + tid=tid) + status, rset = self.conn.execute_2darray(SQL) + + for row in rset['rows']: + if row["convalidated"]: + icon = "icon-foreign_key_no_validate" + valid = False + else: + icon = "icon-foreign_key" + valid = True + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon=icon, + valid=valid + )) + return res + + @check_precondition + def create(self, gid, sid, did, scid, tid, fkid=None): + """ + This function will create a foreign key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + required_args = ['columns'] + + data = request.form if request.form else json.loads(request.data.decode()) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + for arg in required_args: + if arg not in data: + return make_json_response( + status=400, + success=0, + errormsg=_( + "Couldn't find required parameter (%s)." % str(arg) + ) + ) + elif isinstance(data[arg], list) and len(data[arg]) < 1: + return make_json_response( + status=400, + success=0, + errormsg=_( + "Couldn't find required parameter (%s)." % str(arg) + ) + ) + + data['schema'] = self.schema + data['table'] = self.table + try: + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=data['columns'][0]['references']) + status, res = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + data['remote_schema'] = res['rows'][0]['schema'] + data['remote_table'] = res['rows'][0]['table'] + + if 'name' not in data or data['name'] == "": + SQL = render_template( + "/".join([self.template_path, 'begin.sql'])) + # Start transaction. + status, res = self.conn.execute_scalar(SQL) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + # The below SQL will execute CREATE DDL only + SQL = render_template( + "/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + if 'name' not in data or data['name'] == "": + sql = render_template( + "/".join([self.template_path, + 'get_oid_with_transaction.sql']), + tid=tid) + + status, res = self.conn.execute_dict(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + self.end_transaction() + + data['name'] = res['rows'][0]['name'] + + else: + sql = render_template("/".join([self.template_path, 'get_oid.sql']), name=data['name']) + status, res = self.conn.execute_dict(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + if res['rows'][0]["convalidated"]: + icon = "icon-foreign_key_no_validate" + valid = False + else: + icon = "icon-foreign_key" + valid = True + + sql = render_template("/".join([self.template_path, 'alter.sql']), data=data, conn=self.conn) + sql = sql.strip('\n').strip(' ') + + if sql != '': + status, result = self.conn.execute_scalar(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=result) + + if data['autoindex']: + sql = render_template( + "/".join([self.template_path, 'create_index.sql']), + data=data, conn=self.conn) + sql = sql.strip('\n').strip(' ') + + if sql != '': + status, idx_res = self.conn.execute_scalar(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=idx_res) + + return jsonify( + node=self.blueprint.generate_browser_node( + res['rows'][0]['oid'], + tid, + data['name'], + valid=valid, + icon=icon + ) + ) + + except Exception as e: + self.end_transaction() + return make_json_response( + status=400, + success=0, + errormsg=e + ) + + @check_precondition + def update(self, gid, sid, did, scid, tid, fkid=None): + """ + This function will update the data for the selected + foreign key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + data = request.form if request.form else json.loads(request.data.decode()) + + try: + data['schema'] = self.schema + data['table'] = self.table + sql = self.get_sql(data, tid, fkid) + sql = sql.strip('\n').strip(' ') + if sql != "": + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + sql = render_template("/".join([self.template_path, 'get_oid.sql']), + tid=tid, + name=data['name']) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if res['rows'][0]["convalidated"]: + icon = "icon-foreign_key_no_validate" + valid = False + else: + icon = "icon-foreign_key" + valid = True + + return make_json_response( + success=1, + info="Foreign key updated", + data={ + 'id': res['rows'][0]['oid'], + 'tid': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did, + 'icon':icon, + 'valid':valid + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': fkid, + 'tid': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, fkid=None): + """ + This function will delete an existing foreign key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + # Below code will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + try: + sql = render_template("/".join([self.template_path, 'get_name.sql']), fkid=fkid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data['schema'] = self.schema + data['table'] = self.table + + sql = render_template("/".join([self.template_path, 'delete.sql']), data=data, cascade=cascade) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=_("Foreign key dropped."), + data={ + 'id': fkid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, tid, fkid=None): + """ + This function returns modified SQL for the selected + foreign key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + data['schema'] = self.schema + data['table'] = self.table + try: + sql = self.get_sql(data, tid, fkid) + sql = sql.strip('\n').strip(' ') + + return make_json_response( + data=sql, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_sql(self, data, tid, fkid=None): + """ + This function will generate sql from model data. + + Args: + data: Contains the data of the selected foreign key constraint. + tid: Table ID. + fkid: Foreign key constraint ID + + Returns: + + """ + if fkid is not None: + sql = render_template("/".join([self.template_path, 'properties.sql']), tid=tid, cid=fkid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + required_args = ['name'] + for arg in required_args: + if arg not in data: + data[arg] = old_data[arg] + + sql = render_template("/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data) + + if 'autoindex' in data and data['autoindex'] and\ + ('coveringindex' in data and + data['coveringindex'] != ''): + + col_sql = render_template("/".join([self.template_path, + 'get_constraint_cols.sql']), + tid=tid, + keys=zip(old_data['confkey'], old_data['conkey']), + confrelid=old_data['confrelid']) + + status, res = self.conn.execute_dict(col_sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + for row in res['rows']: + columns.append({"local_column": row['conattname'], + "references": old_data['confrelid'], + "referenced": row['confattname']}) + + data['columns'] = columns + + sql += render_template( + "/".join([self.template_path, 'create_index.sql']), + data=data, conn=self.conn) + else: + required_args = ['columns'] + + for arg in required_args: + if arg not in data: + return _('-- definition incomplete') + elif isinstance(data[arg], list) and len(data[arg]) < 1: + return _('-- definition incomplete') + + if data['autoindex'] and ('coveringindex' not in data or + data['coveringindex'] == ''): + return _('-- definition incomplete') + + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=data['columns'][0]['references']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + data['remote_schema'] = rset['rows'][0]['schema'] + data['remote_table'] = rset['rows'][0]['table'] + + sql = render_template("/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn) + sql += "\n" + sql += render_template("/".join([self.template_path, 'alter.sql']), + data=data, conn=self.conn) + + if data['autoindex']: + sql += render_template( + "/".join([self.template_path, 'create_index.sql']), + data=data, conn=self.conn) + return sql + + @check_precondition + def sql(self, gid, sid, did, scid, tid, fkid=None): + """ + This function generates sql to show in the sql pane for the selected + foreign key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + fkid: Foreign key constraint ID + + Returns: + + """ + try: + SQL = render_template( + "/".join([self.template_path, 'properties.sql']), + tid=tid, conn=self.conn, cid=fkid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data['schema'] = self.schema + data['table'] = self.table + + sql = render_template("/".join([self.template_path, + 'get_constraint_cols.sql']), + tid=tid, + keys=zip(data['confkey'], data['conkey']), + confrelid=data['confrelid']) + + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + for row in res['rows']: + columns.append({"local_column": row['conattname'], + "references": data['confrelid'], + "referenced": row['confattname']}) + + data['columns'] = columns + + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=data['columns'][0]['references']) + status, res = self.conn.execute_2darray(SQL) + + if not status: + return internal_server_error(errormsg=res) + + data['remote_schema'] = res['rows'][0]['schema'] + data['remote_table'] = res['rows'][0]['table'] + + SQL = render_template( + "/".join([self.template_path, 'create.sql']), data=data) + SQL += "\n" + SQL += render_template( + "/".join([self.template_path, 'alter.sql']), + data=data, conn=self.conn) + + sql_header = "-- Constraint: {0}\n\n-- ".format(data['name']) + + sql_header += render_template( + "/".join([self.template_path, 'delete.sql']), + data=data) + sql_header += "\n" + + SQL = sql_header + SQL + + return ajax_response(response=SQL) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, tid, fkid=None): + """ + This function gets the dependents and returns an ajax response + for the event trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + etid: Event trigger ID + """ + dependents_result = self.get_dependents(self.conn, fkid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid, fkid=None): + """ + This function gets the dependencies and returns an ajax response + for the event trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + etid: Event trigger ID + """ + dependencies_result = self.get_dependencies(self.conn, fkid) + return ajax_response( + response=dependencies_result, + status=200 + ) + + @check_precondition + def validate_foreign_key(self, gid, sid, did, scid, tid, fkid): + """ + + Args: + gid: + sid: + did: + scid: + tid: + fkid: + + Returns: + + """ + data = {} + try: + data['schema'] = self.schema + data['table'] = self.table + sql = render_template("/".join([self.template_path, 'get_name.sql']), fkid=fkid) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + data['name'] = res + sql = render_template("/".join([self.template_path, 'validate.sql']), data=data) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=_("Foreign key updated."), + data={ + 'id': fkid, + 'tid': tid, + 'scid': scid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def search_coveringindex(self, tid, cols): + """ + + Args: + tid: Table id + cols: column list + + Returns: + + """ + + cols = set(cols) + SQL = render_template("/".join([self.template_path, + 'get_constraints.sql']), + tid=tid) + status, constraints = self.conn.execute_dict(SQL) + + if not status: + raise Exception(constraints) + + for costrnt in constraints['rows']: + + sql = render_template( + "/".join([self.template_path, 'get_cols.sql']), + cid=costrnt['oid'], + colcnt=costrnt['indnatts']) + status, rest = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=rest) + + indexcols = set() + for r in rest['rows']: + indexcols.add(r['column'].strip('"')) + + if len(cols - indexcols) == len(indexcols - cols) == 0: + return costrnt["idxname"] + + return None + + @check_precondition + def get_coveringindex(self, gid, sid, did, scid, tid=None): + """ + + Args: + gid: + sid: + did: + scid: + tid: + + Returns: + + """ + + data = request.args if request.args else None + index = None + try: + if data and 'cols' in data: + cols = set(json.loads(data['cols'])) + index = self.search_coveringindex(tid, cols) + + return make_json_response( + data=index, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + +constraint = ConstraintRegistry( + 'foreign_key', ForeignKeyConstraintModule, ForeignKeyConstraintView + ) +ForeignKeyConstraintView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key.png new file mode 100644 index 0000000000000000000000000000000000000000..b3605500439db72c2c43879abb61a24c956e1bf5 GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UAm`Z~Df*BafCZDwc@shmVT^JZv z^(q?yd7K3vk;OpT1B~5HX4?T7@t!V@ArhC96BcmYxO{&<`^@{hp1!(zcCE*!Xt{ek z>#~zoS#lB*UhMz1;M236-R60Bchvnn_1J5|fxo9_r|aE`sZD=6Ra!x`dfT1-l|Ns# zc?qz5Y;4QhSCW{_ZI`Xd&M@z_Ipcx{mt%ldsg}4#l%yncptAuDUumo!Wsy4AQFoS3~H9d3Uf LtDnm{r-UW|7b#?7 literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key_no_validate.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key_no_validate.png new file mode 100644 index 0000000000000000000000000000000000000000..975561bcf7237d5e99e9d42ff705db67f1bb2fdb GIT binary patch literal 618 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47?}cmLR^8|J$v@__xDejFyZ2b zi$8z-{QB`zYisM4En6<0I(I!O=_mukh49ENQx_g}_r9B%`S9VxT^qNb3Jm#HUVhl! zdv;s@UR$S+;o(=WUVZT3K}|{3x3aQtMMYop^7dFed<+YF@9+Q4!}I?A`&DI?UvqQ6 zWMq6!O8O8Q`rZ#H?s56juX-#d5r&6_u0x^(Hn`SXVl9ln*C z`XLx7?)%QoZIzC3Utiz$4V$k4O$ZKt@8k2%)%A^?{c8)0-OjEbf`Zz;|9=Z=GFcI(xm;(te?;`dmTbt&{T+H?Q>ONi$u%pUW#W=hVD!YnKOx z0%J*#UoeBivm0qZ4rhT!WHFHT0Ash4*>*t2d`}n05Q)ocd!Gt51&FlhcQmc(yxVnG z_QqY=_y4tuT#tzt)X$mu_uM%>#d^WRQWlF}KCf&v?%p7fwDq9h|E9y52U)it=ayGp zV=v&MA>$~rv4TnDgeT|961JN$^VB39^+a5!oSK^T_Oj!Ts>L_67GHkr`19O1cITdB zlVAFHub7p*F(z^E%~veD|1!GIIVYasa7c9fZ)Shnk3kvpt3v;?HQy7Q(A;;*1?W`O z64!{5l*E!$tK_0oAjM#0U}&goV61Ck5n^a$WoT|?V5x0jW@TV-`Pqsj6b-rgDVb@N WxHT-=EpQL0fx*+&&t;ucLK6V@6Aj$} literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/css/foreign_key.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/css/foreign_key.css new file mode 100644 index 000000000..aeaff11c1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/css/foreign_key.css @@ -0,0 +1,12 @@ +.icon-foreign_key { + background-image: url('{{ url_for('NODE-foreign_key.static', filename='img/foreign_key.png') }}') !important; + background-repeat: no-repeat; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + +.icon-foreign_key_no_validate { + background-image: url('{{ url_for('NODE-foreign_key.static', filename='img/foreign_key_no_validate.png') }}') !important; + border-radius: 10px +} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/js/foreign_key.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/js/foreign_key.js new file mode 100644 index 000000000..7da458a3a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/templates/foreign_key/js/foreign_key.js @@ -0,0 +1,1072 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'], +function($, _, S, pgAdmin, pgBrowser, Alertify) { + + var formatNode = function(opt) { + if (!opt.id) { + return opt.text; + } + + var optimage = $(opt.element).data('image'); + + if(!optimage){ + return opt.text; + } else { + return $( + '' + opt.text + '' + ); + } + }, + headerSelectControlTemplate = _.template([ + '
', + ' ', + '
'].join("\n")); + + var ForeignKeyColumnModel = pgBrowser.Node.Model.extend({ + defaults: { + local_column: undefined, + references: undefined, + referenced: undefined + }, + schema: [{ + id: 'local_column', label:'Local', type:'text', editable: false, + cellHeaderClasses: 'width_percent_50', cell:'string', + headerCell: Backgrid.Extension.CustomHeaderCell + },{ + id: 'referenced', label:'Referenced', type: 'text', editable: false, + cell:'string', cellHeaderClasses: 'width_percent_50', + headerCell: Backgrid.Extension.CustomHeaderCell + }] + }); + + var ForeignKeyColumnControl = Backform.ForeignKeyColumnControl = + Backform.UniqueColCollectionControl.extend({ + + initialize: function(opts) { + Backform.UniqueColCollectionControl.prototype.initialize.apply( + this, arguments + ); + + var self = this, + node = 'foreign_key', + headerSchema = [{ + id: 'local_column', label:'', type:'text', + node: 'column', control: Backform.NodeListByNameControl.extend({ + initialize: function() { + // Here we will decide if we need to call URL + // Or fetch the data from parent columns collection + if(self.model.handler) { + Backform.Select2Control.prototype.initialize.apply(this, arguments); + // Do not listen for any event(s) for existing constraint. + if (_.isUndefined(self.model.get('oid'))) { + var tableCols = self.model.top.get('columns'); + this.listenTo(tableCols, 'remove' , this.removeColumn); + this.listenTo(tableCols, 'change:name', this.resetColOptions); + } + + this.custom_options(); + } else { + Backform.NodeListByNameControl.prototype.initialize.apply(this, arguments); + } + }, + removeColumn: function (m) { + var that = this; + setTimeout(function () { + that.custom_options(); + that.render.apply(that); + }, 50); + }, + resetColOptions: function(m) { + var that = this; + + if (m.previous('name') == self.headerData.get('local_column')) { + /* + * Table column name has changed so update + * column name in foreign key as well. + */ + self.headerData.set( + {"local_column": m.get('name')}); + self.headerDataChanged(); + } + + setTimeout(function () { + that.custom_options(); + that.render.apply(that); + }, 50); + }, + custom_options: function() { + // We will add all the columns entered by user in table model + var columns = self.model.top.get('columns'), + added_columns_from_tables = []; + + if (columns.length > 0) { + _.each(columns.models, function(m) { + var col = m.get('name'); + if(!_.isUndefined(col) && !_.isNull(col)) { + added_columns_from_tables.push( + {label: col, value: col, image:'icon-column'} + ); + } + }); + } + // Set the values in to options so that user can select + this.field.set('options', added_columns_from_tables); + }, + template: headerSelectControlTemplate, + remove: function () { + if(self.model.handler) { + var tableCols = self.model.top.get('columns'); + this.stopListening(tableCols, 'remove' , this.removeColumn); + this.stopListening(tableCols, 'change:name' , this.resetColOptions); + + Backform.Select2Control.prototype.remove.apply(this, arguments); + + } else { + Backform.NodeListByNameControl.prototype.remove.apply(this, arguments); + } + } + }), + select2: { + allowClear: false, width: 'style', + placeholder: 'Select column' + }, first_empty: !_.isUndefined(self.model.get('oid')), + version_compatible: self.field.get('version_compatible'), + disabled: function(m) { + return !_.isUndefined(self.model.get('oid')); + } + },{ + id: 'references', label:'', type: 'text', cache_level: 'server', + select2: { + allowClear: false, width: 'style', + placeholder: 'Select foreign table', + }, first_empty: true, + control: Backform.NodeListByNameControl.extend({ + formatter: Backform.ControlFormatter, + template: headerSelectControlTemplate + }), + url: 'all_tables', node: 'table', + version_compatible: self.field.get('version_compatible'), + disabled: function(m) { + return !_.isUndefined(self.model.get('oid')); + }, + transform: function(rows) { + var res = []; + _.each(rows, function(r) { + res.push({ + 'value': r.value, + 'image': "icon-table", + 'label': r.label + }); + }); + return res; + } + },{ + id: 'referenced', label:'', type: 'text', cache_level: 'server', + transform: function(rows) { + var res = []; + _.each(rows, function(r) { + res.push({ + 'value': r.name, + 'image': 'icon-column', + 'label': r.name + }); + }); + return res; + }, + control: Backform.Select2Control.extend({ + formatter: Backform.ControlFormatter, + template: headerSelectControlTemplate, + render: function() { + var self = this, + url = self.field.get('url') || self.defaults.url, + m = self.model, + tid = m.get('references'); + + // Clear any existing value before setting new options. + m.set(self.field.get('name'), null, {silent: true}); + + if (url && !_.isUndefined(tid) && !_.isNull(tid) && tid != '') { + var node = this.field.get('schema_node'), + node_info = this.field.get('node_info'), + full_url = node.generate_url.apply( + node, [ + null, url, this.field.get('node_data'), + this.field.get('url_with_id') || false, node_info + ]), + data = []; + + if (this.field.get('version_compatible')) { + m.trigger('pgadmin:view:fetching', m, self.field); + $.ajax({ + async: false, + data : {tid:tid}, + url: full_url, + success: function(res) { + data = res.data; + }, + error: function() { + m.trigger('pgadmin:view:fetch:error', m, self.field); + } + }); + m.trigger('pgadmin:view:fetched', m, self.field); + } + /* + * Transform the data + */ + transform = this.field.get('transform') || self.defaults.transform; + if (transform && _.isFunction(transform)) { + // We will transform the data later, when rendering. + // It will allow us to generate different data based on the + // dependencies. + self.field.set('options', transform.bind(self, data)); + } else { + self.field.set('options', data); + } + } else { + self.field.set('options', []); + } + Backform.Select2Control.prototype.render.apply(this, arguments); + return this; + } + }), url: 'get_columns', first_empty: true, + select2: { + width: "style", + placeholder: 'Select column', + templateResult: formatNode, + templateSelection: formatNode + }, + deps:['references'], node: 'table', + version_compatible: self.field.get('version_compatible'), + disabled: function(m) { + return !_.isUndefined(self.model.get('oid')); + } + }], + headerDefaults = {local_column: null, + references: null, + referenced:null}, + gridCols = ['local_column', 'references', 'referenced']; + + if ((!self.model.isNew() && _.isUndefined(self.model.handler)) || + (_.has(self.model, 'handler') && + !_.isUndefined(self.model.handler) && + !_.isUndefined(self.model.get('oid')))) { + var column = self.collection.first(); + if (column) { + headerDefaults["references"] = column.get("references"); + } + } + + self.headerData = new (Backbone.Model.extend({ + defaults: headerDefaults, + schema: headerSchema + }))({}); + + var headerGroups = Backform.generateViewSchema( + self.field.get('node_info'), self.headerData, 'create', + node, self.field.get('node_data') + ), + fields = []; + + _.each(headerGroups, function(o) { + fields = fields.concat(o.fields); + }); + + self.headerFields = new Backform.Fields(fields); + self.gridSchema = Backform.generateGridColumnsFromModel( + //null, ForeignKeyColumnModel, 'edit', gridCols + self.field.get('node_info'), self.field.get('model'), 'edit', + gridCols, self.field.get('schema_node') + ); + + self.controls = []; + self.listenTo(self.headerData, "change", self.headerDataChanged); + self.listenTo(self.headerData, "select2", self.headerDataChanged); + self.listenTo(self.collection, "add", self.onAddorRemoveColumns); + self.listenTo(self.collection, "remove", self.onAddorRemoveColumns); + }, + + generateHeader: function(data) { + var header = [ + '
', + '
', + '
', + '
', + ' ', + '
', + '
', + '
', + '
', + '
', + '
', + '
', + ' ', + '
', + '
', + '
', + '
', + '
', + ' ', + '
', + '
', + '
', + '
', + '
',].join("\n") + + _.extend(data, { + column_label: '{{ _('Local column')}}', + add_label: '{{ _('ADD')}}', + references_label: '{{ _('References')}}', + referenced_label: '{{ _('Referencing')}}' + }); + + var self = this, + headerTmpl = _.template(header), + $header = $(headerTmpl(data)), + controls = this.controls; + + this.headerFields.each(function(field) { + var control = new (field.get("control"))({ + field: field, + model: self.headerData + }); + + $header.find('div[header="' + field.get('name') + '"]').append( + control.render().$el + ); + + controls.push(control); + }); + + // We should not show add but in properties mode + if (data.mode == 'properties') { + $header.find("button.add").remove(); + } + + self.$header = $header; + + return $header; + }, + + events: _.extend( + {}, Backform.UniqueColCollectionControl.prototype.events, + {'click button.add': 'addColumns'} + ), + + showGridControl: function(data) { + + var self = this, + titleTmpl = _.template([ + "
", + "", + "
"].join("\n")), + $gridBody = + $("
").append( + titleTmpl({label: data.label}) + ); + + $gridBody.append(self.generateHeader(data)); + + var gridSchema = _.clone(this.gridSchema); + + // Insert Delete Cell into Grid + if (data.disabled == false && data.canDelete) { + gridSchema.columns.unshift({ + name: "pg-backform-delete", label: "", + cell: Backgrid.Extension.DeleteCell, + editable: false, cell_priority: -1 + }); + } + + // Initialize a new Grid instance + var grid = self.grid = new Backgrid.Grid({ + columns: gridSchema.columns, + collection: self.collection, + className: "backgrid table-bordered" + }); + self.$grid = grid.render().$el; + + $gridBody.append(self.$grid); + + setTimeout(function() { + self.headerData.set({ + 'local_column': + self.$header.find( + 'div[header="local_column"] select option:first' + ).val(), + 'referenced': + self.$header.find( + 'div[header="referenced"] select option:first' + ).val(), + 'references': + self.$header.find( + 'div[header="references"] select option:first' + ).val() + }, {silent:true} + ); + }, 10); + + // Render node grid + return $gridBody; + }, + + headerDataChanged: function() { + var self = this, val, + data = this.headerData.toJSON(), + inSelected = false, + checkVars = ['local_column', 'referenced']; + + if (!self.$header) { + return; + } + + if (self.control_data.canAdd) { + self.collection.each(function(m) { + if (!inSelected) { + _.each(checkVars, function(v) { + if (!inSelected) { + val = m.get(v); + inSelected = (( + (_.isUndefined(val) || _.isNull(val)) && + (_.isUndefined(data[v]) || _.isNull(data[v])) + ) || + (val == data[v])); + } + }); + } + }); + } + else { + inSelected = true; + } + + self.$header.find('button.add').prop('disabled', inSelected); + }, + + addColumns: function(ev) { + ev.preventDefault(); + var self = this, + local_column = self.headerData.get('local_column'), + referenced = self.headerData.get('referenced'); + + if (!local_column || local_column == '' || + !referenced || referenced =='') { + return false; + } + + var m = new (self.field.get('model'))( + self.headerData.toJSON()), + coll = self.model.get(self.field.get('name')); + + coll.add(m); + + var idx = coll.indexOf(m); + + // idx may not be always > -1 because our UniqueColCollection may + // remove 'm' if duplicate value found. + if (idx > -1) { + self.$grid.find('.new').removeClass('new'); + + var newRow = self.grid.body.rows[idx].$el; + + newRow.addClass("new"); + $(newRow).pgMakeVisible('backform-tab'); + } else { + delete m; + } + + return false; + }, + + onAddorRemoveColumns: function() { + var self = this; + + // Wait for collection to be updated before checking for the button to be + // enabled, or not. + setTimeout(function() { + if (self.collection.length > 0) { + self.$header.find( + 'div[header="references"] select' + ).prop('disabled', true); + } else { + self.$header.find( + 'div[header="references"] select' + ).prop('disabled', false); + } + + self.collection.trigger('pgadmin:columns:updated', self.collection); + + self.headerDataChanged(); + + if ((!_.has(self.model, 'handler') || (_.has(self.model, 'handler') && + _.isUndefined(self.model.handler))) || + (_.has(self.model, 'handler') && !_.isUndefined(self.model.handler) && + !_.isUndefined(self.model.handler.get('oid')))) { + self.getCoveringIndex(); + } + + }, 10); + }, + + getCoveringIndex: function() { + + var self = this, + url = 'get_coveringindex', + m = self.model + cols = [], + coveringindex = null; + + self.collection.each(function(m){ + cols.push(m.get('local_column')); + }) + + if (cols.length > 0) { + var node = this.field.get('schema_node'), + node_info = this.field.get('node_info'), + full_url = node.generate_url.apply( + node, [ + null, url, this.field.get('node_data'), + this.field.get('url_with_id') || false, node_info + ]); + + if (this.field.get('version_compatible')) { + m.trigger('pgadmin:view:fetching', m, self.field); + $.ajax({ + async: false, + data : {cols:JSON.stringify(cols)}, + url: full_url, + success: function(res) { + coveringindex = res.data; + }, + error: function() { + m.trigger('pgadmin:view:fetch:error', m, self.field); + } + }); + m.trigger('pgadmin:view:fetched', m, self.field); + } + } + + if (coveringindex) { + m.set('hasindex', true); + m.set('autoindex', false); + m.set('coveringindex', coveringindex); + } else { + m.set('coveringindex', null); + m.set('autoindex', true); + m.set('hasindex', false); + } + }, + + remove: function() { + /* + * Stop listening the events registered by this control. + */ + this.stopListening(this.headerData, "change", this.headerDataChanged); + this.listenTo(this.headerData, "select2", this.headerDataChanged); + this.listenTo(this.collection, "remove", this.onRemoveVariable); + // Remove header controls. + _.each(this.controls, function(controls) { + controls.remove(); + }); + + ForeignKeyColumnControl.__super__.remove.apply(this, arguments); + + // Remove the header model + delete (this.headerData); + + } + }); + + // Extend the browser's node class for foreign key node + if (!pgBrowser.Nodes['foreign_key']) { + pgAdmin.Browser.Nodes['foreign_key'] = pgBrowser.Node.extend({ + type: 'foreign_key', + label: '{{ _('Foreign key') }}', + collection_type: 'coll-constraints', + sqlAlterHelp: 'ddl-alter.html', + sqlCreateHelp: 'ddl-constraints.html', + hasSQL: true, + hasDepends: false, + parent_type: 'table', + canDrop: true, + canDropCascade: true, + hasDepends: true, + Init: function() { + /* Avoid multiple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_foreign_key_on_coll', node: 'coll-constraints', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Foreign key...') }}', + icon: 'wcTabIcon icon-foreign_key', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'validate_foreign_key', node: 'foreign_key', module: this, + applies: ['object', 'context'], callback: 'validate_foreign_key', + category: 'validate', priority: 4, label: '{{ _('Validate foreign key') }}', + icon: 'fa fa-link', enable : 'is_not_valid' + } + ]); + }, + is_not_valid: function(node) { + return (node && !node.valid); + }, + callbacks: { + validate_foreign_key: function(args) { + var input = args || {}; + obj = this, + t = pgBrowser.tree, + i = input.item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined; + + if (!d) { + return false; + } + var data = d; + $.ajax({ + url: obj.generate_url(i, 'validate', d, true), + type:'GET', + success: function(res) { + if (res.success == 1) { + Alertify.success("{{ _('" + res.info + "') }}"); + t.removeIcon(i); + data.valid = true; + data.icon = 'icon-foreign_key'; + t.addIcon(i, {icon: data.icon}); + setTimeout(function() {t.deselect(i);}, 10); + setTimeout(function() {t.select(i);}, 100); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + msg = S('{{ _(' + err.errormsg + ')}}').value(); + Alertify.error("{{ _('" + err.errormsg + "') }}"); + } + } catch (e) {} + t.unload(i); + } + }); + + return false; + } + }, + // Define the model for foreign key node + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + oid: undefined, + comment: undefined, + condeferrable: undefined, + condeferred: undefined, + confmatchtype: undefined, + convalidated: undefined, + columns: undefined, + confupdtype: "a", + confdeltype: "a", + autoindex: true, + coveringindex: undefined, + hasindex:undefined + }, + toJSON: function () { + var d = pgAdmin.Browser.Node.Model.prototype.toJSON.apply(this, arguments); + delete d.hasindex; + return d; + }, + // Define the schema for the foreign key node + schema: [{ + id: 'name', label: '{{ _('Name') }}', type: 'text', + mode: ['properties', 'create', 'edit'], editable:true, + headerCell: Backgrid.Extension.CustomHeaderCell, cellHeaderClasses: 'width_percent_50' + },{ + id: 'oid', label:'{{ _('OID') }}', cell: 'string', + type: 'text' , mode: ['properties'] + },{ + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + deps:['name'], disabled:function(m) { + var name = m.get('name'); + if (!(name && name != '')) { + setTimeout(function(){ + m.set('comment', null); + },10); + return true; + } else { + return false; + } + } + },{ + id: 'condeferrable', label: '{{ _('Deferrable') }}', + type: 'switch', group: '{{ _('Definition') }}', + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + return !_.isUndefined(m.get('oid')); + } + // We can't update condeferrable of existing foreign key. + return !m.isNew(); + } + },{ + id: 'condeferred', label: '{{ _('Deferred') }}', + type: 'switch', group: '{{ _('Definition') }}', + deps: ['condeferrable'], + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + return !_.isUndefined(m.get('oid')); + } else if(!m.isNew()) { + return true; + } + // Disable if condeferred is false or unselected. + if(m.get('condeferrable') == true) { + return false; + } else { + setTimeout(function(){ + m.set('condeferred', false); + },10); + return true; + } + } + },{ + id: 'confmatchtype', label: '{{ _('Match type') }}', + type: 'switch', group: '{{ _('Definition') }}', + options: { + onText: 'FULL', + offText: 'SIMPLE', + },disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + return !_.isUndefined(m.get('oid')); + } + // We can't update condeferred of existing foreign key. + return !m.isNew(); + } + },{ + id: 'convalidated', label: "{{ _("Validated?") }}", + type: 'switch', group: '{{ _('Definition') }}', + options: { + onText: 'Yes', + offText: 'No' + },disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed + return !(_.isUndefined(m.get('oid')) || m.get("convalidated")); + } + // We can't update condeferred of existing foreign key. + return !(m.isNew() || m.get("convalidated")); + } + },{ + id: 'autoindex', label: '{{ _('Auto FK index') }}', + type: 'switch', group: '{{ _('Definition') }}', + deps: ['name', 'hasindex'], + options: { + onText: 'Yes', + offText: 'No', + },disabled: function(m) { + var index = m.get('coveringindex'), + autoindex = m.get('autoindex'), + setIndexName = function() { + var name = m.get('name'), + oldindex = 'fki_'+m.previous ('name'); + + if (m.get('hasindex')) { + return true; + } else if (m.get('autoindex') && !_.isUndefined(name) && !_.isNull(name) && + name != '' && (_.isUndefined(index) || _.isNull(index) || + index == '' || index == oldindex)) { + var newIndex = 'fki_' + name; + m.set('coveringindex', newIndex); + return false; + } else { + return false; + } + }; + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + if(_.isUndefined(m.get('oid')) && _.isUndefined(m.handler.get('oid'))) { + setTimeout(function () { + m.set('autoindex', false); + }, 10); + return true; + } else { + return setIndexName(); + } + } else { + if(!m.isNew() && autoindex && !_.isUndefined(index) && + !_.isNull(index) && index != '' && m.get('hasindex')) { + return true; + } else { + return setIndexName(); + } + } + } + },{ + id: 'coveringindex', label: '{{ _('Covering index') }}', type: 'text', + mode: ['properties', 'create', 'edit'], group: '{{ _('Definition') }}', + deps:['autoindex', 'hasindex'], + disabled: function(m) { + var index = m.get('coveringindex'), + setIndexName = function() { + if (m.get('hasindex')) { + return true; + } else if (!m.get('autoindex')) { + setTimeout(function () { + m.set('coveringindex', null); + }); + return true; + } else { + setTimeout(function () { + var name = m.get('name'), + newIndex = 'fki_' + name; + + if (m.get('autoindex') && !_.isUndefined(name) && !_.isNull(name) && + name != '') { + m.set('coveringindex', newIndex); + } + }); + + return false; + } + }; + + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + if (_.isUndefined(m.get('oid')) && _.isUndefined(m.handler.get('oid'))) { + return true; + } else { + return setIndexName(); + } + + } else if (!m.isNew() && m.get('autoindex') && !_.isUndefined(index) + && _.isNull(index) && index == '') { + return true; + } + + return setIndexName(); + } + },{ + id: 'columns', label: '{{ _('Columns') }}', + type: 'collection', group: '{{ _('Columns') }}', disabled: false, + node: 'foreign_key', editable: false, headerCell: Backgrid.Extension.CustomHeaderCell, + cellHeaderClasses: 'width_percent_50', + cell: Backgrid.StringCell.extend({ + initialize: function() { + Backgrid.StringCell.prototype.initialize.apply(this, arguments); + var self = this, + collection = this.model.get('columns'); + // Do not listen for any event(s) for existing constraint. + if (_.isUndefined(self.model.get('oid'))) { + var tableCols = self.model.top.get('columns'); + self.listenTo(tableCols, 'remove' , self.removeColumn); + self.listenTo(tableCols, 'change:name', self.resetColOptions); + } + + self.model.get('columns').on('pgadmin:columns:updated', function() { + self.render.apply(self); + }); + self.listenTo(collection, "add", self.render); + self.listenTo(collection, "remove", self.render); + }, + removeColumn: function(m){ + var self = this, + removedCols = self.model.get('columns').where( + {local_column: m.get('name')} + ); + + self.model.get('columns').remove(removedCols); + setTimeout(function () { + self.render(); + }, 10); + + setTimeout(function () { + constraints = self.model.top.get("foreign_key"); + var removed = []; + constraints.each(function(constraint) { + if (constraint.get("columns").length == 0) { + removed.push(constraint); + } + }); + constraints.remove(removed); + },100); + }, + resetColOptions : function(m) { + var self = this, + updatedCols = self.model.get('columns').where( + {"local_column": m.previous('name')} + ); + if (updatedCols.length > 0) { + /* + * Table column name has changed so update + * column name in foreign key as well. + */ + updatedCols[0].set( + {"local_column": m.get('name')}); + } + + setTimeout(function () { + self.render(); + }, 10); + }, + formatter: { + fromRaw: function (rawValue, model) { + var cols = [], + remote_cols = []; + if (rawValue.length > 0) { + rawValue.each(function(col){ + cols.push(col.get('local_column')); + remote_cols.push(col.get('referenced')) + }); + return '('+cols.join(', ')+') -> ('+ remote_cols.join(', ')+')'; + } + return ""; + }, + toRaw: function (val, model) { + return val; + } + }, + render: function() { + return Backgrid.StringCell.prototype.render.apply(this, arguments); + }, + remove: function() { + var tableCols = this.model.top.get('columns'); + + this.stopListening(tableCols, 'remove' , self.removeColumn); + this.stopListening(tableCols, 'change:name' , self.resetColOptions); + + Backgrid.StringCell.prototype.remove.apply(this, arguments); + } + }), + canAdd: function(m) { + // We can't update columns of existing foreign key. + return m.isNew(); + }, canDelete: true, + control: ForeignKeyColumnControl, + model: ForeignKeyColumnModel, + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + return !_.isUndefined(m.get('oid')); + } + // We can't update columns of existing foreign key. + return !m.isNew(); + } + },{ + id: 'confupdtype', label:'{{ _('On update') }}', + type:"select2", group: '{{ _('Action') }}', mode: ['edit','create'], + select2:{width:"50%", allowClear: false}, + options: [ + {label: "NO ACTION", value: "a"}, + {label: "RESTRICT", value: "r"}, + {label: "CASCADE", value: "c"}, + {label: "SET NULL", value: "n"}, + {label: "SET DEFAULT", value: "d"} + ],disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + return !_.isUndefined(m.get('oid')); + } + // We can't update confupdtype of existing foreign key. + return !m.isNew(); + } + },{ + id: 'confdeltype', label:'{{ _('On delete') }}', + type:"select2", group: '{{ _('Action') }}', mode: ['edit','create'], + select2:{width:"50%", allowClear: false}, + options: [ + {label: "NO ACTION", value: "a"}, + {label: "RESTRICT", value: "r"}, + {label: "CASCADE", value: "c"}, + {label: "SET NULL", value: "n"}, + {label: "SET DEFAULT", value: "d"} + ],disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'handler') && !_.isUndefined(m.handler)) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + return !_.isUndefined(m.get('oid')); + } + // We can't update confdeltype of existing foreign key. + return !m.isNew(); + } + } + ], + validate: function() { + this.errorModel.clear(); + + var columns = this.get('columns'); + if ((_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) { + var msg = '{{ _('Please specify columns for Foreign key.') }}'; + this.errorModel.set('columns', msg); + return msg; + } + + var coveringindex = this.get('coveringindex'), + autoindex = this.get('autoindex'); + if (autoindex && (_.isUndefined(coveringindex) || _.isNull(coveringindex) || + String(coveringindex).replace(/^\s+|\s+$/g, '') == '')) { + var msg = '{{ _('Please specify covering index name.') }}'; + this.errorModel.set('coveringindex', msg); + return msg; + } + + return null; + } + }), + + canCreate: function(itemData, item, data) { + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData, parents = []; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to c reate table + if (_.indexOf(['schema'], d._type) > -1) + return true; + parents.push(d._type); + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // If node is under catalog then do not allow 'create' menu + if (_.indexOf(parents, 'catalog') > -1) { + return false; + } else { + return true; + } + } + }); + } + + return pgBrowser.Nodes['foreign_key']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/__init__.py new file mode 100644 index 000000000..471a71e09 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/__init__.py @@ -0,0 +1,878 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements Primary key constraint Node""" + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext as _ +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +import pgadmin.browser.server_groups.servers.databases as database +from functools import wraps +from pgadmin.browser.server_groups.servers.databases.schemas.tables.constraints.type \ + import ConstraintRegistry, ConstraintTypeModule + + +class IndexConstraintModule(ConstraintTypeModule): + """ + class IndexConstraintModule(CollectionNodeModule) + + A module class for Primary key constraint node derived from ConstraintTypeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the PrimaryKeyConstraintModule and it's base module. + + * get_nodes(gid, sid, did) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for language, when any of the database node is + initialized. + """ + + NODE_TYPE = 'Index constraint' + COLLECTION_LABEL = _('index_constraint') + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the PrimaryKeyConstraintModule and it's base module. + + Args: + *args: + **kwargs: + + Returns: + + """ + self.min_ver = None + self.max_ver = None + super(IndexConstraintModule, self).__init__(*args, **kwargs) + + def get_nodes(self, gid, sid, did, scid, tid): + """ + Generate the collection node + """ + pass + + @property + def node_inode(self): + """ + Override this property to make the node a leaf node. + + Returns: False as this is the leaf node + """ + return False + + @property + def script_load(self): + """ + Load the module script for primary_key, when any of the table node is + initialized. + + Returns: node type of the server module. + """ + return database.DatabaseModule.NODE_TYPE + + +class PrimaryKeyConstraintModule(IndexConstraintModule): + """ + class PrimaryKeyConstraintModule(IndexConstraintModule) + + A module class for the catalog schema node derived from IndexConstraintModule. + """ + + NODE_TYPE = 'primary_key' + COLLECTION_LABEL = _("Primary key") + + +primary_key_blueprint = PrimaryKeyConstraintModule(__name__) + + +class UniqueConstraintModule(IndexConstraintModule): + """ + class UniqueConstraintModule(IndexConstraintModule) + + A module class for the catalog schema node derived from IndexConstraintModule. + """ + + NODE_TYPE = 'unique_constraint' + COLLECTION_LABEL = _("Unique constraint") + + +unique_constraint_blueprint = UniqueConstraintModule(__name__) + + +class IndexConstraintView(PGChildNodeView): + """ + class PrimaryKeyConstraintView(PGChildNodeView) + + A view class for Primary key constraint node derived from PGChildNodeView. This class is + responsible for all the stuff related to view like creating, updating Primary key constraint + node, showing properties, showing sql in sql pane. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the PrimaryKeyConstraintView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * list() + - This function returns primary key constraint nodes within that + collection as http response. + + * get_list() + - This function is used to list all the language nodes within that collection + and return list of primary key constraint nodes. + + * nodes() + - This function returns child node within that collection. + Here return all primary key constraint node as http response. + + * get_nodes() + - returns all primary key constraint nodes' list. + + * properties() + - This function will show the properties of the selected primary key. + + * update() + - This function will update the data for the selected primary key. + + * msql() + - This function is used to return modified SQL for the selected primary key. + + * get_sql() + - This function will generate sql from model data. + + * sql(): + - This function will generate sql to show it in sql pane for the selected primary key. + + * get_indices(): + - This function returns indices for current table. + + """ + + node_type = 'index_constraint' + + node_label = _('Index constraint') + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [{'type': 'int', 'id': 'cid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'delete': [{'delete': 'delete'}], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}] + }) + + def module_js(self): + """ + This property defines (if javascript) exists for this node. + Override this property for your own logic. + """ + return make_response( + render_template( + "index_constraint/js/index_constraint.js", + _=_, + node_type=self.node_type, + node_label=self.node_label + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + _( + "Connection to the server has been lost!" + ) + ) + + self.template_path = 'index_constraint/sql' + # We need parent's name eg table name and schema name + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=kwargs['tid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + self.schema = row['schema'] + self.table = row['table'] + return f(*args, **kwargs) + + return wrap + + def end_transaction(self): + SQL = render_template( + "/".join([self.template_path, 'end.sql'])) + # End transaction if any. + self.conn.execute_scalar(SQL) + + @check_precondition + def properties(self, gid, sid, did, scid, tid, cid=None): + """ + This function is used to list all the primary key + nodes within that collection. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + sql = render_template("/".join([self.template_path, 'properties.sql']), + tid=tid, + cid=cid, + constraint_type= self.constraint_type) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + result = res['rows'][0] + + sql = render_template( + "/".join([self.template_path, 'get_constraint_cols.sql']), + cid=cid, + colcnt=result['indnatts']) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + for row in res['rows']: + columns.append({"column": row['column'].strip('"')}) + + result['columns'] = columns + + return ajax_response( + response=result, + status=200 + ) + + @check_precondition + def list(self, gid, sid, did, scid, tid, cid=None): + """ + This function returns all primary keys + nodes within that collection as a http response. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + try: + res = self.get_node_list(gid, sid, did, scid, tid, cid) + return ajax_response( + response=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_node_list(self, gid, sid, did, scid, tid, cid=None): + """ + This function returns all primary keys + nodes within that collection as a list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + SQL = render_template("/".join([self.template_path, 'properties.sql']), + tid=tid, + constraint_type= self.constraint_type) + status, res = self.conn.execute_dict(SQL) + + return res['rows'] + + @check_precondition + def nodes(self, gid, sid, did, scid, tid, cid=None): + """ + This function returns all event trigger nodes as a + http response. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + try: + res = self.get_nodes(gid, sid, did, scid, tid, cid) + return make_json_response( + data=res, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_nodes(self, gid, sid, did, scid, tid, cid=None): + """ + This function returns all event trigger nodes as a list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + res = [] + SQL = render_template("/".join([self.template_path, 'nodes.sql']), + tid=tid, + constraint_type=self.constraint_type) + status, rset = self.conn.execute_2darray(SQL) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon="icon-%s" % self.node_type + )) + return res + + @check_precondition + def create(self, gid, sid, did, scid, tid, cid=None): + """ + This function will create a primary key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + required_args = [ + [u'columns', u'index'] # Either of one should be there. + ] + + data = request.form if request.form else json.loads(request.data.decode()) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + for arg in required_args: + if isinstance(arg, list): + for param in arg: + if (param in data and + (not isinstance(data[param], list) or + (isinstance(data[param], list) and + len(data[param]) > 0))): + break + else: + return make_json_response( + status=400, + success=0, + errormsg=_( + "Couldn't find at least one required parameter (%s)." % str(param) + ) + ) + + elif arg not in data: + return make_json_response( + status=400, + success=0, + errormsg=_( + "Couldn't find the required parameter (%s)." % arg + ) + ) + + data['schema'] = self.schema + data['table'] = self.table + try: + if 'name' not in data or data['name'] == "": + SQL = render_template( + "/".join([self.template_path, 'begin.sql'])) + # Start transaction. + status, res = self.conn.execute_scalar(SQL) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + # The below SQL will execute CREATE DDL only + SQL = render_template( + "/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn, + constraint_name=self.constraint_name + ) + + status, msg = self.conn.execute_scalar(SQL) + if not status: + self.end_transaction() + return internal_server_error(errormsg=msg) + + if 'name' not in data or data['name'] == "": + sql = render_template( + "/".join([self.template_path, + 'get_oid_with_transaction.sql'], + ), + constraint_type=self.constraint_type, + tid=tid) + + status, res = self.conn.execute_dict(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + self.end_transaction() + + data['name'] = res['rows'][0]['name'] + + else: + sql = render_template("/".join([self.template_path, 'get_oid.sql']), + tid=tid, + constraint_type=self.constraint_type, + name=data['name']) + status, res = self.conn.execute_dict(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=res) + + sql = render_template("/".join([self.template_path, 'alter.sql']), + data=data, + conn=self.conn) + sql = sql.strip('\n').strip(' ') + + if sql != '': + status, result = self.conn.execute_scalar(sql) + if not status: + self.end_transaction() + return internal_server_error(errormsg=result) + + return jsonify( + node=self.blueprint.generate_browser_node( + res['rows'][0]['oid'], + tid, + data['name'], + icon="icon-%s" % self.node_type + ) + ) + + except Exception as e: + self.end_transaction() + return make_json_response( + status=400, + success=0, + errormsg=e + ) + + @check_precondition + def update(self, gid, sid, did, scid, tid, cid=None): + """ + This function will update the data for the selected + primary key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + data = request.form if request.form else json.loads(request.data.decode()) + + try: + data['schema'] = self.schema + data['table'] = self.table + sql = self.get_sql(data, tid, cid) + sql = sql.strip('\n').strip(' ') + if sql != "": + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + sql = render_template("/".join([self.template_path, 'get_oid.sql']), + tid=tid, + constraint_type=self.constraint_type, + name=data['name']) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Constraint updated", + data={ + 'id': cid, + 'tid': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': cid, + 'tid': tid, + 'scid': scid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, cid=None): + """ + This function will delete an existing primary key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + # Below code will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + try: + sql = render_template("/".join([self.template_path, 'get_name.sql']), + tid=tid, + constraint_type = self.constraint_type, + cid=cid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data['schema'] = self.schema + data['table'] = self.table + + sql = render_template("/".join([self.template_path, 'delete.sql']), + data=data, + cascade=cascade) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=_("{0} dropped.".format(self.node_label)), + data={ + 'id': cid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, tid, cid=None): + """ + This function returns modified SQL for the selected + primary key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + data = {} + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + data['schema'] = self.schema + data['table'] = self.table + try: + sql = self.get_sql(data, tid, cid) + sql = sql.strip('\n').strip(' ') + + return make_json_response( + data=sql, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_sql(self, data, tid, cid=None): + """ + This function will generate sql from model data. + + Args: + data: Contains the data of the selected primary key constraint. + tid: Table ID. + cid: Primary key constraint ID + + Returns: + + """ + if cid is not None: + sql = render_template("/".join([self.template_path, 'properties.sql']), + tid=tid, + cid=cid, + constraint_type= self.constraint_type) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + old_data = res['rows'][0] + required_args = [u'name'] + for arg in required_args: + if arg not in data: + data[arg] = old_data[arg] + + sql = render_template("/".join([self.template_path, 'update.sql']), + data=data, + o_data=old_data) + else: + required_args = [ + [u'columns', u'index'] # Either of one should be there. + ] + + for arg in required_args: + if isinstance(arg, list): + for param in arg: + if (param in data and + ((isinstance(data[param], str) and + data[param] != "") or + (isinstance(data[param], list) and + len(data[param]) > 0))): + break + else: + return _('-- definition incomplete') + + elif arg not in data: + return _('-- definition incomplete') + + sql = render_template("/".join([self.template_path, 'create.sql']), + data=data, + conn=self.conn, + constraint_name=self.constraint_name) + sql += "\n" + sql += render_template("/".join([self.template_path, 'alter.sql']), + data=data, + conn=self.conn) + + return sql + + @check_precondition + def sql(self, gid, sid, did, scid, tid, cid=None): + """ + This function generates sql to show in the sql pane for the selected + primary key. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + cid: Primary key constraint ID + + Returns: + + """ + try: + SQL = render_template( + "/".join([self.template_path, 'properties.sql']), + tid=tid, + conn=self.conn, + cid=cid, + constraint_type=self.constraint_type) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = res['rows'][0] + data['schema'] = self.schema + data['table'] = self.table + + sql = render_template( + "/".join([self.template_path, 'get_constraint_cols.sql']), + cid=cid, colcnt=data['indnatts']) + + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + + columns = [] + for row in res['rows']: + columns.append({"column": row['column'].strip('"')}) + + data['columns'] = columns + + SQL = render_template( + "/".join([self.template_path, 'create.sql']), + data=data, + constraint_name=self.constraint_name) + SQL += "\n" + SQL += render_template( + "/".join([self.template_path, 'alter.sql']), + data=data, conn=self.conn) + + sql_header = "-- Constraint: {0}\n\n-- ".format(data['name']) + + sql_header += render_template( + "/".join([self.template_path, 'delete.sql']), + data=data) + sql_header += "\n" + + SQL = sql_header + SQL + + return ajax_response(response=SQL) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + +class PrimaryKeyConstraintView(IndexConstraintView): + node_type = 'primary_key' + + node_label = _('Primary key') + + constraint_name = "PRIMARY KEY" + + constraint_type = "p" + + +class UniqueConstraintView(IndexConstraintView): + node_type = 'unique_constraint' + + node_label = _('Unique constraint') + + constraint_name = "UNIQUE" + + constraint_type = "u" + + +primary_key_constraint = ConstraintRegistry( + 'primary_key', PrimaryKeyConstraintModule, PrimaryKeyConstraintView + ) + +unique_constraint = ConstraintRegistry( + 'unique_constraint', UniqueConstraintModule, UniqueConstraintView + ) + +PrimaryKeyConstraintView.register_node_view(primary_key_blueprint) +UniqueConstraintView.register_node_view(unique_constraint_blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/primary_key.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/primary_key.png new file mode 100644 index 0000000000000000000000000000000000000000..b57f59778554c3b0ee1b872a4174cbe631ee64e2 GIT binary patch literal 443 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfD`Vm@3!4NRdR5F#O>qdUq7Aw{C>xWm$N>;n)Uh3 z;xBI(e|bIY^Q(!UUNnAw(e(Yp+IP=)e*Jjj^V^Ldo=^DrV#4P)3vM4z`1*Fitz!|N zUo?Mt)qC$u?bnY-Kfl}j@%4gFujk!8nS1w4)!Qd4-ac7$_f*mGm0D_V?`Q*^#aI&L z7tG-B>_!@p!&%@FSq!8-z}W3%wjGcW@9E+gB5^r6VF6b{N>Y-`Gvm!0Hf=0cIJvo? zsPOZLPmT=s%*?{88RjpTu%Tncj2$gYMC1b;qT2k!{Nfw~JtJL3mox?X#ySUk&+eD! zaI9ft8(Q1kei>9nN|tWU|090*aWnc!;aB6z!8lVOS MPgg&ebxsLQ07e(akpKVy literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.png new file mode 100644 index 0000000000000000000000000000000000000000..e82857235ed1d5bb351b981c447ec3370d495fa9 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}V}MVHE0AtpdP65|Z{ONGc?&PK z9lkT?%wyl?6GdCEg|9qcv+MTqD^L55-OJi^HFW8P+MRbYmt2Wha=CWfUF*U_tvl~U zF1%W|<$g;4`J&}FqUK+(-}In$+k@yiHybuQDqDIhZuaelb&vDr-HMxer)=5dRj1!9 zI{IqDj_1wmp7v~dv0(q3HOJoP31mnB9l=-<+NGIBA*;;oEjPCQ(Bz!3V!CK3^T!Lkmz5Z{ zJ+895(X7z(qO)VNNwR|esV?IU#U}a)#Wn!vrU}|MxFz=n-DijU5 n`6-!cl@JXEmS7D))h1R3W)KahriZQpYGCkm^>bP0l+XkKGQ6yC literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/templates/index_constraint/js/index_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/templates/index_constraint/js/index_constraint.js new file mode 100644 index 000000000..b19d90f2a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/templates/index_constraint/js/index_constraint.js @@ -0,0 +1,536 @@ +define( + ['jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'], +function($, _, S, pgAdmin, pgBrowser, alertify) { + + // Extend the browser's node class for index constraint node + if (!pgBrowser.Nodes['{{node_type}}']) { + pgAdmin.Browser.Nodes['{{node_type}}'] = pgBrowser.Node.extend({ + type: '{{node_type}}', + label: '{{ node_label }}', + collection_type: 'coll-constraints', + sqlAlterHelp: 'ddl-alter.html', + sqlCreateHelp: 'ddl-constraints.html', + hasSQL: true, + hasDepends: false, + parent_type: 'table', + canDrop: true, + canDropCascade: true, + Init: function() { + /* Avoid multiple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_{{node_type}}_on_coll', node: 'coll-constraints', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ node_label }}', + icon: 'wcTabIcon icon-{{node_type}}', data: {action: 'create', check: true}, + enable: 'canCreate' + + } + ]); + }, + canCreate: function(itemData, item, data) { + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData, parents = []; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to c reate table + if (_.indexOf(['schema'], d._type) > -1) { + {% if node_type == 'primary_key' %} + // There should be only one primary key per table. + var children = t.children(arguments[1], false), + primary_key_found = false; + + _.each(children, function(child){ + data = pgBrowser.tree.itemData($(child)); + if (!primary_key_found && data._type == "primary_key") { + primary_key_found = true; + } + }); + return !primary_key_found; + {% else %} + return true; + {% endif %} + } + parents.push(d._type); + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // If node is under catalog then do not allow 'create' menu + if (_.indexOf(parents, 'catalog') > -1) { + return false; + } else { + return true; + } + }, + + // Define the model for index constraint node + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + oid: undefined, + comment: undefined, + spcname: "pg_default", + index: undefined, + fillfactor: undefined, + condeferrable: undefined, + condeferred: undefined, + columns: [] + }, + + // Define the schema for the index constraint node + schema: [{ + id: 'name', label: '{{ _('Name') }}', type: 'text', + mode: ['properties', 'create', 'edit'], editable:true, + cellHeaderClasses:'width_percent_40', + },{ + id: 'oid', label:'{{ _('OID') }}', cell: 'string', + type: 'text' , mode: ['properties'], editable: false, + cellHeaderClasses:'width_percent_20', + },{ + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + deps:['name'], disabled:function(m) { + var name = m.get('name'); + if (!(name && name != '')) { + setTimeout(function(){ + m.set('comment', null); + },10); + return true; + } else { + return false; + } + } + },{ + id: 'columns', label: '{{ _('Columns') }}', + type: 'collection', group: '{{ _('Definition') }}', + editable: false, + cell: Backgrid.StringCell.extend({ + initialize: function() { + Backgrid.StringCell.prototype.initialize.apply(this, arguments); + + var self = this, + collection = this.model.get('columns'); + + // Do not listen for any event(s) for existing constraint. + if (_.isUndefined(self.model.get('oid'))) { + var tableCols = self.model.top.get('columns'); + self.listenTo(tableCols, 'remove' , self.removeColumn); + self.listenTo(tableCols, 'change:name', self.resetColOptions); + } + + collection.on('pgadmin:multicolumn:updated', function() { + self.render.apply(self); + }); + self.listenTo(collection, "add", self.render); + self.listenTo(collection, "remove", self.render); + }, + removeColumn: function(m) { + var self = this, + removedCols = self.model.get('columns').where( + {column: m.get('name')} + ); + + self.model.get('columns').remove(removedCols); + setTimeout(function () { + self.render(); + }, 10); + + {% if node_type == 'primary_key' %} + var key = 'primary_key' + {% else %} + var key = 'unique_constraint' + {% endif %} + + setTimeout(function () { + constraints = self.model.top.get(key); + var removed = []; + constraints.each(function(constraint) { + if (constraint.get("columns").length == 0) { + removed.push(constraint); + } + }); + constraints.remove(removed); + },100); + + }, + resetColOptions : function(m) { + var self = this, + updatedCols = self.model.get('columns').where( + {column: m.previous('name')} + ); + if (updatedCols.length > 0) { + /* + * Table column name has changed so update + * column name in primary key as well. + */ + updatedCols[0].set( + {"column": m.get('name')}, + {silent: true}); + } + + setTimeout(function () { + self.render(); + }, 10); + }, + formatter: { + fromRaw: function (rawValue, model) { + return rawValue.pluck("column").toString(); + }, + toRaw: function (val, model) { + return val; + } + }, + render: function() { + return Backgrid.StringCell.prototype.render.apply(this, arguments); + }, + remove: function() { + var tableCols = this.model.top.get('columns'), + primary_key_col = this.model.get('columns'); + + if (primary_key_col) { + primary_key_col.off('pgadmin:multicolumn:updated'); + } + + this.stopListening(tableCols, 'remove' , self.removeColumn); + this.stopListening(tableCols, 'change:name' , self.resetColOptions); + + Backgrid.StringCell.prototype.remove.apply(this, arguments); + } + }), + canDelete: true, canAdd: true, + control: Backform.MultiSelectAjaxControl.extend({ + formatter: { + fromRaw: function (rawData, model) { + var res = _.isObject(rawData) ? + rawData : JSON.parse(rawData); + + return _.pluck(res, 'column'); + }, + toRaw: function (formattedData, model) { + return formattedData; + } + }, + defaults: _.extend( + {}, + Backform.NodeListByNameControl.prototype.defaults, + { + select2: { + multiple: true, + allowClear: true, + width: 'style', + placeholder: '{{ _('Select the column(s)') }}', + } + } + ), + initialize: function() { + // Here we will decide if we need to call URL + // Or fetch the data from parent columns collection + var self = this; + if(this.model.handler) { + Backform.Select2Control.prototype.initialize.apply(this, arguments); + // Do not listen for any event(s) for existing constraint. + if (_.isUndefined(self.model.get('oid'))) { + var tableCols = self.model.top.get('columns'); + self.listenTo(tableCols, 'remove' , self.resetColOptions); + self.listenTo(tableCols, 'change:name', self.resetColOptions); + } + + self.custom_options(); + } else { + Backform.MultiSelectAjaxControl.prototype.initialize.apply(this, arguments); + } + self.model.get('columns').on('pgadmin:multicolumn:updated', function() { + self.render.apply(self); + }); + }, + resetColOptions: function(m) { + var self = this; + + setTimeout(function () { + self.custom_options(); + self.render.apply(self); + }, 50); + + }, + custom_options: function() { + // We will add all the columns entered by user in table model + var columns = this.model.top.get('columns'), + added_columns_from_tables = []; + + if (columns.length > 0) { + _.each(columns.models, function(m) { + var col = m.get('name'); + if(!_.isUndefined(col) && !_.isNull(col)) { + added_columns_from_tables.push( + {label: col, value: col, image:'icon-column'} + ); + } + }); + } + // Set the values in to options so that user can select + this.field.set('options', added_columns_from_tables); + }, + onChange: function(e) { + var self = this, + model = this.model, + $el = $(e.target), + attrArr = this.field.get("name").split('.'), + name = attrArr.shift(), + path = attrArr.join('.'), + vals = this.getValueFromDOM(), + collection = model.get(name), + removed = []; + + this.stopListening(this.model, "change:" + name, this.render); + + /* + * Iterate through all the values, and find out how many are already + * present in the collection. + */ + collection.each(function(m) { + var column = m.get('column'), + idx = _.indexOf(vals, column); + + if (idx > -1) { + vals.splice(idx, 1); + } else { + removed.push(column); + } + }); + + /* + * Adding new values + */ + + _.each(vals, function(v) { + var m = new (self.field.get('model'))( + {column: v}, { silent: true, + top: self.model.top, + collection: collection, + handler: collection + }); + + collection.add(m); + }); + + /* + * Removing unwanted! + */ + _.each(removed, function(v) { + collection.remove(collection.where({column: v})); + }); + + this.listenTo(this.model, "change:" + name, this.render); + }, + remove: function() { + if(this.model.handler) { + var self = this, + tableCols = self.model.top.get('columns'); + self.stopListening(tableCols, 'remove' , self.resetColOptions); + self.stopListening(tableCols, 'change:name' , self.resetColOptions); + self.model.get('columns').off('pgadmin:multicolumn:updated'); + + Backform.Select2Control.prototype.remove.apply(this, arguments); + + } else { + Backform.MultiSelectAjaxControl.prototype.remove.apply(this, arguments); + } + } + }), + deps: ['index'], node: 'column', + model: pgBrowser.Node.Model.extend({ + defaults: { + column: undefined + }, + validate: function() { + return null; + } + }), + transform : function(data){ + var res = []; + if (data && _.isArray(data)) { + _.each(data, function(d) { + res.push({label: d.label, value: d.label, image:'icon-column'}); + }) + } + return res; + }, + select2:{allowClear:false}, + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'top') && !_.isUndefined(m.top) + && !m.top.isNew()) { + // If OID is undefined then user is trying to add + // new constraint which should be allowed for Unique + return !_.isUndefined(m.get('oid')); + } + + // We can't update columns of existing index constraint. + if (!m.isNew()) { + return true; + } + // Disable if index is selected. + var index = m.get('index'); + if(_.isUndefined(index) || index == '') { + return false; + } else { + var col = m.get('columns'); + col.reset(); + return true; + } + } + },{ + id: 'spcname', label: '{{ _('Tablespace') }}', + type: 'text', group: '{{ _('Definition') }}', + control: 'node-list-by-name', node: 'tablespace', + deps: ['index'], + select2:{allowClear:false}, + filter: function(m) { + // Don't show pg_global tablespace in selection. + if (m.label == "pg_global") return false; + else return true; + }, + disabled: function(m) { + // Disable if index is selected. + m = m.top || m; + var index = m.get('index'); + if(_.isUndefined(index) || index == '') { + return false; + } else { + setTimeout(function(){ + m.set('spcname', ''); + },10); + return true; + } + } + },{ + id: 'index', label: '{{ _('Index') }}', + type: 'text', group: '{{ _('Definition') }}', + control: Backform.NodeListByNameControl.extend({ + initialize:function() { + if (_.isUndefined(this.model.top)) { + Backform.NodeListByNameControl.prototype.initialize.apply(this,arguments); + } else { + Backform.Control.prototype.initialize.apply(this,arguments); + } + } + }), + select2:{allowClear:true}, node: 'index', + disabled: function(m) { + // If we are in table edit mode then disable it + if (_.has(m, 'top') && !_.isUndefined(m.top) + && !m.top.isNew()) { + return true; + } + + // We can't update index of existing index constraint. + return !m.isNew(); + }, + // We will not show this field in Create Table mode + visible: function(m) { + return !_.isUndefined(m.top.node_info['table']); + } + },{ + id: 'fillfactor', label: '{{ _('Fill factor') }}', deps: ['index'], + type: 'int', group: '{{ _('Definition') }}', allowNull: true, + disabled: function(m) { + // Disable if index is selected. + var index = m.get('index'); + if(_.isUndefined(index) || index == '') { + return false; + } else { + setTimeout(function(){ + m.set('fillfactor', null); + },10); + return true; + } + } + },{ + id: 'condeferrable', label: '{{ _('Deferrable') }}', + type: 'switch', group: '{{ _('Definition') }}', deps: ['index'], + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'top') && !_.isUndefined(m.top) + && !m.top.isNew()) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + return !_.isUndefined(m.get('oid')); + } + + // We can't update condeferrable of existing index constraint. + if (!m.isNew()) { + return true; + } + // Disable if index is selected. + var index = m.get('index'); + if(_.isUndefined(index) || index == '') { + return false; + } else { + setTimeout(function(){ + m.set('condeferrable', false); + },10); + return true; + } + } + },{ + id: 'condeferred', label: '{{ _('Deferred') }}', + type: 'switch', group: '{{ _('Definition') }}', + deps: ['condeferrable'], + disabled: function(m) { + // If we are in table edit mode then + if (_.has(m, 'top') && !_.isUndefined(m.top) + && !m.top.isNew()) { + // If OID is undefined then user is trying to add + // new constraint which should allowed for Unique + return !_.isUndefined(m.get('oid')); + } + + // We can't update condeferred of existing index constraint. + if (!m.isNew()) { + return true; + } + // Disable if condeferred is false or unselected. + if(m.get('condeferrable') == true) { + return false; + } else { + setTimeout(function(){ + m.set('condeferred', false); + },10); + return true; + } + } + } + ], + validate: function() { + this.errorModel.clear(); + // Clear parent's error as well + if (_.has(this, 'top')) { + this.top.errorModel.clear(); + } + + var columns = this.get('columns'), + index = this.get('index'); + + if ((_.isUndefined(index) || String(index).replace(/^\s+|\s+$/g, '') == '') && + (_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) { + var msg = '{{ _('Please specify columns for ') }}' + '{{ node_label }}'; + this.errorModel.set('columns', msg); + return msg; + } + + return null; + } + }) + }); + } + + return pgBrowser.Nodes['{{node_type}}']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/static/img/coll-constraints.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/static/img/coll-constraints.png new file mode 100644 index 0000000000000000000000000000000000000000..d62e13705c50e6c0cf8f19d680053e8643e28751 GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFv3GfMV1=2TrO847{Jl`|t`Qpas znRGPqvlydizRJ&>B`Om#V}R-yOM?7@862M7NCR>>3p^r=fwTu0yPeFo z12TL)T^vI=t|uoPU||ZF@;Gg7=uums=9bIK=Egd~(us-3g@Iv02gfsK^JP^)gH=mhBT7;dOH!?p zi&B9UgOP!ufv%yEu7P2Qk%5(ov6YF5wt=aYfq}(LRXG$5x%nxXX_XKS29{tAAk|g| XW)KahriZQpYGCkm^>bP0l+XkKyyRU} literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/templates/constraints/js/constraints.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/templates/constraints/js/constraints.js new file mode 100644 index 000000000..c8144fd3e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/templates/constraints/js/constraints.js @@ -0,0 +1,54 @@ +define( + [ + 'jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', + 'pgadmin.browser.collection'{% for c in constraints %}, 'pgadmin.node.{{ c|safe }}'{%endfor%} + ], +function($, _, S, pgAdmin, pgBrowser) { + + if (!pgBrowser.Nodes['coll-constraints']) { + var databases = pgAdmin.Browser.Nodes['coll-constraints'] = + pgAdmin.Browser.Collection.extend({ + node: 'constraints', + label: '{{ _('Constraints') }}', + type: 'coll-constraints', + columns: ['name', 'comment'] + }); + }; + + if (!pgBrowser.Nodes['constraints']) { + pgAdmin.Browser.Nodes['constraints'] = pgBrowser.Node.extend({ + type: 'constraints', + label: '{{ _('Constraints') }}', + collection_type: 'coll-constraints', + parent_type: ['table'], + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([]); + }, + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + oid: undefined, + comment: undefined + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', type: 'text', + mode: ['properties', 'create', 'edit'] + },{ + id: 'oid', label:'{{ _('Oid') }}', cell: 'string', + type: 'text' , mode: ['properties'] + },{ + id: 'comment', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'] + }] + }) + }); + } + + return pgBrowser.Nodes['constraints']; +}); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/type.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/type.py new file mode 100644 index 000000000..d75bf2b9c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/type.py @@ -0,0 +1,42 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from pgadmin.browser.collection import CollectionNodeModule +from flask import Blueprint + + +class ConstraintRegistry(object): + """ + ConstraintTypeRegistry + + It is more of a registry for difference type of constraints for the tables. + Its job is to initialize to different type of constraint blueprint and + register it with its respective NodeView. + """ + registry = dict() + + def __init__(self, name, con_blueprint, con_nodeview): + if name not in ConstraintRegistry.registry: + + blueprint = con_blueprint(name) + + # TODO:: register the view with the blueprint + con_nodeview.register_node_view(blueprint) + + ConstraintRegistry.registry[name] = { + 'blueprint': blueprint, + 'nodeview': con_nodeview + } + + +class ConstraintTypeModule(CollectionNodeModule): + register = Blueprint.register + + def __init__(self, *args, **kwargs): + super(ConstraintTypeModule, self).__init__(*args, **kwargs) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py new file mode 100644 index 000000000..4573a9778 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/__init__.py @@ -0,0 +1,874 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Index Node """ + +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases as database +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \ + parse_priv_to_db +from functools import wraps +import json + + +class IndexesModule(CollectionNodeModule): + """ + class IndexesModule(CollectionNodeModule) + + A module class for Index node derived from CollectionNodeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the Index and it's base module. + + * get_nodes(gid, sid, did, scid, tid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for schema, when any of the server node is + initialized. + """ + + NODE_TYPE = 'index' + COLLECTION_LABEL = gettext("Indexes") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the IndexModule and it's base module. + + Args: + *args: + **kwargs: + """ + self.min_ver = None + self.max_ver = None + super(IndexesModule, self).__init__(*args, **kwargs) + + def BackendSupported(self, manager, **kwargs): + """ + Load this module if vid is view, we will not load it under + material view + """ + if super(IndexesModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection(did=kwargs['did']) + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + if 'vid' not in kwargs: + return True + + template_path = 'index/sql/9.1_plus' + SQL = render_template("/".join( + [template_path, 'backend_support.sql']), vid=kwargs['vid']) + status, res = conn.execute_scalar(SQL) + + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check vid is view not material view + # then true, othewise false + return res + + def get_nodes(self, gid, sid, did, scid, **kwargs): + """ + Generate the collection node + """ + assert('tid' in kwargs or 'vid' in kwargs) + yield self.generate_browser_collection_node( + kwargs['tid'] if 'tid' in kwargs else kwargs['vid'] + ) + + @property + def script_load(self): + """ + Load the module script for server, when any of the server-group node is + initialized. + """ + return database.DatabaseModule.NODE_TYPE + + @property + def node_inode(self): + """ + Load the module node as a leaf node + """ + return False + +blueprint = IndexesModule(__name__) + + +class IndexesView(PGChildNodeView): + """ + This class is responsible for generating routes for Index node + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the IndexView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * list() + - This function is used to list all the Index nodes within that + collection. + + * nodes() + - This function will used to create all the child node within that + collection, Here it will create all the Index node. + + * properties(gid, sid, did, scid, tid, idx) + - This function will show the properties of the selected Index node + + * create(gid, sid, did, scid, tid) + - This function will create the new Index object + + * update(gid, sid, did, scid, tid, idx) + - This function will update the data for the selected Index node + + * delete(self, gid, sid, scid, tid, idx): + - This function will drop the Index object + + * msql(gid, sid, did, scid, tid, idx) + - This function is used to return modified SQL for the selected + Index node + + * get_sql(data, scid, tid) + - This function will generate sql from model data + + * sql(gid, sid, did, scid): + - This function will generate sql to show it in sql pane for the + selected Index node. + + * dependency(gid, sid, did, scid): + - This function will generate dependency list show it in dependency + pane for the selected Index node. + + * dependent(gid, sid, did, scid): + - This function will generate dependent list to show it in dependent + pane for the selected Index node. + """ + + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [ + {'type': 'int', 'id': 'idx'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'delete': [{'delete': 'delete'}], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'get_collations': [{'get': 'get_collations'}, + {'get': 'get_collations'}], + 'get_access_methods': [{'get': 'get_access_methods'}, + {'get': 'get_access_methods'}], + 'get_op_class': [{'get': 'get_op_class'}, + {'get': 'get_op_class'}] + }) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + # We need datlastsysoid to check if current index is system index + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + + # we will set template path for sql scripts + self.template_path = 'index/sql/9.1_plus' + + # We need parent's name eg table name and schema name + # when we create new index in update we can fetch it using + # property sql + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=kwargs['tid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + self.schema = row['schema'] + self.table = row['table'] + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def get_collations(self, gid, sid, did, scid, tid, idx=None): + """ + This function will return list of collation available + via AJAX response + """ + res = [{'label': '', 'value': ''}] + try: + SQL = render_template("/".join([self.template_path, + 'get_collations.sql'])) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['collation'], + 'value': row['collation']} + ) + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_access_methods(self, gid, sid, did, scid, tid, idx=None): + """ + This function will return list of access methods available + via AJAX response + """ + res = [{'label': '', 'value': ''}] + try: + SQL = render_template("/".join([self.template_path, + 'get_am.sql'])) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['amname'], + 'value': row['amname']} + ) + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def get_op_class(self, gid, sid, did, scid, tid, idx=None): + """ + This function will return list of op_class method + for each access methods available via AJAX response + """ + res = dict() + try: + # Fetching all the access methods + SQL = render_template("/".join([self.template_path, + 'get_am.sql'])) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + # Fetching all the op_classes for each access method + SQL = render_template("/".join([self.template_path, + 'get_op_class.sql']), + oid=row['oid']) + status, result = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + op_class_list = [{'label': '', 'value': ''}] + + for r in result['rows']: + op_class_list.append({'label': r['opcname'], + 'value': r['opcname']}) + + # Append op_class list in main result as collection + res[row['amname']] = op_class_list + + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + + @check_precondition + def list(self, gid, sid, did, scid, tid): + """ + This function is used to list all the schema nodes within that collection. + + Args: + gid: Server group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of available schema nodes + """ + + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), tid=tid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, tid): + """ + This function will used to create all the child node within that collection. + Here it will create all the schema node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of available schema child nodes + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), tid=tid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon="icon-index" + )) + + return make_json_response( + data=res, + status=200 + ) + + def _column_details(self, idx, data): + """ + This functional will fetch list of column details for index + + Args: + idx: Index OID + data: Properties data + + Returns: + Updated properties data with column details + """ + + SQL = render_template("/".join([self.template_path, + 'column_details.sql']), idx=idx) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + # 'attdef' comes with quotes from query so we need to strip them + # 'options' we need true/false to render switch ASC(false)/DESC(true) + columns = [] + cols = [] + cnt = 1 + for row in rset['rows']: + # We need all data as collection for ColumnsModel + cols_data = { + 'colname': row['attdef'].strip('"'), + 'collspcname': row['collnspname'], + 'op_class': row['opcname'], + } + if row['options'][0] == 'DESC': + cols_data['sort_order'] = True + columns.append(cols_data) + + # We need same data as string to display in properties window + # If multiple column then separate it by colon + cols_str = row['attdef'] + if row['collnspname']: + cols_str += ' COLLATE ' + row['collnspname'] + if row['opcname']: + cols_str += ' ' + row['opcname'] + if row['options'][0] == 'DESC': + cols_str += ' DESC' + cols.append(cols_str) + + # Push as collection + data['columns'] = columns + # Push as string + data['cols'] = ', '.join(cols) + + return data + + + @check_precondition + def properties(self, gid, sid, did, scid, tid, idx): + """ + This function will show the properties of the selected schema node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + scid: Schema ID + tid: Table ID + idx: Index ID + + Returns: + JSON of selected schema node + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, idx=idx, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + # Making copy of output for future use + data = dict(res['rows'][0]) + + # Add column details for current index + data = self._column_details(idx, data) + + return ajax_response( + response=data, + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid, tid): + """ + This function will creates new the schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + data = request.form if request.form else json.loads( + request.data.decode() + ) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + required_args = { + 'name': 'Name', + 'columns': 'Columns' + } + + for arg in required_args: + err_msg = None + if arg == 'columns' and len(data['columns']) < 1: + err_msg = "You must provide one or more column to create index" + + if arg not in data: + err_msg = "Couldn't find the required parameter (%s)." % \ + required_args[arg] + # Check if we have at least one column + if err_msg is not None: + return make_json_response( + status=410, + success=0, + errormsg=gettext(err_msg) + ) + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + try: + SQL = render_template("/".join([self.template_path, + 'create.sql']), + data=data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # If user chooses concurrent index then we can not run it along + # with other alter statments so we will separate alter index part + SQL = render_template("/".join([self.template_path, + 'alter.sql']), + data=data, conn=self.conn) + SQL = SQL.strip('\n').strip(' ') + if SQL != '': + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # we need oid to to add object in tree at browser + SQL = render_template("/".join([self.template_path, + 'get_oid.sql']), + tid=tid, data=data) + status, idx = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=tid) + + return jsonify( + node=self.blueprint.generate_browser_node( + idx, + scid, + data['name'], + icon="icon-index" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, idx): + """ + This function will updates existing the schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + idx: Index ID + """ + # Below will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + + try: + # We will first fetch the index name for current request + # so that we create template for dropping index + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, idx=idx, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = dict(res['rows'][0]) + + SQL = render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn, cascade=cascade) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Index is dropped"), + data={ + 'id': idx, + 'tid': tid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, tid, idx): + """ + This function will updates existing the schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + idx: Index ID + """ + data = request.form if request.form else json.loads(request.data.decode()) + data['schema'] = self.schema + data['table'] = self.table + try: + SQL = self.get_sql(scid, tid, idx, data) + if SQL and SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Index updated", + data={ + 'id': idx, + 'tid': tid, + 'scid': scid + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': idx, + 'tid': tid, + 'scid': scid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + + @check_precondition + def msql(self, gid, sid, did, scid, tid, idx=None): + """ + This function will generates modified sql for schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + idx: Index ID (When working with existing index) + """ + data = dict() + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + try: + SQL = self.get_sql(scid, tid, idx, data) + + if SQL and SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_sql(self, scid, tid, idx, data): + """ + This function will genrate sql from model data + """ + if idx is not None: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, idx=idx, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + old_data = dict(res['rows'][0]) + + # If name is not present in data then + # we will fetch it from old data, we also need schema & table name + if 'name' not in data: + data['name'] = old_data['name'] + + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data, conn=self.conn + ) + else: + required_args = { + 'name': 'Name', + 'columns': 'Columns' + } + for arg in required_args: + err = False + if arg == 'columns' and len(data['columns']) < 1: + err = True + + if arg not in data: + err = True + # Check if we have at least one column + if err: + return gettext('-- incomplete definition') + + # If the request for new object which do not have did + SQL = render_template("/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn) + SQL += "\n" + SQL += render_template("/".join([self.template_path, 'alter.sql']), + data=data, conn=self.conn) + + return SQL + + @check_precondition + def sql(self, gid, sid, did, scid, tid, idx): + """ + This function will generates reverse engineered sql for schema object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + idx: Index ID + """ + try: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, idx=idx, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + # Add column details for current index + data = self._column_details(idx, data) + + SQL = self.get_sql(scid, tid, None, data) + + sql_header = "-- Index: {0}\n\n-- ".format(data['name']) + sql_header += render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn) + + SQL = sql_header + '\n\n' + SQL + + return ajax_response(response=SQL) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, tid, idx): + """ + This function get the dependents and return ajax response + for the schema node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + idx: Index ID + """ + dependents_result = self.get_dependents( + self.conn, idx + ) + + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid, idx): + """ + This function get the dependencies and return ajax response + for the schema node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + idx: Index ID + + """ + dependencies_result = self.get_dependencies( + self.conn, idx + ) + + return ajax_response( + response=dependencies_result, + status=200 + ) + +IndexesView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/img/coll-index.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/img/coll-index.png new file mode 100644 index 0000000000000000000000000000000000000000..bb1513c8287e2ee5b39c36836fc01636ed35cf5f GIT binary patch literal 468 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfoB*E?S0LTF`o3ZPMa`Pi^5sX2 z>o4lmo>i#s{~Mc;}!vhk&@*M_wFx^kv_@PdjdY*m&jL%JXj) zpL#Xt=!@wGpZi26dW9#rhbFAP@YX3X&fY(E*X@s+uf1P$;qB7Xujd_mIpfgtu2Mf2 zp!*n0g8YIR9G=}s19CVEJR*yMvo4lmo>isa+J5uHrmOGQUVOXa?3=}>Ud=o9a@OG&(+)g;b8MqB(8G)+L4Lsu4$p3+0Xdun z9+AaB+5?Q;PG;Ky88x0Rjv*44lM@t}42%pnFW}g)X=8DL5_f?jgZYf}hYz1VuCJh> zqN8+*RZu{9`h+QyrcInWxt&8pLrc?p<%(4+vooAnM7C}zT~hk>3olQv0@E?IWoggE zriEQI+a|y$Y+cRnuAVL)&N097^x3RdP`(kYX@0Ff`CLG}1LN3^6jWGBLI?G1WFOwK6c6_fBsWiiX_$l+3hB ihz0{oum+%N6DtEVh=x 0) { + if(!_.every(cols.pluck('colname'))) { + msg = '{{ _('You must specify column name.') }}'; + this.errorModel.set('columns', msg); + return msg; + } + } else if(cols){ + msg = '{{ _('You must specify at least one column.') }}'; + this.errorModel.set('columns', msg); + return msg; + } + return null; + }, + // We will check if we are under schema node & in 'create' mode + inSchema: function() { + if(this.node_info && 'catalog' in this.node_info) { + return true; + } + return false; + }, + // We will check if we are under schema node & in 'create' mode + inSchemaWithModelCheck: function(m) { + if(this.node_info && 'schema' in this.node_info) { + // We will disable control if it's in 'edit' mode + if (m.isNew()) { + return false; + } else { + return true; + } + } + return true; + }, + // Checks weather to enable/disable control + inSchemaWithColumnCheck: function(m) { + if(this.node_info && 'schema' in this.node_info) { + // We will disable control if it's system columns + // ie: it's position is less then 1 + if (m.isNew()) { + return false; + } else { + // if we are in edit mode + if (!_.isUndefined(m.get('attnum')) && m.get('attnum') >= 1 ) { + return false; + } else { + return true; + } + } + } + return true; + } + }), + // Below function will enable right click menu for creating column + canCreate: function(itemData, item, data) { + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData, parents = []; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to c reate table + if (_.indexOf(['schema'], d._type) > -1) + return true; + parents.push(d._type); + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // If node is under catalog then do not allow 'create' menu + if (_.indexOf(parents, 'catalog') > -1) { + return false; + } else { + return true; + } + } + }); + } + + return pgBrowser.Nodes['index']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/__init__.py new file mode 100644 index 000000000..f442338a8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/__init__.py @@ -0,0 +1,497 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements Rule Node""" + +import json +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +import pgadmin.browser.server_groups.servers.databases.schemas as schemas +from pgadmin.browser.server_groups.servers.databases.schemas.utils import \ + parse_rule_definition +from pgadmin.browser.collection import CollectionNodeModule +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps + + +class RuleModule(CollectionNodeModule): + """ + class RuleModule(CollectionNodeModule): + + A rule collection Node which inherits CollectionNodeModule + class and define methods: + get_nodes - To generate collection node. + script_load - tells when to load js file. + csssnppets - add css to page + """ + NODE_TYPE = 'rule' + COLLECTION_LABEL = gettext("Rules") + + def __init__(self, *args, **kwargs): + self.min_ver = None + self.max_ver = None + + super(RuleModule, self).__init__(*args, **kwargs) + + def BackendSupported(self, manager, **kwargs): + """ + Load this module if tid is view, we will not load it under + material view + """ + if super(RuleModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection(did=kwargs['did']) + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + if 'vid' not in kwargs: + return True + + self.template_path = 'rules/sql' + SQL = render_template("/".join( + [self.template_path, 'backend_support.sql'] + ), vid=kwargs['vid']) + status, res = conn.execute_scalar(SQL) + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check tid is view not material view + # then true, othewise false + if res is True: + return res + else: + return res + + def get_nodes(self, gid, sid, did, scid, **kwargs): + """ + Generate the collection node + """ + assert('tid' in kwargs or 'vid' in kwargs) + yield self.generate_browser_collection_node( + kwargs['tid'] if 'tid' in kwargs else kwargs['vid'] + ) + + @property + def node_inode(self): + """ + If a node has children return True otherwise False + """ + return False + + @property + def script_load(self): + """ + Load the module script for rule, when any of the database nodes are + initialized. + """ + return schemas.SchemaModule.NODE_TYPE + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "browser/css/collection.css", + node_type=self.node_type, + _=gettext + ), + render_template( + "rules/css/rule.css", + node_type=self.node_type, + _=gettext + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + + +# Create blueprint of RuleModule. +blueprint = RuleModule(__name__) + + +class RuleView(PGChildNodeView): + """ + This is a class for rule node which inherits the + properties and methods from PGChildNodeView class and define + various methods to list, create, update and delete rule. + + Variables: + --------- + * node_type - tells which type of node it is + * parent_ids - id with its type and name of parent nodes + * ids - id with type and name of extension module being used. + * operations - function routes mappings defined. + """ + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [ + {'type': 'int', 'id': 'rid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'children': [{ + 'get': 'children' + }], + 'delete': [{'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'configs': [{'get': 'configs'}] + }) + + def module_js(self): + """ + This property defines whether Javascript exists for this node. + """ + return make_response( + render_template( + "rules/js/rules.js", + _=gettext + ), + 200, {'Content-Type': 'application/x-javascript'} + ) + + def check_precondition(f): + """ + This function will behave as a decorator which will check the + database connection before running a view. It will also attach + manager, conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver( + PG_DEFAULT_DRIVER).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + self.template_path = 'rules/sql' + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid, tid): + """ + Fetch all rule properties and render into properties tab + """ + + # fetch schema name by schema id + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), tid=tid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, tid): + """ + List all the rules under the Rules Collection node + """ + res = [] + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), tid=tid) + + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon="icon-rule" + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, scid, tid, rid): + """ + Fetch the properties of an individual rule and render in properties tab + + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql'] + ), rid=rid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + return ajax_response( + response=parse_rule_definition(res), + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid, tid): + """ + This function will create a new rule object + """ + required_args = [ + 'name', + ] + + data = request.form if request.form else \ + json.loads(request.data.decode()) + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Couldn't find the required parameter (%s)." % arg + ) + ) + try: + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # Fetch the rule id against rule name to display node + # in tree browser + SQL = render_template("/".join( + [self.template_path, 'rule_id.sql']), rule_name=data['name']) + status, rule_id = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=rule_id) + return jsonify( + node=self.blueprint.generate_browser_node( + rule_id, + tid, + data['name'], + icon="icon-rule" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, tid, rid): + """ + This function will update a rule object + """ + data = request.form if request.form else \ + json.loads(request.data.decode()) + SQL = self.getSQL(gid, sid, data, tid, rid) + try: + if SQL and SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + return make_json_response( + success=1, + info=gettext("Rule updated"), + data={ + 'id': tid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': tid, + 'scid': scid, + 'did': did + } + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, rid): + """ + This function will drop a rule object + """ + # Below will decide if it's simple drop or drop with cascade call + cascade = True if self.cmd == 'delete' else False + + try: + # Get name for rule from did + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), rid=rid) + status, res_data = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res_data) + # drop rule + rset = res_data['rows'][0] + SQL = render_template("/".join( + [self.template_path, 'delete.sql']), + rulename=rset['rulename'], + relname=rset['relname'], + nspname=rset['nspname'], + cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Rule dropped"), + data={ + 'id': tid, + 'sid': sid, + 'gid': gid, + 'did': did + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, tid, rid=None): + """ + This function returns modified SQL + """ + data = request.args + SQL = self.getSQL(gid, sid, data, tid, rid) + return make_json_response( + data=SQL, + status=200 + ) + + @check_precondition + def sql(self, gid, sid, did, scid, tid, rid): + """ + This function will generate sql to render into the sql panel + """ + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), rid=rid) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + res_data = parse_rule_definition(res) + SQL = render_template("/".join( + [self.template_path, 'create.sql']), + data=res_data, display_comments=True) + + return ajax_response(response=SQL) + + def getSQL(self, gid, sid, data, tid, rid): + """ + This function will generate sql from model data + """ + try: + if rid is not None: + SQL = render_template("/".join( + [self.template_path, 'properties.sql']), rid=rid) + status, res = self.conn.execute_dict(SQL) + res_data = [] + res_data = parse_rule_definition(res) + if not status: + return internal_server_error(errormsg=res) + old_data = res_data + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data + ) + else: + SQL = render_template("/".join( + [self.template_path, 'create.sql']), data=data) + return SQL + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, tid, rid): + """ + This function gets the dependents and returns an ajax response + for the rule node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + tid: View ID + rid: Rule ID + """ + dependents_result = self.get_dependents(self.conn, rid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid, rid): + """ + This function gets the dependencies and returns sn ajax response + for the rule node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + tid: View ID + rid: Rule ID + """ + dependencies_result = self.get_dependencies(self.conn, rid) + return ajax_response( + response=dependencies_result, + status=200 + ) + +RuleView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/img/coll-rule.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/img/coll-rule.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe6ed27d98351a03e98451fe83e785aeb51351f GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!Q-Dv1E0BJDrt0oW+5b=H{C_&@ z|C4F|pG^AyxbOed8ULTo{{Ljk|Hl*lKkj*b(Es0~_J5B$|2=B`_pssL!=`@^>;65g z`S+m8Y=`zEpec+cL4Lsu4$p3+0Xdun9+AaB+5?Q;PG;Ky8Bv}tjv*44r}laCH7M}7 zJY3k(rBU~;KGd5<=#Js+WtXq}O?@?enTE6ko10E>S;3Z`GiP+(9r|ThaVJc2?wlhg ziC9V-ADTyViR>?)F zK#IZ0z|cU~&`8(7FvQ5f%EZ{p#8lhB)XKnM-aEZjC>nC}Q!>*kAsP%U!5V<7O{@&e WAR10h4_yP)z~JfX=d#Wzp$P!7cZ;e3 literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/img/rule.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/rules/static/img/rule.png new file mode 100644 index 0000000000000000000000000000000000000000..8b4978090e33bbe3d5f7ed249fd93c2da34f52d6 GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}cz{ocE0DgsQug(ks{c>t{C_(0 z|C1^IANT%y)b{UT{r{)4{y&-e|8f7nNA3R}HvW51{r~Cg|4*j>e>~yeqt1U1oBlnh z`S+mw|I-=&pG^AyxclFumVXax|2?Sq_n>kK%jYzp(TpWQe!&b5&u)M?oCO|{#X#Bv zjNMLV+W{G&o-U3d5|`KZnTs_T@Hi`bDEDUl`g^`}GN)YjKDBz@O#-F|X1Becro3=L zVnUz`J9~da#}rq6_MWOkIpP6gt8f32>#JDS-4zfcy)J)|(ya4dQ}_S3nP9`r$iw)~ zA83JUiEBhjN@7W>RdP`(kYX@0Ff`CLG}1LN3^6jWGBLI?G1WFOwK6c6_fBsWiiX_$ ml+3hBhz0{oum+%N6DtEVh=x= 90400) { + return false; + } + return true; + } + }, + { + id: 'oid', label:'{{ _("OID") }}', + type: 'text', disabled: true, mode: ['properties'] + }, + { + id: 'schema', label:'{{ _("") }}', + type: 'text', visible: false, disabled: function(m) { + // It is used while generating sql + m.set('schema', m.node_info.schema.label); + } + }, + { + id: 'view', label:'{{ _("") }}', + type: 'text', visible: false, disabled: function(m){ + + // It is used while generating sql + m.set('view', this.node_data.label); + } + }, + { + id: 'event', label:'{{ _("Event") }}', control: 'select2', + group: '{{ _("Definition") }}', type: 'text', + select2: { + width: '100%', + allowClear: false + }, + options:[ + {label: 'Select', value: 'Select'}, + {label: 'Insert', value: 'Insert'}, + {label: 'Update', value: 'Update'}, + {label: 'Delete', value: 'Delete'} + ] + }, + { + id: 'do_instead', label:'{{ _("Do Instead") }}', group: '{{ _("Definition") }}', + type: 'switch' + }, + { + id: 'condition', label:'{{ _("Condition") }}', + type: 'text', group: '{{ _("Definition") }}', + control: Backform.SqlFieldControl + }, + { + id: 'statements', label:'{{ _("Commands") }}', + type: 'text', group: '{{ _("Definition") }}', + control: Backform.SqlFieldControl + }, + { + id: 'system_rule', label:'{{ _("System rule?") }}', + type: 'switch', mode: ['properties'] + }, + { + id: 'enabled', label:'{{ _("Enabled?") }}', + type: 'switch', mode: ['properties'] + }, + { + id: 'comment', label:'{{ _("Comment") }}', cell: 'string', type: 'multiline' + } + ], + validate: function() { + + // Triggers specific error messages for fields + var err = {}, + errmsg, + field_name = this.get('name'); + if (_.isUndefined(field_name) || _.isNull(field_name) || + String(field_name).replace(/^\s+|\s+$/g, '') === '') + { + err['name'] = '{{ _("Please specify name.") }}'; + errmsg = errmsg || err['name']; + this.errorModel.set('name', errmsg); + return errmsg; + } + else + { + this.errorModel.unset('name'); + } + return null; + } + }), + + // Show or hide create rule menu option on parent node + canCreate: function(itemData, item, data) { + + // If check is false then , we will allow create menu + if (data && data.check === false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + + // To iterate over tree to check parent node + while (i) { + + // If it is schema then allow user to create rule + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-rule' == d._type) { + + //Check if we are not child of rule + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + prev_j = t.hasParent(prev_i) ? t.parent(prev_i) : null; + prev_e = prev_j ? t.itemData(prev_j) : null; + prev_k = t.hasParent(prev_j) ? t.parent(prev_j) : null; + prev_f = prev_k ? t.itemData(prev_k) : null; + if( prev_f._type == 'catalog') { + return false; + } else { + return true; + } + } + + /** + Check if it is view and its parent node is schema + then allow to create Rule + */ + else if('view' == d._type){ + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + prev_j = t.hasParent(prev_i) ? t.parent(prev_i) : null; + prev_e = prev_j ? t.itemData(prev_j) : null; + if(prev_e._type == 'schema') { + return true; + }else{ + return false; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + + // By default we do not want to allow create menu + return true; + + } + + }); + } + + return pgBrowser.Nodes['coll-rule']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/coll-table.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/coll-table.png new file mode 100644 index 0000000000000000000000000000000000000000..680e86457e8c48cd01329f0303d663b0304ab44d GIT binary patch literal 555 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMf-Te%o613!~@ehb_5 z*>m*=+of*}=e<^+`SRYqd(Ef+dI3e2yw#Zb^8Wq%j~+dG{P^+Hr%&4tJb&=u!SdzH z*Q{Cd=FOXT@7}$9`SRhzhs%~NTd`ur+O=!fuV4S_)vF1oK0kl{{PpYCFJ8P@xpL+E z_wPS`{P_0m+f}PpZP>73)22;No;>;R;X^gZ0Y5ZmzF7BH^&ij!j3q&S!3+-1ZlnP@ zoCO|{#X#BvjNMLV+W{F%JzX3_BreCEKQ7i}Akg}-$5cydO$Teo7MG=+N#Fmg3_MdIbtj55z467wYr(8Tf`DM;NgJ}*2Ts9xt za8DvyRL7)e=Crp3yMhn8I2OC7#SEE=o%X78W@Hc8CaPZTbY<@8<<)d7|eU8w+cl=ZhlH;S|vn-fhAZ2 cP_>Dbff+=@sp+9>fEpM)UHx3vIVCg!0JlsKi2wiq literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table-repl-sm.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table-repl-sm.png new file mode 100644 index 0000000000000000000000000000000000000000..967bd937fb89c483a95146c88fb4c066f6da5711 GIT binary patch literal 675 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47>xpaLR^7d#i`G`&i$Km{^#1O zpHAI*b?@=>XV0HMd-m+;jb~*$Ul`1J(Q)=)^XY$8$A0G@_?f)(TiB-0o~u9DE`4h_ z@3s2Om(8dDdI7mh-fGNzx%ukbyLa#2yLa#Y{ris|J$n53@zbYI+YdaSw90wIwfqwo z*FAoGfBDMAH*a5Fvu4ekH*em(d-w9?%PH&JH{32+bvgH?KAH>pEkLwcYDb6lTnjSMzn9Td;a|S>({S~kKHdkaxd?|-Ppw^%jf&_9uI9h z=v%+m{Qdj)A3uJ4`}S?Y;d{CJ?}SW0kv_e={eWNN9=GZpj#VoSH*DCjY15{heRqN; z9}AyzIO^#`_{y}aw(m0jnr?*8!ML;jpIsdLYCufG;M>s0K@OF`34WUhOff8dA4 z%okCUjx~DRx&#b0#*!evU+O&yNCx<@>XyMS% zT)kq|%G22yS((l(B_&_K@bZY7^z`^PFfCiQEbW=tw6JS3X1CO~DKJ`Bv%9OOi-$`m zo0s!XXjnddd;j|R^$pt!6DEjxC|IbN=-4P3oeI0`tSzM>AS^9Df5MC@JZ0`&N5tB$ zgxJ`zGT48TG(363Ckg0g)e_f;l9a@fRIB8oR3OD*WMF8ZYiOivU>IU#U}a)#Wn!vr zU}|MxFz=n-DijU5`6-!cl@JXEmS7D))h1R3W)KahriZQpYGCkm^>bP0l+XkKRw_GR literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table-repl.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table-repl.png new file mode 100644 index 0000000000000000000000000000000000000000..2a082c67b8c307b673d32fc0df12fb5e857fc1f7 GIT binary patch literal 839 zcmV-N1GxN&P)EGk$<>u$+=H}Yr<*d%a^DG-QC^Z-rnEe-{9cj z;o;%p;^O1u9WCpoxM|ysXD2tspRD3tgNi;?CkCB?eFjJ>gwu{ zsX?E@R!@hogbGU53F;fxbw4xv*<+N)Ih*5}x!G5j$xe;LSDnnO!`mg6=3t=LR+PtVpU^d$&@U_=27 zKmjIZ7FITP4o*%kZXRBc03W}AppdYLsF=8fBv=KXlz^Z#P(Vgj4k940prELvtfH!> z&Zz+wK=3uSzyjJjx|({b`UZwZ#wK6^Q!{f5ODk&|TRS^@uz-W3le3Gfo4b~Yp%Ej4 zho_g9m$#3vpTE0tKwuD1AUGs6EIcAID%w3JHZ}k#5Eq}2n3SB7n&zG!n2`w-h|kK- z$<50z$Sce&DlP&Fl$4g0S5(FZq*qnf)YLLE)YUgMHZ`}jg47q))`G2VYwzgnf_WbR zhg&kzn38;O0000bbVXQnWMOn=I%9HWVRU5xGB7bPEip1JFfmjzFgi3dIy5yaFf}?b zFrMx%ssI20C3HntbYx+4WjbwdWNBu305UK!FfA}SEif@uGBY|fG&(RgD=;-WFfhuO RRjdF2002ovPDHLkV1h^asowwq literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/img/table.png new file mode 100644 index 0000000000000000000000000000000000000000..37b2227d8fe0cf76bde23855676403c57ccfb569 GIT binary patch literal 593 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfjR2nzS0Jr8^?C1w|C7%DUUd2E zt{ZPJ-hX-j@$;w8oT%hof0v-kaQT=vdv;Tyd3Q5-o1ObdiClJ8#Zj*xN*~_O&>pg{QUXz*RNl;OkH}&m?E%JaC$sHdY(w2yI*ETJB#CxDVyM_6hsq?*{ z%bR2yj^^b{bb0Zwd(Bu=J=bCx>jtGG3nR*NpRqG!KlWJZ=J(tnh2i}TIU#U}a)#Wn!vrU}|MxFz=n-DijU5`6-!cl@JXEmS7D))h1R3W)Kah TriZQpYGCkm^>bP0l+XkKw!A9P literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/alter.sql new file mode 100644 index 000000000..0fb0ea5b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/alter.sql @@ -0,0 +1,4 @@ +{% if data.comment %} +COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/create.sql new file mode 100644 index 000000000..8d8c10aed --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/create.sql @@ -0,0 +1,4 @@ +{% if data %} +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} CHECK ({{ data.consrc }}); +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..5a85b4f74 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/delete.sql @@ -0,0 +1,3 @@ +{% if data %} +ALTER TABLE {{ conn|qtIdent(data.nspname, data.relname) }} DROP CONSTRAINT {{ conn|qtIdent(data.name) }}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_name.sql new file mode 100644 index 000000000..12dfa1585 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_name.sql @@ -0,0 +1,4 @@ +SELECT conname as name +FROM pg_constraint ct +WHERE contype = 'c' +AND ct.oid = {{cid}}::oid \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_oid.sql new file mode 100644 index 000000000..fa521e642 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_oid.sql @@ -0,0 +1,7 @@ +SELECT + oid, conname as name +FROM + pg_constraint +WHERE + conrelid = {{tid}}::oid + AND conname={{ name|qtLiteral }}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_oid_with_transaction.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_oid_with_transaction.sql new file mode 100644 index 000000000..0f2e29f59 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_oid_with_transaction.sql @@ -0,0 +1,5 @@ +SELECT ct.oid, + ct.conname as name +FROM pg_constraint ct +WHERE contype='c' AND + conrelid = {{tid}}::oid LIMIT 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_parent.sql new file mode 100644 index 000000000..a65285787 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/get_parent.sql @@ -0,0 +1,7 @@ +SELECT nsp.nspname AS schema, + rel.relname AS table +FROM + pg_class rel +JOIN pg_namespace nsp +ON rel.relnamespace = nsp.oid::int +WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/nodes.sql new file mode 100644 index 000000000..0701c9f78 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/nodes.sql @@ -0,0 +1,6 @@ +SELECT c.oid, conname as name + FROM pg_constraint c +WHERE contype = 'c' +{% if tid %} + AND conrelid = {{ tid }}::oid +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..18cdb35e3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/properties.sql @@ -0,0 +1,13 @@ +SELECT c.oid, conname as name, relname, nspname, description as comment , + pg_get_expr(conbin, conrelid, true) as consrc + FROM pg_constraint c + JOIN pg_class cl ON cl.oid=conrelid + JOIN pg_namespace nl ON nl.oid=relnamespace +LEFT OUTER JOIN + pg_description des ON (des.objoid=c.oid AND + des.classoid='pg_constraint'::regclass) +WHERE contype = 'c' + AND conrelid = {{ tid }}::oid +{% if cid %} + AND c.oid = {{ cid }}::oid +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/update.sql new file mode 100644 index 000000000..57d81c7fc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.1_plus/update.sql @@ -0,0 +1,4 @@ +{% if data.comment is defined and data.comment != o_data.comment %} +COMMENT ON CONSTRAINT {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/alter.sql new file mode 100644 index 000000000..0fb0ea5b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/alter.sql @@ -0,0 +1,4 @@ +{% if data.comment %} +COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/create.sql new file mode 100644 index 000000000..2c7a57f4e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/create.sql @@ -0,0 +1,6 @@ +{% if data %} +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} CHECK ({{ data.consrc }}){% if data.convalidated %} + + NOT VALID{% endif %}{% if data.connoinherit %} NO INHERIT{% endif %}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/delete.sql new file mode 100644 index 000000000..5a85b4f74 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/delete.sql @@ -0,0 +1,3 @@ +{% if data %} +ALTER TABLE {{ conn|qtIdent(data.nspname, data.relname) }} DROP CONSTRAINT {{ conn|qtIdent(data.name) }}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_name.sql new file mode 100644 index 000000000..a92f89304 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_name.sql @@ -0,0 +1,5 @@ +SELECT conname as name, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype = 'c' +AND ct.oid = {{cid}}::oid \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_oid.sql new file mode 100644 index 000000000..46f32c9bc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_oid.sql @@ -0,0 +1,8 @@ +SELECT + oid, conname as name, + NOT convalidated as convalidated +FROM + pg_constraint +WHERE + conrelid = {{tid}}::oid + AND conname={{ name|qtLiteral }}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_oid_with_transaction.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_oid_with_transaction.sql new file mode 100644 index 000000000..b497e3fa1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_oid_with_transaction.sql @@ -0,0 +1,6 @@ +SELECT ct.oid, + ct.conname as name, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype='c' AND + conrelid = {{tid}}::oid LIMIT 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_parent.sql new file mode 100644 index 000000000..a65285787 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/get_parent.sql @@ -0,0 +1,7 @@ +SELECT nsp.nspname AS schema, + rel.relname AS table +FROM + pg_class rel +JOIN pg_namespace nsp +ON rel.relnamespace = nsp.oid::int +WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/nodes.sql new file mode 100644 index 000000000..6fdc3c103 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/nodes.sql @@ -0,0 +1,7 @@ +SELECT c.oid, conname as name, + NOT convalidated as convalidated + FROM pg_constraint c +WHERE contype = 'c' +{% if tid %} + AND conrelid = {{ tid }}::oid +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/properties.sql new file mode 100644 index 000000000..509d31793 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/properties.sql @@ -0,0 +1,14 @@ +SELECT c.oid, conname as name, relname, nspname, description as comment, + pg_get_expr(conbin, conrelid, true) as consrc, + connoinherit, NOT convalidated as convalidated + FROM pg_constraint c + JOIN pg_class cl ON cl.oid=conrelid + JOIN pg_namespace nl ON nl.oid=relnamespace +LEFT OUTER JOIN + pg_description des ON (des.objoid=c.oid AND + des.classoid='pg_constraint'::regclass) +WHERE contype = 'c' + AND conrelid = {{ tid }}::oid +{% if cid %} + AND c.oid = {{ cid }}::oid +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/update.sql new file mode 100644 index 000000000..6c27923ff --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/update.sql @@ -0,0 +1,13 @@ +{% if data %} +{% if data.name != o_data.name %} +ALTER TABLE {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + RENAME CONSTRAINT {{ conn|qtIdent(o_data.name) }} TO {{ conn|qtIdent(data.name) }};{% endif -%} +{% if 'convalidated' in data and o_data.convalidated != data.convalidated and not data.convalidated %} + +ALTER TABLE {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + VALIDATE CONSTRAINT {{ conn|qtIdent(data.name) }};{% endif -%} +{% if data.comment is defined and data.comment != o_data.comment %} + +COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{ data.comment|qtLiteral }};{% endif %} +{% endif -%} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/validate.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/validate.sql new file mode 100644 index 000000000..5a62c801a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/check_constraint/sql/9.2_plus/validate.sql @@ -0,0 +1,2 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + VALIDATE CONSTRAINT {{ conn|qtIdent(data.name) }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/macros/privilege.macros b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/macros/privilege.macros new file mode 100644 index 000000000..7eafd60f0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/macros/privilege.macros @@ -0,0 +1,13 @@ +{% macro APPLY(conn, schema_name, table_object, column_object, role, privs, with_grant_privs) -%} +{% if privs %} +GRANT {% for p in privs %}{% if loop.index != 1 %}, {% endif %}{{p}}({{conn|qtIdent(column_object)}}){% endfor %} + ON {{ conn|qtIdent(schema_name, table_object) }} TO {{ conn|qtIdent(role) }}; +{% endif %} +{% if with_grant_privs %} +GRANT {% for p in with_grant_privs %}{% if loop.index != 1 %}, {% endif %}{{p}}({{conn|qtIdent(column_object)}}){% endfor %} + ON {{ conn|qtIdent(schema_name, table_object) }} TO {{ conn|qtIdent(role) }} WITH GRANT OPTION; +{% endif %} +{%- endmacro %} +{% macro RESETALL(conn, schema_name, table_object, column_object, role) -%} +REVOKE ALL({{ conn|qtIdent(column_object) }}) ON {{ conn|qtIdent(schema_name, table_object) }} FROM {{ conn|qtIdent(role) }}; +{%- endmacro %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/macros/security.macros b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/macros/security.macros new file mode 100644 index 000000000..39587c32b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/macros/security.macros @@ -0,0 +1,6 @@ +{% macro APPLY(conn, type, schema_name, parent_object, child_object, provider, label) -%} +SECURITY LABEL FOR {{ conn|qtIdent(provider) }} ON {{ type }} {{ conn|qtIdent(schema_name, parent_object, child_object) }} IS {{ label|qtLiteral }}; +{%- endmacro %} +{% macro DROP(conn, type, schema_name, parent_object, child_object, provider) -%} +SECURITY LABEL FOR {{ conn|qtIdent(provider) }} ON {{ type }} {{ conn|qtIdent(schema_name, parent_object, child_object) }} IS NULL; +{%- endmacro %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/acl.sql new file mode 100644 index 000000000..5c44a96c4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/acl.sql @@ -0,0 +1,34 @@ +SELECT 'attacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname 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 attacl + FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attnum = {{clid}}::int + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable + AS is_grantable, (d).privilege_type AS privilege_type FROM (SELECT + aclexplode(attacl) as d FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attnum = {{clid}}::int) 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 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/create.sql new file mode 100644 index 000000000..51eea522b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/create.sql @@ -0,0 +1,38 @@ +{% import 'column/macros/security.macros' as SECLABLE %} +{% import 'column/macros/privilege.macros' as PRIVILEGE %} +{% import 'macros/variable.macros' as VARIABLE %} +{### Add column ###} +{% if data.name and data.cltype %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ADD COLUMN {{conn|qtIdent(data.name)}} {{data.cltype}}{% if data.attlen %} +({{data.attlen}}{% if data.attprecision%}, {{data.attprecision}}{% endif %}){% endif %}{% if data.hasSqrBracket %} +[]{% endif %}{% if data.collspcname %} + COLLATE {{data.collspcname}}{% endif %}{% if data.attnotnull %} + NOT NULL{% endif %}{% if data.defval %} + DEFAULT {{data.defval}}{% endif %}; + +{% endif %} +{### Add comments ###} +{% if data and data.description %} +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.table, data.name)}} + IS {{data.description|qtLiteral}}; + +{% endif %} +{### Add variables to column ###} +{% if data.attoptions %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, data.attoptions) }} + +{% endif %} +{### ACL ###} +{% if data.attacl %} +{% for priv in data.attacl %} +{{ PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{### Security Lables ###} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'COLUMN',data.schema, data.table, data.name, r.provider, r.label) }} +{% endfor %} +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..0e16251b6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/delete.sql @@ -0,0 +1 @@ +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} DROP COLUMN {{conn|qtIdent(data.name)}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/depend.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/depend.sql new file mode 100644 index 000000000..f5f39e7d0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/depend.sql @@ -0,0 +1,9 @@ +SELECT + ref.relname AS refname, d2.refclassid, dep.deptype AS deptype +FROM pg_depend dep + LEFT JOIN pg_depend d2 ON dep.objid=d2.objid AND dep.refobjid != d2.refobjid + LEFT JOIN pg_class ref ON ref.oid=d2.refobjid + LEFT JOIN pg_attribute att ON d2.refclassid=att.attrelid AND d2.refobjsubid=att.attnum + {{ where }} AND + dep.classid=(SELECT oid FROM pg_class WHERE relname='pg_attrdef') AND + dep.refobjid NOT IN (SELECT d3.refobjid FROM pg_depend d3 WHERE d3.objid=d2.refobjid) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/edit_mode_types.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/edit_mode_types.sql new file mode 100644 index 000000000..8bc63853c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/edit_mode_types.sql @@ -0,0 +1,5 @@ +SELECT tt.oid, format_type(tt.oid,NULL) AS typname + FROM pg_cast + JOIN pg_type tt ON tt.oid=casttarget +WHERE castsource={{type_id}} + AND castcontext IN ('i', 'a') \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_collations.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_collations.sql new file mode 100644 index 000000000..803c4d4aa --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_collations.sql @@ -0,0 +1,7 @@ +SELECT --nspname, collname, + CASE WHEN length(nspname) > 0 AND length(collname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(collname)) + ELSE '' END AS collation +FROM pg_collation c, pg_namespace n + WHERE c.collnamespace=n.oid +ORDER BY nspname, collname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_inherited_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_inherited_tables.sql new file mode 100644 index 000000000..37934b8e4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_inherited_tables.sql @@ -0,0 +1,12 @@ +SELECT array_to_string(array_agg(inhrelname), ', ') inhrelname, attrname +FROM + (SELECT + inhparent::regclass AS inhrelname, + a.attname AS attrname + FROM pg_inherits i + LEFT JOIN pg_attribute a ON + (attrelid = inhparent AND attnum > 0) + WHERE inhrelid = {{tid}}::oid + ORDER BY inhseqno + ) a +GROUP BY attrname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_parent.sql new file mode 100644 index 000000000..5dd5d3ce0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_parent.sql @@ -0,0 +1,5 @@ +SELECT nsp.nspname AS schema ,rel.relname AS table +FROM pg_class rel + JOIN pg_namespace nsp + ON rel.relnamespace = nsp.oid::int + WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_position.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_position.sql new file mode 100644 index 000000000..cea57210a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_position.sql @@ -0,0 +1,4 @@ +SELECT att.attnum +FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attname = {{data.name|qtLiteral}} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_types.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_types.sql new file mode 100644 index 000000000..469096c3b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/get_types.sql @@ -0,0 +1,14 @@ +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 typisdefined AND typtype IN ('b', 'c', 'd', 'e', 'r') + AND NOT EXISTS (select 1 from pg_class where relnamespace=typnamespace and relname = typname and relkind != 'c') + AND (typname not like '_%' OR NOT EXISTS (select 1 from pg_class where relnamespace=typnamespace and relname = substring(typname from 2)::name and relkind != 'c')) + AND nsp.nspname != 'information_schema' + ) AS dummy + ORDER BY nspname <> 'pg_catalog', nspname <> 'public', nspname, 1 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/is_referenced.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/is_referenced.sql new file mode 100644 index 000000000..7d0bfc367 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/is_referenced.sql @@ -0,0 +1,5 @@ +SELECT COUNT(1) +FROM pg_depend dep + JOIN pg_class cl ON dep.classid=cl.oid AND relname='pg_rewrite' + WHERE refobjid= {{tid}}::oid + AND refobjsubid= {{clid|qtLiteral}}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/nodes.sql new file mode 100644 index 000000000..36ed8f63c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/nodes.sql @@ -0,0 +1,18 @@ +SELECT att.attname as name, att.attnum as OID, format_type(ty.oid,NULL) AS datatype +FROM pg_attribute att + JOIN pg_type ty ON ty.oid=atttypid + JOIN pg_namespace tn ON tn.oid=ty.typnamespace + JOIN pg_class cl ON cl.oid=att.attrelid + JOIN pg_namespace na ON na.oid=cl.relnamespace + LEFT OUTER JOIN pg_type et ON et.oid=ty.typelem + LEFT OUTER JOIN pg_attrdef def ON adrelid=att.attrelid AND adnum=att.attnum + LEFT OUTER JOIN (pg_depend JOIN pg_class cs ON objid=cs.oid AND cs.relkind='S') ON refobjid=att.attrelid AND refobjsubid=att.attnum + LEFT OUTER JOIN pg_namespace ns ON ns.oid=cs.relnamespace + LEFT OUTER JOIN pg_index pi ON pi.indrelid=att.attrelid AND indisprimary +WHERE att.attrelid = {{tid}}::oid +{### To show system objects ###} +{% if not show_sys_objects %} + AND att.attnum > 0 +{% endif %} + AND att.attisdropped IS FALSE + ORDER BY att.attnum diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..d53690637 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/properties.sql @@ -0,0 +1,45 @@ +SELECT att.attname as name, att.*, def.*, pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS defval, + CASE WHEN att.attndims > 0 THEN 1 ELSE 0 END AS isarray, + format_type(ty.oid,NULL) AS typname, + format_type(ty.oid,att.atttypmod) AS displaytypname, + tn.nspname as typnspname, et.typname as elemtypname, + ty.typstorage AS defaultstorage, cl.relname, na.nspname, + concat(quote_ident(na.nspname) ,'.', quote_ident(cl.relname)) AS parent_tbl, + att.attstattarget, description, cs.relname AS sername, + ns.nspname AS serschema, + (SELECT count(1) FROM pg_type t2 WHERE t2.typname=ty.typname) > 1 AS isdup, + indkey, coll.collname, nspc.nspname as collnspname , attoptions, + -- Start pgAdmin4, added to save time on client side parsing + CASE WHEN length(coll.collname) > 0 AND length(nspc.nspname) > 0 THEN + concat(quote_ident(coll.collname),'.',quote_ident(nspc.nspname)) + ELSE '' END AS collspcname, + CASE WHEN strpos(format_type(ty.oid,att.atttypmod), '.') > 0 THEN + split_part(format_type(ty.oid,att.atttypmod), '.', 2) + ELSE format_type(ty.oid,att.atttypmod) END AS cltype, + -- End pgAdmin4 + EXISTS(SELECT 1 FROM pg_constraint WHERE conrelid=att.attrelid AND contype='f' AND att.attnum=ANY(conkey)) As is_fk, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=att.atttypid AND sl1.objsubid=0) AS seclabels, + (CASE WHEN (att.attnum < 1) THEN true ElSE false END) AS is_sys_column +FROM pg_attribute att + JOIN pg_type ty ON ty.oid=atttypid + JOIN pg_namespace tn ON tn.oid=ty.typnamespace + JOIN pg_class cl ON cl.oid=att.attrelid + JOIN pg_namespace na ON na.oid=cl.relnamespace + LEFT OUTER JOIN pg_type et ON et.oid=ty.typelem + LEFT OUTER JOIN pg_attrdef def ON adrelid=att.attrelid AND adnum=att.attnum + LEFT OUTER JOIN pg_description des ON (des.objoid=att.attrelid AND des.objsubid=att.attnum AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN (pg_depend JOIN pg_class cs ON objid=cs.oid AND cs.relkind='S') ON refobjid=att.attrelid AND refobjsubid=att.attnum + LEFT OUTER JOIN pg_namespace ns ON ns.oid=cs.relnamespace + LEFT OUTER JOIN pg_index pi ON pi.indrelid=att.attrelid AND indisprimary + LEFT OUTER JOIN pg_collation coll ON att.attcollation=coll.oid + LEFT OUTER JOIN pg_namespace nspc ON coll.collnamespace=nspc.oid +WHERE att.attrelid = {{tid}}::oid +{% if clid %} + AND att.attnum = {{clid}}::int +{% endif %} +{### To show system objects ###} +{% if not show_sys_objects %} + AND att.attnum > 0 +{% endif %} + AND att.attisdropped IS FALSE + ORDER BY att.attnum diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/update.sql new file mode 100644 index 000000000..9501a18a2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.1_plus/update.sql @@ -0,0 +1,107 @@ +{% import 'column/macros/security.macros' as SECLABLE %} +{% import 'column/macros/privilege.macros' as PRIVILEGE %} +{% import 'macros/variable.macros' as VARIABLE %} +{### Rename column name ###} +{% if data.name != o_data.name %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + RENAME {{conn|qtIdent(o_data.name)}} TO {{conn|qtIdent(data.name)}}; + +{% endif %} +{### Alter column type and collation ###} +{% if (data.cltype and data.cltype != o_data.cltype) or (data.attlen and data.attlen != o_data.attlen) or (data.attprecision and data.attprecision != o_data.attprecision) %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} TYPE {% if data.cltype %}{{data.cltype}} {% else %}{{o_data.cltype}} {% endif %}{% if data.attlen %} +({{data.attlen}}{% if data.attprecision%}, {{data.attprecision}}{% endif %}){% endif %}{% if data.hasSqrBracket %} +[]{% endif %}{% if data.collspcname and data.collspcname != o_data.collspcname %} + COLLATE {{data.collspcname}}{% endif %}; + +{% endif %} +{### Alter column default value ###} +{% if data.defval and data.defval != o_data.defval %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} SET DEFAULT {{data.defval}}; + +{% endif %} +{### Alter column not null value ###} +{% if 'attnotnull' in data and data.attnotnull != o_data.attnotnull %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} {% if data.attnotnull %}SET{% else %}DROP{% endif %} NOT NULL; + +{% endif %} +{### Alter column statistics value ###} +{% if data.attstattarget and data.attstattarget != o_data.attstattarget %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} SET STATISTICS {{data.attstattarget}}; + +{% endif %} +{### Alter column storage value ###} +{% if data.attstorage and data.attstorage != o_data.attstorage %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} SET STORAGE {%if data.attstorage == 'p' %} +PLAIN{% elif data.attstorage == 'm'%}MAIN{% elif data.attstorage == 'e'%} +EXTERNAL{% elif data.attstorage == 'x'%}EXTENDED{% endif %}; + +{% endif %} +{% if data.description is defined %} +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.table, data.name)}} + IS {{data.description|qtLiteral}}; + +{% endif %} +{### Update column variables ###} +{% if 'attoptions' in data and data.attoptions|length > 0 %} +{% set variables = data.attoptions %} +{% if 'deleted' in variables and variables.deleted|length > 0 %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.UNSET(conn, 'COLUMN', data.name, variables.deleted) }} +{% endif %} +{% if 'added' in variables and variables.added|length > 0 %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, variables.added) }} +{% endif %} +{% if 'changed' in variables and variables.changed|length > 0 %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, variables.changed) }} +{% endif %} + +{% endif %} +{### Update column privileges ###} +{# Change the privileges #} +{% if data.attacl %} +{% if 'deleted' in data.attacl %} +{% for priv in data.attacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, data.schema, data.table, data.name, priv.grantee) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.attacl %} +{% for priv in data.attacl.changed %} +{{ PRIVILEGE.RESETALL(conn, data.schema, data.table, data.name, priv.grantee) }} +{{ PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.attacl %} +{% for priv in data.attacl.added %} +{{ PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{### Uppdate tablespace securitylabel ###} +{# The SQL generated below will change Security Label #} +{% if data.seclabels and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'COLUMN', data.schema, data.table, data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'COLUMN',data.schema, data.table, data.name, r.provider, r.label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'COLUMN',data.schema, data.table, data.name, r.provider, r.label) }} +{% endfor %} +{% endif %} + +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/acl.sql new file mode 100644 index 000000000..5c44a96c4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/acl.sql @@ -0,0 +1,34 @@ +SELECT 'attacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname 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 attacl + FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attnum = {{clid}}::int + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable + AS is_grantable, (d).privilege_type AS privilege_type FROM (SELECT + aclexplode(attacl) as d FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attnum = {{clid}}::int) 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 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/create.sql new file mode 100644 index 000000000..51eea522b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/create.sql @@ -0,0 +1,38 @@ +{% import 'column/macros/security.macros' as SECLABLE %} +{% import 'column/macros/privilege.macros' as PRIVILEGE %} +{% import 'macros/variable.macros' as VARIABLE %} +{### Add column ###} +{% if data.name and data.cltype %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ADD COLUMN {{conn|qtIdent(data.name)}} {{data.cltype}}{% if data.attlen %} +({{data.attlen}}{% if data.attprecision%}, {{data.attprecision}}{% endif %}){% endif %}{% if data.hasSqrBracket %} +[]{% endif %}{% if data.collspcname %} + COLLATE {{data.collspcname}}{% endif %}{% if data.attnotnull %} + NOT NULL{% endif %}{% if data.defval %} + DEFAULT {{data.defval}}{% endif %}; + +{% endif %} +{### Add comments ###} +{% if data and data.description %} +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.table, data.name)}} + IS {{data.description|qtLiteral}}; + +{% endif %} +{### Add variables to column ###} +{% if data.attoptions %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, data.attoptions) }} + +{% endif %} +{### ACL ###} +{% if data.attacl %} +{% for priv in data.attacl %} +{{ PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{### Security Lables ###} +{% if data.seclabels %} +{% for r in data.seclabels %} +{{ SECLABLE.APPLY(conn, 'COLUMN',data.schema, data.table, data.name, r.provider, r.label) }} +{% endfor %} +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/delete.sql new file mode 100644 index 000000000..0e16251b6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/delete.sql @@ -0,0 +1 @@ +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} DROP COLUMN {{conn|qtIdent(data.name)}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/depend.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/depend.sql new file mode 100644 index 000000000..f5f39e7d0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/depend.sql @@ -0,0 +1,9 @@ +SELECT + ref.relname AS refname, d2.refclassid, dep.deptype AS deptype +FROM pg_depend dep + LEFT JOIN pg_depend d2 ON dep.objid=d2.objid AND dep.refobjid != d2.refobjid + LEFT JOIN pg_class ref ON ref.oid=d2.refobjid + LEFT JOIN pg_attribute att ON d2.refclassid=att.attrelid AND d2.refobjsubid=att.attnum + {{ where }} AND + dep.classid=(SELECT oid FROM pg_class WHERE relname='pg_attrdef') AND + dep.refobjid NOT IN (SELECT d3.refobjid FROM pg_depend d3 WHERE d3.objid=d2.refobjid) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/edit_mode_types.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/edit_mode_types.sql new file mode 100644 index 000000000..0c112c568 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/edit_mode_types.sql @@ -0,0 +1,5 @@ +SELECT tt.oid, format_type(tt.oid,NULL) AS typname +FROM pg_cast + JOIN pg_type tt ON tt.oid=casttarget +WHERE castsource={{type_id}} + AND castcontext IN ('i', 'a') \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_collations.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_collations.sql new file mode 100644 index 000000000..7418742e5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_collations.sql @@ -0,0 +1,7 @@ +SELECT --nspname, collname, + CASE WHEN length(nspname) > 0 AND length(collname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(collname)) + ELSE '' END AS collation +FROM pg_collation c, pg_namespace n + WHERE c.collnamespace=n.oid + ORDER BY nspname, collname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_inherited_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_inherited_tables.sql new file mode 100644 index 000000000..37934b8e4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_inherited_tables.sql @@ -0,0 +1,12 @@ +SELECT array_to_string(array_agg(inhrelname), ', ') inhrelname, attrname +FROM + (SELECT + inhparent::regclass AS inhrelname, + a.attname AS attrname + FROM pg_inherits i + LEFT JOIN pg_attribute a ON + (attrelid = inhparent AND attnum > 0) + WHERE inhrelid = {{tid}}::oid + ORDER BY inhseqno + ) a +GROUP BY attrname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_parent.sql new file mode 100644 index 000000000..5dd5d3ce0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_parent.sql @@ -0,0 +1,5 @@ +SELECT nsp.nspname AS schema ,rel.relname AS table +FROM pg_class rel + JOIN pg_namespace nsp + ON rel.relnamespace = nsp.oid::int + WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_position.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_position.sql new file mode 100644 index 000000000..cea57210a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_position.sql @@ -0,0 +1,4 @@ +SELECT att.attnum +FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attname = {{data.name|qtLiteral}} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_types.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_types.sql new file mode 100644 index 000000000..469096c3b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/get_types.sql @@ -0,0 +1,14 @@ +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 typisdefined AND typtype IN ('b', 'c', 'd', 'e', 'r') + AND NOT EXISTS (select 1 from pg_class where relnamespace=typnamespace and relname = typname and relkind != 'c') + AND (typname not like '_%' OR NOT EXISTS (select 1 from pg_class where relnamespace=typnamespace and relname = substring(typname from 2)::name and relkind != 'c')) + AND nsp.nspname != 'information_schema' + ) AS dummy + ORDER BY nspname <> 'pg_catalog', nspname <> 'public', nspname, 1 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/is_referenced.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/is_referenced.sql new file mode 100644 index 000000000..7d0bfc367 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/is_referenced.sql @@ -0,0 +1,5 @@ +SELECT COUNT(1) +FROM pg_depend dep + JOIN pg_class cl ON dep.classid=cl.oid AND relname='pg_rewrite' + WHERE refobjid= {{tid}}::oid + AND refobjsubid= {{clid|qtLiteral}}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/nodes.sql new file mode 100644 index 000000000..7a15333c5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/nodes.sql @@ -0,0 +1,18 @@ +SELECT att.attname as name, att.attnum as OID, format_type(ty.oid,NULL) AS datatype +FROM pg_attribute att + JOIN pg_type ty ON ty.oid=atttypid + JOIN pg_namespace tn ON tn.oid=ty.typnamespace + JOIN pg_class cl ON cl.oid=att.attrelid + JOIN pg_namespace na ON na.oid=cl.relnamespace + LEFT OUTER JOIN pg_type et ON et.oid=ty.typelem + LEFT OUTER JOIN pg_attrdef def ON adrelid=att.attrelid AND adnum=att.attnum + LEFT OUTER JOIN (pg_depend JOIN pg_class cs ON objid=cs.oid AND cs.relkind='S') ON refobjid=att.attrelid AND refobjsubid=att.attnum + LEFT OUTER JOIN pg_namespace ns ON ns.oid=cs.relnamespace + LEFT OUTER JOIN pg_index pi ON pi.indrelid=att.attrelid AND indisprimary +WHERE att.attrelid = {{tid}}::oid + {### To show system objects ###} + {% if not show_sys_objects %} + AND att.attnum > 0 + {% endif %} + AND att.attisdropped IS FALSE + ORDER BY att.attnum diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/properties.sql new file mode 100644 index 000000000..d53690637 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/properties.sql @@ -0,0 +1,45 @@ +SELECT att.attname as name, att.*, def.*, pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS defval, + CASE WHEN att.attndims > 0 THEN 1 ELSE 0 END AS isarray, + format_type(ty.oid,NULL) AS typname, + format_type(ty.oid,att.atttypmod) AS displaytypname, + tn.nspname as typnspname, et.typname as elemtypname, + ty.typstorage AS defaultstorage, cl.relname, na.nspname, + concat(quote_ident(na.nspname) ,'.', quote_ident(cl.relname)) AS parent_tbl, + att.attstattarget, description, cs.relname AS sername, + ns.nspname AS serschema, + (SELECT count(1) FROM pg_type t2 WHERE t2.typname=ty.typname) > 1 AS isdup, + indkey, coll.collname, nspc.nspname as collnspname , attoptions, + -- Start pgAdmin4, added to save time on client side parsing + CASE WHEN length(coll.collname) > 0 AND length(nspc.nspname) > 0 THEN + concat(quote_ident(coll.collname),'.',quote_ident(nspc.nspname)) + ELSE '' END AS collspcname, + CASE WHEN strpos(format_type(ty.oid,att.atttypmod), '.') > 0 THEN + split_part(format_type(ty.oid,att.atttypmod), '.', 2) + ELSE format_type(ty.oid,att.atttypmod) END AS cltype, + -- End pgAdmin4 + EXISTS(SELECT 1 FROM pg_constraint WHERE conrelid=att.attrelid AND contype='f' AND att.attnum=ANY(conkey)) As is_fk, + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=att.atttypid AND sl1.objsubid=0) AS seclabels, + (CASE WHEN (att.attnum < 1) THEN true ElSE false END) AS is_sys_column +FROM pg_attribute att + JOIN pg_type ty ON ty.oid=atttypid + JOIN pg_namespace tn ON tn.oid=ty.typnamespace + JOIN pg_class cl ON cl.oid=att.attrelid + JOIN pg_namespace na ON na.oid=cl.relnamespace + LEFT OUTER JOIN pg_type et ON et.oid=ty.typelem + LEFT OUTER JOIN pg_attrdef def ON adrelid=att.attrelid AND adnum=att.attnum + LEFT OUTER JOIN pg_description des ON (des.objoid=att.attrelid AND des.objsubid=att.attnum AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN (pg_depend JOIN pg_class cs ON objid=cs.oid AND cs.relkind='S') ON refobjid=att.attrelid AND refobjsubid=att.attnum + LEFT OUTER JOIN pg_namespace ns ON ns.oid=cs.relnamespace + LEFT OUTER JOIN pg_index pi ON pi.indrelid=att.attrelid AND indisprimary + LEFT OUTER JOIN pg_collation coll ON att.attcollation=coll.oid + LEFT OUTER JOIN pg_namespace nspc ON coll.collnamespace=nspc.oid +WHERE att.attrelid = {{tid}}::oid +{% if clid %} + AND att.attnum = {{clid}}::int +{% endif %} +{### To show system objects ###} +{% if not show_sys_objects %} + AND att.attnum > 0 +{% endif %} + AND att.attisdropped IS FALSE + ORDER BY att.attnum diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/update.sql new file mode 100644 index 000000000..39e2f3117 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/update.sql @@ -0,0 +1,105 @@ +{% import 'column/macros/security.macros' as SECLABLE %} +{% import 'column/macros/privilege.macros' as PRIVILEGE %} +{% import 'macros/variable.macros' as VARIABLE %} +{### Rename column name ###} +{% if data.name != o_data.name %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + RENAME {{conn|qtIdent(o_data.name)}} TO {{conn|qtIdent(data.name)}}; + +{% endif %} +{### Alter column type and collation ###} +{% if (data.cltype and data.cltype != o_data.cltype) or (data.attlen and data.attlen != o_data.attlen) or (data.attprecision and data.attprecision != o_data.attprecision) %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} TYPE {% if data.cltype %}{{data.cltype}} {% else %}{{o_data.cltype}} {% endif %}{% if data.attlen %} +({{data.attlen}}{% if data.attprecision%}, {{data.attprecision}}{% endif %}){% endif %}{% if data.hasSqrBracket %} +[]{% endif %}{% if data.collspcname and data.collspcname != o_data.collspcname %} + COLLATE {{data.collspcname}}{% endif %}; + +{% endif %} +{### Alter column default value ###} +{% if data.defval and data.defval != o_data.defval %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} SET DEFAULT {{data.defval}}; + +{% endif %} +{### Alter column not null value ###} +{% if 'attnotnull' in data and data.attnotnull != o_data.attnotnull %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} {% if data.attnotnull %}SET{% else %}DROP{% endif %} NOT NULL; + +{% endif %} +{### Alter column statistics value ###} +{% if data.attstattarget and data.attstattarget != o_data.attstattarget %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} SET STATISTICS {{data.attstattarget}}; + +{% endif %} +{### Alter column storage value ###} +{% if data.attstorage and data.attstorage != o_data.attstorage %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} SET STORAGE {%if data.attstorage == 'p' %} +PLAIN{% elif data.attstorage == 'm'%}MAIN{% elif data.attstorage == 'e'%} +EXTERNAL{% elif data.attstorage == 'x'%}EXTENDED{% endif %}; + +{% endif %} +{% if data.description is defined %} +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.table, data.name)}} + IS {{data.description|qtLiteral}}; + +{% endif %} +{### Update column variables ###} +{% if 'attoptions' in data and data.attoptions|length > 0 %} +{% set variables = data.attoptions %} +{% if 'deleted' in variables and variables.deleted|length > 0 %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.UNSET(conn, 'COLUMN', data.name, variables.deleted) }} +{% endif %} +{% if 'added' in variables and variables.added|length > 0 %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, variables.added) }} +{% endif %} +{% if 'changed' in variables and variables.changed|length > 0 %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, variables.changed) }} +{% endif %} +{% endif %} +{### Update column privileges ###} +{# Change the privileges #} +{% if data.attacl %} +{% if 'deleted' in data.attacl %} +{% for priv in data.attacl.deleted %} +{{ PRIVILEGE.RESETALL(conn, data.schema, data.table, data.name, priv.grantee) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.attacl %} +{% for priv in data.attacl.changed %} +{{ PRIVILEGE.RESETALL(conn, data.schema, data.table, data.name, priv.grantee) }} +{{ PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% if 'added' in data.attacl %} +{% for priv in data.attacl.added %} +{{ PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{% endif %} +{### Uppdate tablespace securitylabel ###} +{# The SQL generated below will change Security Label #} +{% if data.seclabels and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABLE.DROP(conn, 'COLUMN', data.schema, data.table, data.name, r.provider) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.APPLY(conn, 'COLUMN',data.schema, data.table, data.name, r.provider, r.label) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.APPLY(conn, 'COLUMN',data.schema, data.table, data.name, r.provider, r.label) }} +{% endfor %} +{% endif %} +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/alter.sql new file mode 100644 index 000000000..0fb0ea5b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/alter.sql @@ -0,0 +1,4 @@ +{% if data.comment %} +COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/begin.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/begin.sql new file mode 100644 index 000000000..58bfee118 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/begin.sql @@ -0,0 +1 @@ +BEGIN; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql new file mode 100644 index 000000000..c6a53cd3f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql @@ -0,0 +1,12 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} ( + {% for col in data.columns %}{% if loop.index != 1 %}, + {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %} + WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %} + + USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %} +{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} + INITIALLY DEFERRED{% endif%} +{% endif%}{% if data.constraint %} WHERE ({{data.constraint}}){% endif%}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..209679574 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/delete.sql @@ -0,0 +1,3 @@ +{% if data %} +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} DROP CONSTRAINT {{ conn|qtIdent(data.name) }}{% if cascade%} CASCADE{% endif %}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/end.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/end.sql new file mode 100644 index 000000000..92d09d5d0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/end.sql @@ -0,0 +1 @@ +END; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_access_methods.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_access_methods.sql new file mode 100644 index 000000000..5d740578d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_access_methods.sql @@ -0,0 +1,6 @@ +SELECT amname +FROM pg_am +WHERE EXISTS (SELECT 1 + FROM pg_proc + WHERE oid=amgettuple) +ORDER BY amname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_constraint_cols.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_constraint_cols.sql new file mode 100644 index 000000000..cc1a902bf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_constraint_cols.sql @@ -0,0 +1,22 @@ +{% for n in range(colcnt|int) %} +{% if loop.index != 1 %} +UNION +{% endif %} +SELECT + i.indoption[{{loop.index -1}}] AS options, + pg_get_indexdef(i.indexrelid, {{loop.index}}, true) AS coldef, + op.oprname, + CASE WHEN (o.opcdefault = FALSE) THEN o.opcname ELSE null END AS opcname +, + coll.collname, + nspc.nspname as collnspname, + format_type(ty.oid,NULL) AS col_type +FROM pg_index i +JOIN pg_attribute a ON (a.attrelid = i.indexrelid AND attnum = {{loop.index}}) +JOIN pg_type ty ON ty.oid=a.atttypid +LEFT OUTER JOIN pg_opclass o ON (o.oid = i.indclass[{{loop.index -1}}]) +LEFT OUTER JOIN pg_constraint c ON (c.conindid = i.indexrelid) LEFT OUTER JOIN pg_operator op ON (op.oid = c.conexclop[{{loop.index}}]) +LEFT OUTER JOIN pg_collation coll ON a.attcollation=coll.oid +LEFT OUTER JOIN pg_namespace nspc ON coll.collnamespace=nspc.oid +WHERE i.indexrelid = {{cid}}::oid +{% endfor %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_name.sql new file mode 100644 index 000000000..7aaa5223f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_name.sql @@ -0,0 +1,3 @@ +SELECT conname as name +FROM pg_constraint ct +WHERE ct.conindid = {{cid}}::oid \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oid.sql new file mode 100644 index 000000000..168e13bbe --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oid.sql @@ -0,0 +1,4 @@ +SELECT ct.oid +FROM pg_constraint ct +WHERE contype='x' AND +ct.conname = {{ name|qtLiteral }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oid_with_transaction.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oid_with_transaction.sql new file mode 100644 index 000000000..abe636a18 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oid_with_transaction.sql @@ -0,0 +1,6 @@ +SELECT ct.oid, + ct.conname as name, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype='f' AND + conrelid = {{tid}}::oid LIMIT 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oper_class.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oper_class.sql new file mode 100644 index 000000000..c6739d0d6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_oper_class.sql @@ -0,0 +1,7 @@ +SELECT opcname +FROM pg_opclass opc, +pg_am am +WHERE opcmethod=am.oid AND + am.amname ={{indextype|qtLiteral}} AND + NOT opcdefault +ORDER BY 1 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_operator.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_operator.sql new file mode 100644 index 000000000..9978b9639 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_operator.sql @@ -0,0 +1,29 @@ +SELECT DISTINCT op.oprname as oprname +FROM pg_operator op, +( SELECT oid + FROM (SELECT format_type(t.oid,NULL) AS typname, + t.oid as oid + FROM pg_type t + JOIN pg_namespace nsp ON typnamespace=nsp.oid + WHERE (NOT (typname = 'unknown' AND nspname = 'pg_catalog')) AND + typisdefined AND + typtype IN ('b', 'c', 'd', 'e', 'r') AND + NOT EXISTS (SELECT 1 + FROM pg_class + WHERE relnamespace=typnamespace AND + relname = typname AND + relkind != 'c') AND + (typname NOT LIKE '_%' OR + NOT EXISTS (SELECT 1 + FROM pg_class + WHERE relnamespace=typnamespace AND + relname = SUBSTRING(typname FROM 2)::name AND + relkind != 'c')) + {% if not show_sysobj %} + AND nsp.nspname != 'information_schema' + {% endif %} + UNION SELECT 'bigserial', 0 + UNION SELECT 'serial', 0) t1 + WHERE typname = {{type|qtLiteral}}) AS types +WHERE oprcom > 0 AND + (op.oprleft=types.oid OR op.oprright=types.oid) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_parent.sql new file mode 100644 index 000000000..a65285787 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/get_parent.sql @@ -0,0 +1,7 @@ +SELECT nsp.nspname AS schema, + rel.relname AS table +FROM + pg_class rel +JOIN pg_namespace nsp +ON rel.relnamespace = nsp.oid::int +WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/nodes.sql new file mode 100644 index 000000000..c67c40d23 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/nodes.sql @@ -0,0 +1,7 @@ +SELECT conindid as oid, + conname as name, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype='x' AND + conrelid = {{tid}}::oid +ORDER BY conname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..3a1c897a5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/properties.sql @@ -0,0 +1,30 @@ +SELECT cls.oid, + cls.relname as name, + indnatts, + amname, + COALESCE(spcname, 'pg_default') as spcname, + CASE contype + WHEN 'p' THEN desp.description + WHEN 'u' THEN desp.description + WHEN 'x' THEN desp.description + ELSE des.description + END AS comment, + condeferrable, + condeferred, + substring(array_to_string(cls.reloptions, ',') from 'fillfactor=([0-9]*)') AS fillfactor +FROM pg_index idx +JOIN pg_class cls ON cls.oid=indexrelid +JOIN pg_class tab ON tab.oid=indrelid +LEFT OUTER JOIN pg_tablespace ta on ta.oid=cls.reltablespace +JOIN pg_namespace n ON n.oid=tab.relnamespace +JOIN pg_am am ON am.oid=cls.relam +LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') +LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +LEFT OUTER JOIN pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_description desp ON (desp.objoid=con.oid AND desp.objsubid = 0 AND desp.classoid='pg_constraint'::regclass) +WHERE indrelid = {{tid}}::oid +{% if cid %} +AND cls.oid = {{cid}}::oid +{% endif %} +AND contype='x' +ORDER BY cls.relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/update.sql new file mode 100644 index 000000000..4d70f215f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/update.sql @@ -0,0 +1,22 @@ +{### SQL to update exclusion constraint object ###} +{% if data %} +{# ==== To update exclusion constraint name ==== #} +{% 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 exclusion constraint tablespace ==== #} +{% if data.spcname and data.spcname != o_data.spcname %} +ALTER INDEX {{ conn|qtIdent(data.schema, data.name) }} + SET TABLESPACE {{ conn|qtIdent(data.spcname) }}; +{% endif %} +{% if data.fillfactor and data.fillfactor != o_data.fillfactor %} +ALTER INDEX {{ conn|qtIdent(data.schema, data.name) }} + SET (FILLFACTOR={{ data.fillfactor }}); +{% endif %} +{# ==== To update exclusion constraint 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 %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/alter.sql new file mode 100644 index 000000000..0fb0ea5b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/alter.sql @@ -0,0 +1,4 @@ +{% if data.comment %} +COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/begin.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/begin.sql new file mode 100644 index 000000000..58bfee118 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/begin.sql @@ -0,0 +1 @@ +BEGIN; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql new file mode 100644 index 000000000..c6a53cd3f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql @@ -0,0 +1,12 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} ( + {% for col in data.columns %}{% if loop.index != 1 %}, + {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %} + WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %} + + USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %} +{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} + INITIALLY DEFERRED{% endif%} +{% endif%}{% if data.constraint %} WHERE ({{data.constraint}}){% endif%}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/delete.sql new file mode 100644 index 000000000..209679574 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/delete.sql @@ -0,0 +1,3 @@ +{% if data %} +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} DROP CONSTRAINT {{ conn|qtIdent(data.name) }}{% if cascade%} CASCADE{% endif %}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/end.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/end.sql new file mode 100644 index 000000000..92d09d5d0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/end.sql @@ -0,0 +1 @@ +END; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_access_methods.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_access_methods.sql new file mode 100644 index 000000000..5d740578d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_access_methods.sql @@ -0,0 +1,6 @@ +SELECT amname +FROM pg_am +WHERE EXISTS (SELECT 1 + FROM pg_proc + WHERE oid=amgettuple) +ORDER BY amname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_constraint_cols.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_constraint_cols.sql new file mode 100644 index 000000000..c119ccb39 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_constraint_cols.sql @@ -0,0 +1,22 @@ +{% for n in range(colcnt|int) %} +{% if loop.index != 1 %} +UNION +{% endif %} +SELECT + i.indoption[{{loop.index -1}}] AS options, + pg_get_indexdef(i.indexrelid, {{loop.index}}, true) AS coldef, + op.oprname, + CASE WHEN (o.opcdefault = FALSE) THEN o.opcname ELSE null END AS opcname +, + coll.collname, + nspc.nspname as collnspname, + format_type(ty.oid,NULL) AS datatype +FROM pg_index i +JOIN pg_attribute a ON (a.attrelid = i.indexrelid AND attnum = {{loop.index}}) +JOIN pg_type ty ON ty.oid=a.atttypid +LEFT OUTER JOIN pg_opclass o ON (o.oid = i.indclass[{{loop.index -1}}]) +LEFT OUTER JOIN pg_constraint c ON (c.conindid = i.indexrelid) LEFT OUTER JOIN pg_operator op ON (op.oid = c.conexclop[{{loop.index}}]) +LEFT OUTER JOIN pg_collation coll ON a.attcollation=coll.oid +LEFT OUTER JOIN pg_namespace nspc ON coll.collnamespace=nspc.oid +WHERE i.indexrelid = {{cid}}::oid +{% endfor %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_name.sql new file mode 100644 index 000000000..7aaa5223f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_name.sql @@ -0,0 +1,3 @@ +SELECT conname as name +FROM pg_constraint ct +WHERE ct.conindid = {{cid}}::oid \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oid.sql new file mode 100644 index 000000000..168e13bbe --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oid.sql @@ -0,0 +1,4 @@ +SELECT ct.oid +FROM pg_constraint ct +WHERE contype='x' AND +ct.conname = {{ name|qtLiteral }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oid_with_transaction.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oid_with_transaction.sql new file mode 100644 index 000000000..abe636a18 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oid_with_transaction.sql @@ -0,0 +1,6 @@ +SELECT ct.oid, + ct.conname as name, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype='f' AND + conrelid = {{tid}}::oid LIMIT 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oper_class.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oper_class.sql new file mode 100644 index 000000000..c6739d0d6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_oper_class.sql @@ -0,0 +1,7 @@ +SELECT opcname +FROM pg_opclass opc, +pg_am am +WHERE opcmethod=am.oid AND + am.amname ={{indextype|qtLiteral}} AND + NOT opcdefault +ORDER BY 1 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_operator.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_operator.sql new file mode 100644 index 000000000..675a7deb4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_operator.sql @@ -0,0 +1,30 @@ +SELECT DISTINCT op.oprname as oprname +FROM pg_operator op, +( SELECT oid + FROM (SELECT format_type(t.oid,NULL) AS typname, + t.oid as oid + FROM pg_type t + JOIN pg_namespace nsp ON typnamespace=nsp.oid + WHERE (NOT (typname = 'unknown' AND nspname = 'pg_catalog')) AND + typisdefined AND + typtype IN ('b', 'c', 'd', 'e', 'r') AND + NOT EXISTS (SELECT 1 + FROM pg_class + WHERE relnamespace=typnamespace AND + relname = typname AND + relkind != 'c') AND + (typname NOT LIKE '_%' OR + NOT EXISTS (SELECT 1 + FROM pg_class + WHERE relnamespace=typnamespace AND + relname = SUBSTRING(typname FROM 2)::name AND + relkind != 'c')) + {% if not show_sysobj %} + AND nsp.nspname != 'information_schema' + {% endif %} + UNION SELECT 'smallserial', 0 + UNION SELECT 'bigserial', 0 + UNION SELECT 'serial', 0) t1 + WHERE typname = {{type|qtLiteral}}) AS types +WHERE oprcom > 0 AND + (op.oprleft=types.oid OR op.oprright=types.oid) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_parent.sql new file mode 100644 index 000000000..a65285787 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/get_parent.sql @@ -0,0 +1,7 @@ +SELECT nsp.nspname AS schema, + rel.relname AS table +FROM + pg_class rel +JOIN pg_namespace nsp +ON rel.relnamespace = nsp.oid::int +WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/nodes.sql new file mode 100644 index 000000000..c67c40d23 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/nodes.sql @@ -0,0 +1,7 @@ +SELECT conindid as oid, + conname as name, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype='x' AND + conrelid = {{tid}}::oid +ORDER BY conname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/properties.sql new file mode 100644 index 000000000..3a1c897a5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/properties.sql @@ -0,0 +1,30 @@ +SELECT cls.oid, + cls.relname as name, + indnatts, + amname, + COALESCE(spcname, 'pg_default') as spcname, + CASE contype + WHEN 'p' THEN desp.description + WHEN 'u' THEN desp.description + WHEN 'x' THEN desp.description + ELSE des.description + END AS comment, + condeferrable, + condeferred, + substring(array_to_string(cls.reloptions, ',') from 'fillfactor=([0-9]*)') AS fillfactor +FROM pg_index idx +JOIN pg_class cls ON cls.oid=indexrelid +JOIN pg_class tab ON tab.oid=indrelid +LEFT OUTER JOIN pg_tablespace ta on ta.oid=cls.reltablespace +JOIN pg_namespace n ON n.oid=tab.relnamespace +JOIN pg_am am ON am.oid=cls.relam +LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') +LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +LEFT OUTER JOIN pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_description desp ON (desp.objoid=con.oid AND desp.objsubid = 0 AND desp.classoid='pg_constraint'::regclass) +WHERE indrelid = {{tid}}::oid +{% if cid %} +AND cls.oid = {{cid}}::oid +{% endif %} +AND contype='x' +ORDER BY cls.relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/update.sql new file mode 100644 index 000000000..4d70f215f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/update.sql @@ -0,0 +1,22 @@ +{### SQL to update exclusion constraint object ###} +{% if data %} +{# ==== To update exclusion constraint name ==== #} +{% 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 exclusion constraint tablespace ==== #} +{% if data.spcname and data.spcname != o_data.spcname %} +ALTER INDEX {{ conn|qtIdent(data.schema, data.name) }} + SET TABLESPACE {{ conn|qtIdent(data.spcname) }}; +{% endif %} +{% if data.fillfactor and data.fillfactor != o_data.fillfactor %} +ALTER INDEX {{ conn|qtIdent(data.schema, data.name) }} + SET (FILLFACTOR={{ data.fillfactor }}); +{% endif %} +{# ==== To update exclusion constraint 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 %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/alter.sql new file mode 100644 index 000000000..0fb0ea5b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/alter.sql @@ -0,0 +1,4 @@ +{% if data.comment %} +COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/begin.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/begin.sql new file mode 100644 index 000000000..58bfee118 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/begin.sql @@ -0,0 +1 @@ +BEGIN; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/create.sql new file mode 100644 index 000000000..7a4eda4b8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/create.sql @@ -0,0 +1,27 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} FOREIGN KEY ({% for columnobj in data.columns %}{% if loop.index != 1 %} +, {% endif %}{{ conn|qtIdent(columnobj.local_column)}}{% endfor %}) + REFERENCES {{ conn|qtIdent(data.remote_schema, data.remote_table) }} ({% for columnobj in data.columns %}{% if loop.index != 1 %} +, {% endif %}{{ conn|qtIdent(columnobj.referenced)}}{% endfor %}) {% if data.confmatchtype %}MATCH FULL{% else %}MATCH SIMPLE{% endif%} + + ON UPDATE{% if data.confupdtype == 'a' %} + NO ACTION{% elif data.confupdtype == 'r' %} + RESTRICT{% elif data.confupdtype == 'c' %} + CASCADE{% elif data.confupdtype == 'n' %} + SET NULL{% elif data.confupdtype == 'd' %} + SET DEFAULT{% endif %} + + ON DELETE{% if data.confdeltype == 'a' %} + NO ACTION{% elif data.confdeltype == 'r' %} + RESTRICT{% elif data.confdeltype == 'c' %} + CASCADE{% elif data.confdeltype == 'n' %} + SET NULL{% elif data.confdeltype == 'd' %} + SET DEFAULT{% endif %} +{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} + INITIALLY DEFERRED{% endif%} +{% endif%} +{% if data.convalidated %} + + NOT VALID{% endif%}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/create_index.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/create_index.sql new file mode 100644 index 000000000..f9c1e88b7 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/create_index.sql @@ -0,0 +1,5 @@ +{% if data.autoindex %} +CREATE INDEX {{ conn|qtIdent(data.coveringindex) }} + ON {{ conn|qtIdent(data.schema, data.table) }}({% for columnobj in data.columns %}{% if loop.index != 1 %} +, {% endif %}{{ conn|qtIdent(columnobj.local_column)}}{% endfor %}); +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/delete.sql new file mode 100644 index 000000000..209679574 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/delete.sql @@ -0,0 +1,3 @@ +{% if data %} +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} DROP CONSTRAINT {{ conn|qtIdent(data.name) }}{% if cascade%} CASCADE{% endif %}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/end.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/end.sql new file mode 100644 index 000000000..92d09d5d0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/end.sql @@ -0,0 +1 @@ +END; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_cols.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_cols.sql new file mode 100644 index 000000000..4b2fee25a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_cols.sql @@ -0,0 +1,7 @@ +{% for n in range(colcnt|int) %} +{% if loop.index != 1 %} +UNION SELECT pg_get_indexdef({{ cid|string }}, {{ loop.index|string }}, true) AS column +{% else %} +SELECT pg_get_indexdef({{ cid|string }} , {{ loop.index|string }} , true) AS column +{% endif %} +{% endfor %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_constraint_cols.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_constraint_cols.sql new file mode 100644 index 000000000..a96e575c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_constraint_cols.sql @@ -0,0 +1,13 @@ +{% for keypair in keys %} +{% if loop.index != 1 %} +UNION +{% endif %} +SELECT a1.attname as conattname, + a2.attname as confattname +FROM pg_attribute a1, + pg_attribute a2 +WHERE a1.attrelid={{tid}}::oid + AND a1.attnum={{keypair[1]}} + AND a2.attrelid={{confrelid}}::oid + AND a2.attnum={{keypair[0]}} +{% endfor %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_constraints.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_constraints.sql new file mode 100644 index 000000000..d2f358cd8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_constraints.sql @@ -0,0 +1,37 @@ +SELECT cls.oid, cls.relname as idxname, indnatts + FROM pg_index idx + JOIN pg_class cls ON cls.oid=indexrelid + LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') + LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +WHERE idx.indrelid = {{tid}}::oid + AND con.contype='p' + +UNION + +SELECT cls.oid, cls.relname as idxname, indnatts + FROM pg_index idx + JOIN pg_class cls ON cls.oid=indexrelid + LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') + LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +WHERE idx.indrelid = {{tid}}::oid + AND con.contype='x' + +UNION + +SELECT cls.oid, cls.relname as idxname, indnatts + FROM pg_index idx + JOIN pg_class cls ON cls.oid=indexrelid + LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') + LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +WHERE idx.indrelid = {{tid}}::oid + AND con.contype='u' + +UNION + +SELECT cls.oid, cls.relname as idxname, indnatts + FROM pg_index idx + JOIN pg_class cls ON cls.oid=indexrelid + LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') + LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +WHERE idx.indrelid = {{tid}}::oid + AND conname IS NULL \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_name.sql new file mode 100644 index 000000000..07fdae243 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_name.sql @@ -0,0 +1,3 @@ +SELECT conname as name +FROM pg_constraint ct +WHERE ct.oid = {{fkid}}::oid \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_oid.sql new file mode 100644 index 000000000..576c9761c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_oid.sql @@ -0,0 +1,5 @@ +SELECT ct.oid, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype='f' AND +ct.conname = {{ name|qtLiteral }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_oid_with_transaction.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_oid_with_transaction.sql new file mode 100644 index 000000000..abe636a18 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_oid_with_transaction.sql @@ -0,0 +1,6 @@ +SELECT ct.oid, + ct.conname as name, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype='f' AND + conrelid = {{tid}}::oid LIMIT 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_parent.sql new file mode 100644 index 000000000..a65285787 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/get_parent.sql @@ -0,0 +1,7 @@ +SELECT nsp.nspname AS schema, + rel.relname AS table +FROM + pg_class rel +JOIN pg_namespace nsp +ON rel.relnamespace = nsp.oid::int +WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/nodes.sql new file mode 100644 index 000000000..966758828 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/nodes.sql @@ -0,0 +1,7 @@ +SELECT ct.oid, + conname as name, + NOT convalidated as convalidated +FROM pg_constraint ct +WHERE contype='f' AND + conrelid = {{tid}}::oid +ORDER BY conname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/properties.sql new file mode 100644 index 000000000..1f506ee87 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/properties.sql @@ -0,0 +1,31 @@ +SELECT ct.oid, + conname as name, + condeferrable, + condeferred, + confupdtype, + confdeltype, + CASE confmatchtype + WHEN 's' THEN FALSE + WHEN 'f' THEN TRUE + END AS confmatchtype, + conkey, + confkey, + confrelid, + nl.nspname as fknsp, + cl.relname as fktab, + nr.nspname as refnsp, + cr.relname as reftab, + description as comment, + NOT convalidated as convalidated +FROM pg_constraint ct +JOIN pg_class cl ON cl.oid=conrelid +JOIN pg_namespace nl ON nl.oid=cl.relnamespace +JOIN pg_class cr ON cr.oid=confrelid +JOIN pg_namespace nr ON nr.oid=cr.relnamespace +LEFT OUTER JOIN pg_description des ON (des.objoid=ct.oid AND des.classoid='pg_constraint'::regclass) +WHERE contype='f' AND +conrelid = {{tid}}::oid +{% if cid %} +AND ct.oid = {{cid}}::oid +{% endif %} +ORDER BY conname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/update.sql new file mode 100644 index 000000000..ee8f8a913 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/update.sql @@ -0,0 +1,18 @@ +{### SQL to update foreign key object ###} +{% if data %} +{# ==== To update foreign key name ==== #} +{% 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 %} +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 %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/validate.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/validate.sql new file mode 100644 index 000000000..5a62c801a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/foreign_key/sql/validate.sql @@ -0,0 +1,2 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + VALIDATE CONSTRAINT {{ conn|qtIdent(data.name) }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/alter.sql new file mode 100644 index 000000000..68a444404 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/alter.sql @@ -0,0 +1,11 @@ +{## Alter index to use cluster type ##} +{% if data.indisclustered %} + +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + CLUSTER ON {{conn|qtIdent(data.name)}}; +{% endif %} +{## Changes description ##} +{% if data.description is defined %} + +COMMENT ON INDEX {{conn|qtIdent(data.schema, data.name)}} + IS {{data.description|qtLiteral}};{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/backend_support.sql new file mode 100644 index 000000000..6f66ed1b4 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/backend_support.sql @@ -0,0 +1,9 @@ +{#=============Checks if it is materialized view========#} +{% if vid %} +SELECT + CASE WHEN c.relkind = 'm' THEN True ELSE False END As m_view +FROM + pg_class c +WHERE + c.oid = {{ vid }}::oid +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/column_details.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/column_details.sql new file mode 100644 index 000000000..2cf540a0c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/column_details.sql @@ -0,0 +1,30 @@ +SELECT + i.indexrelid, + CASE i.indoption[i.attnum - 1] + WHEN 0 THEN ARRAY['ASC', 'NULLS LAST'] + WHEN 1 THEN ARRAY['DESC', 'NULLS FIRST'] + WHEN 2 THEN ARRAY['ASC', 'NULLS FIRST'] + WHEN 3 THEN ARRAY['DESC', 'NULLS '] + ELSE ARRAY['UNKNOWN OPTION' || i.indoption[i.attnum - 1], ''] + END::text[] AS options, + i.attnum, + pg_get_indexdef(i.indexrelid, i.attnum, true) as attdef, + CASE WHEN (o.opcdefault = FALSE) THEN o.opcname ELSE null END AS opcname, + op.oprname AS oprname, + CASE WHEN length(nspc.nspname) > 0 AND length(coll.collname) > 0 THEN + concat(quote_ident(nspc.nspname), '.', quote_ident(coll.collname)) + ELSE '' END AS collnspname +FROM ( + SELECT + indexrelid, i.indoption, i.indclass, + unnest(ARRAY(SELECT generate_series(1, i.indnatts) AS n)) AS attnum + FROM + pg_index i + WHERE i.indexrelid = {{idx}}::OID +) i + LEFT JOIN pg_opclass o ON (o.oid = i.indclass[i.attnum - 1]) + LEFT OUTER JOIN pg_constraint c ON (c.conindid = i.indexrelid) + LEFT OUTER JOIN pg_operator op ON (op.oid = c.conexclop[i.attnum]) + LEFT JOIN pg_attribute a ON (a.attrelid = i.indexrelid AND a.attnum = i.attnum) + LEFT OUTER JOIN pg_collation coll ON a.attcollation=coll.oid + LEFT OUTER JOIN pg_namespace nspc ON coll.collnamespace=nspc.oid; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql new file mode 100644 index 000000000..b31c82754 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql @@ -0,0 +1,12 @@ +CREATE {% if data.indisunique %}UNIQUE {% endif %}INDEX {% if data.isconcurrent %}CONCURRENTLY {% endif %}{{conn|qtIdent(data.name)}} + ON {{conn|qtIdent(data.schema, data.table)}} {% if data.amname %}USING {{conn|qtIdent(data.amname)}}{% endif %} + + ({% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.colname)}}{% if c.collspcname %} COLLATE {{c.collspcname}} {% endif %}{% if c.op_class %} + {{c.op_class}}{% endif %}{% if c.sort_order is defined %}{% if c.sort_order %} DESC{% else %} ASC{% endif %}{% endif %}{% if c.nulls is defined %} NULLS {% if c.nulls %} +FIRST{% else %}LAST{% endif %}{% endif %}{% endfor %}){% if data.fillfactor %} + + WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname %} + + TABLESPACE {{conn|qtIdent(data.spcname)}}{% endif %}{% if data.indconstraint %} + + WHERE {{data.indconstraint}}{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..f3808faaf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/delete.sql @@ -0,0 +1 @@ +DROP INDEX {{conn|qtIdent(data.nspname, data.name)}}{% if cascade %} cascade{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_am.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_am.sql new file mode 100644 index 000000000..5bb579b50 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_am.sql @@ -0,0 +1,3 @@ +-- Fetches access methods +SELECT oid, amname +FROM pg_am \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_collations.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_collations.sql new file mode 100644 index 000000000..7418742e5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_collations.sql @@ -0,0 +1,7 @@ +SELECT --nspname, collname, + CASE WHEN length(nspname) > 0 AND length(collname) > 0 THEN + concat(quote_ident(nspname), '.', quote_ident(collname)) + ELSE '' END AS collation +FROM pg_collation c, pg_namespace n + WHERE c.collnamespace=n.oid + ORDER BY nspname, collname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_oid.sql new file mode 100644 index 000000000..c32402f51 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_oid.sql @@ -0,0 +1,7 @@ +SELECT DISTINCT ON(cls.relname) cls.oid +FROM pg_index idx + JOIN pg_class cls ON cls.oid=indexrelid + JOIN pg_class tab ON tab.oid=indrelid + JOIN pg_namespace n ON n.oid=tab.relnamespace +WHERE indrelid = {{tid}}::OID + AND cls.relname = {{data.name|qtLiteral}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_op_class.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_op_class.sql new file mode 100644 index 000000000..af0133ac5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_op_class.sql @@ -0,0 +1,5 @@ +SELECT opcname, opcmethod +FROM pg_opclass + WHERE opcmethod = {{oid}}::OID + AND NOT opcdefault + ORDER BY 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_parent.sql new file mode 100644 index 000000000..5dd5d3ce0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/get_parent.sql @@ -0,0 +1,5 @@ +SELECT nsp.nspname AS schema ,rel.relname AS table +FROM pg_class rel + JOIN pg_namespace nsp + ON rel.relnamespace = nsp.oid::int + WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/nodes.sql new file mode 100644 index 000000000..9e9c9480e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/nodes.sql @@ -0,0 +1,12 @@ +SELECT DISTINCT ON(cls.relname) cls.oid, cls.relname as name +FROM pg_index idx + JOIN pg_class cls ON cls.oid=indexrelid + JOIN pg_class tab ON tab.oid=indrelid + LEFT OUTER JOIN pg_tablespace ta on ta.oid=cls.reltablespace + JOIN pg_namespace n ON n.oid=tab.relnamespace + JOIN pg_am am ON am.oid=cls.relam + LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') + LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +WHERE indrelid = {{tid}}::OID + AND conname is NULL + ORDER BY cls.relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..2641d4c2d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/properties.sql @@ -0,0 +1,22 @@ +SELECT DISTINCT ON(cls.relname) cls.oid, cls.relname as name, indrelid, indkey, indisclustered, + indisvalid, indisunique, indisprimary, n.nspname,indnatts,cls.reltablespace AS spcoid, + COALESCE(spcname, 'pg_default') as spcname, tab.relname as tabname, indclass, con.oid AS conoid, + CASE WHEN contype IN ('p', 'u', 'x') THEN desp.description + ELSE des.description END AS description, + pg_get_expr(indpred, indrelid, true) as indconstraint, contype, condeferrable, condeferred, amname, + substring(array_to_string(cls.reloptions, ',') from 'fillfactor=([0-9]*)') AS fillfactor + {% if datlastsysoid %}, (CASE WHEN cls.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_idx {% endif %} +FROM pg_index idx + JOIN pg_class cls ON cls.oid=indexrelid + JOIN pg_class tab ON tab.oid=indrelid + LEFT OUTER JOIN pg_tablespace ta on ta.oid=cls.reltablespace + JOIN pg_namespace n ON n.oid=tab.relnamespace + JOIN pg_am am ON am.oid=cls.relam + LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') + LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) + LEFT OUTER JOIN pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_description desp ON (desp.objoid=con.oid AND desp.objsubid = 0 AND desp.classoid='pg_constraint'::regclass) +WHERE indrelid = {{tid}}::OID + AND conname is NULL + {% if idx %}AND cls.oid = {{idx}}::OID {% endif %} + ORDER BY cls.relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/update.sql new file mode 100644 index 000000000..f2acd6c2b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/update.sql @@ -0,0 +1,24 @@ +{## Changes name ##} +{% if data.name and o_data.name != data.name %} +ALTER INDEX {{conn|qtIdent(data.schema, o_data.name)}} + RENAME TO {{conn|qtIdent(data.name)}}; +{% endif %} +{## Changes fillfactor ##} +{% if data.fillfactor and o_data.fillfactor != data.fillfactor %} +ALTER INDEX {{conn|qtIdent(data.schema, data.name)}} + SET (FILLFACTOR={{data.fillfactor}}); +{% endif %} +{## Changes tablespace ##} +{% if data.spcname and o_data.spcname != data.spcname %} +ALTER INDEX {{conn|qtIdent(data.schema, data.name)}} + SET TABLESPACE {{conn|qtIdent(data.spcname)}}; +{% endif %} +{## Alter index to use cluster type ##} +{% if data.indisclustered is defined and o_data.indisclustered != data.indisclustered %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + CLUSTER ON {{conn|qtIdent(data.name)}}; +{% endif %} +{## Changes description ##} +{% if data.description is defined and o_data.description != data.description %} +COMMENT ON INDEX {{conn|qtIdent(data.schema, data.name)}} + IS {{data.description|qtLiteral}};{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/alter.sql new file mode 100644 index 000000000..0fb0ea5b2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/alter.sql @@ -0,0 +1,4 @@ +{% if data.comment %} +COMMENT ON CONSTRAINT {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{ data.comment|qtLiteral }}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/begin.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/begin.sql new file mode 100644 index 000000000..58bfee118 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/begin.sql @@ -0,0 +1 @@ +BEGIN; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/create.sql new file mode 100644 index 000000000..61a717ee3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/create.sql @@ -0,0 +1,12 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} {{constraint_name}} {% if data.index %}USING INDEX {{ conn|qtIdent(data.index) }}{% else %} +({% for columnobj in data.columns %}{% if loop.index != 1 %} +, {% endif %}{{ conn|qtIdent(columnobj.column)}}{% endfor %}){% if data.fillfactor %} + + WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %} + + USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %}{% endif %}{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} + INITIALLY DEFERRED{% endif%} +{% endif%}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/delete.sql new file mode 100644 index 000000000..209679574 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/delete.sql @@ -0,0 +1,3 @@ +{% if data %} +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} DROP CONSTRAINT {{ conn|qtIdent(data.name) }}{% if cascade%} CASCADE{% endif %}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/end.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/end.sql new file mode 100644 index 000000000..92d09d5d0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/end.sql @@ -0,0 +1 @@ +END; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_constraint_cols.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_constraint_cols.sql new file mode 100644 index 000000000..4b2fee25a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_constraint_cols.sql @@ -0,0 +1,7 @@ +{% for n in range(colcnt|int) %} +{% if loop.index != 1 %} +UNION SELECT pg_get_indexdef({{ cid|string }}, {{ loop.index|string }}, true) AS column +{% else %} +SELECT pg_get_indexdef({{ cid|string }} , {{ loop.index|string }} , true) AS column +{% endif %} +{% endfor %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_indices.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_indices.sql new file mode 100644 index 000000000..b9cab213b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_indices.sql @@ -0,0 +1,3 @@ +SELECT relname FROM pg_class, pg_index +WHERE pg_class.oid=indexrelid +AND indrelid={{ tid }} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_name.sql new file mode 100644 index 000000000..a1beafb04 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_name.sql @@ -0,0 +1,15 @@ +SELECT cls.relname as name +FROM pg_index idx +JOIN pg_class cls ON cls.oid=indexrelid +LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND + dep.objid = cls.oid AND + dep.refobjsubid = '0' + AND dep.refclassid=(SELECT oid + FROM pg_class + WHERE relname='pg_constraint') AND + dep.deptype='i') +LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND + con.oid = dep.refobjid) +WHERE indrelid = {{tid}}::oid +AND contype='{{constraint_type}}' +AND cls.oid = {{cid}}::oid; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid.sql new file mode 100644 index 000000000..861ff5060 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid.sql @@ -0,0 +1,4 @@ +SELECT ct.conindid as oid +FROM pg_constraint ct +WHERE contype='{{constraint_type}}' AND +ct.conname = {{ name|qtLiteral }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid_with_transaction.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid_with_transaction.sql new file mode 100644 index 000000000..9ad802003 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_oid_with_transaction.sql @@ -0,0 +1,5 @@ +SELECT ct.conindid as oid, + ct.conname as name +FROM pg_constraint ct +WHERE contype='{{constraint_type}}' AND + conrelid = {{tid}}::oid LIMIT 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_parent.sql new file mode 100644 index 000000000..a65285787 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/get_parent.sql @@ -0,0 +1,7 @@ +SELECT nsp.nspname AS schema, + rel.relname AS table +FROM + pg_class rel +JOIN pg_namespace nsp +ON rel.relnamespace = nsp.oid::int +WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/nodes.sql new file mode 100644 index 000000000..ed96b4424 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/nodes.sql @@ -0,0 +1,14 @@ +SELECT cls.oid, cls.relname as name +FROM pg_index idx +JOIN pg_class cls ON cls.oid=indexrelid +LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND + dep.objid = cls.oid AND + dep.refobjsubid = '0' AND + dep.refclassid=(SELECT oid + FROM pg_class + WHERE relname='pg_constraint') AND + dep.deptype='i') +LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND + con.oid = dep.refobjid) +WHERE indrelid = {{tid}}::oid +AND contype='{{constraint_type}}' \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/properties.sql new file mode 100644 index 000000000..0eabdd735 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/properties.sql @@ -0,0 +1,29 @@ +SELECT cls.oid, + cls.relname as name, + indnatts, + COALESCE(spcname, 'pg_default') as spcname, + CASE contype + WHEN 'p' THEN desp.description + WHEN 'u' THEN desp.description + WHEN 'x' THEN desp.description + ELSE des.description + END AS comment, + condeferrable, + condeferred, + substring(array_to_string(cls.reloptions, ',') from 'fillfactor=([0-9]*)') AS fillfactor +FROM pg_index idx +JOIN pg_class cls ON cls.oid=indexrelid +JOIN pg_class tab ON tab.oid=indrelid +LEFT OUTER JOIN pg_tablespace ta on ta.oid=cls.reltablespace +JOIN pg_namespace n ON n.oid=tab.relnamespace +JOIN pg_am am ON am.oid=cls.relam +LEFT JOIN pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_class WHERE relname='pg_constraint') AND dep.deptype='i') +LEFT OUTER JOIN pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid) +LEFT OUTER JOIN pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass) +LEFT OUTER JOIN pg_description desp ON (desp.objoid=con.oid AND desp.objsubid = 0 AND desp.classoid='pg_constraint'::regclass) +WHERE indrelid = {{tid}}::oid +{% if cid %} +AND cls.oid = {{cid}}::oid +{% endif %} +AND contype='{{constraint_type}}' +ORDER BY cls.relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/update.sql new file mode 100644 index 000000000..66185329d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index_constraint/sql/update.sql @@ -0,0 +1,22 @@ +{### SQL to update constraint object ###} +{% if data %} +{# ==== To update constraint name ==== #} +{% 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 constraint tablespace ==== #} +{% if data.spcname and data.spcname != o_data.spcname %} +ALTER INDEX {{ conn|qtIdent(data.schema, data.name) }} + SET TABLESPACE {{ conn|qtIdent(data.spcname) }}; +{% endif %} +{% if data.fillfactor and data.fillfactor != o_data.fillfactor %} +ALTER INDEX {{ conn|qtIdent(data.schema, data.name) }} + SET (FILLFACTOR={{ data.fillfactor }}); +{% endif %} +{# ==== To update constraint 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 %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/backend_support.sql new file mode 100644 index 000000000..bb5e8d803 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/backend_support.sql @@ -0,0 +1,9 @@ +{#=============Checks if it is materialized view========#} +{% if vid %} +SELECT + CASE WHEN c.relkind = 'm' THEN False ELSE True END As m_view +FROM + pg_class c +WHERE + c.oid = {{ vid }}::oid +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/create.sql new file mode 100644 index 000000000..fad2c7aa1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/create.sql @@ -0,0 +1,27 @@ +{# ============Create Rule============= #} +{% if display_comments %} +-- Rule: {{ data.name }} ON {{ conn|qtIdent(data.schema, data.name) }} + +-- DROP Rule {{ data.name }} ON {{ conn|qtIdent(data.schema, data.name) }}; + +{% endif %} +{% if data.name and data.schema and data.view %} +CREATE OR REPLACE RULE {{ conn|qtIdent(data.name) }} AS + ON {{ data.event|upper if data.event else 'SELECT' }} TO {{ conn|qtIdent(data.schema, data.view) }} +{% if data.condition %} + WHERE {{ data.condition }} +{% endif %} + DO{% if data.do_instead in ['true', True] %} +{{ ' INSTEAD' }} +{% else %} +{{ '' }} +{% endif %} +{% if data.statements %} +{{ data.statements.rstrip(';') }}; +{% else %} + NOTHING; +{% endif %} +{% if data.comment %} + +COMMENT ON RULE {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.view) }} IS {{ data.comment|qtLiteral }};{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/delete.sql new file mode 100644 index 000000000..cf18913b8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/delete.sql @@ -0,0 +1,16 @@ +{# ======== Drop/Cascade Rule ========= #} +{% if rid %} +SELECT + rw.rulename, + cl.relname, + nsp.nspname +FROM + pg_rewrite rw +JOIN pg_class cl ON cl.oid=rw.ev_class +JOIN pg_namespace nsp ON nsp.oid=cl.relnamespace +WHERE + rw.oid={{ rid }}; +{% endif %} +{% if rulename and relname and nspname %} +DROP RULE {{ conn|qtIdent(rulename) }} ON {{ conn|qtIdent(nspname, relname) }} {% if cascade %} CASCADE {% endif %}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/properties.sql new file mode 100644 index 000000000..afdd139ad --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/properties.sql @@ -0,0 +1,27 @@ +{# =================== Fetch Rules ==================== #} +{% if tid or rid %} +SELECT + rw.oid AS oid, + rw.rulename AS name, + relname AS view, + CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname AS schema, + description AS comment, + {# ===== Check whether it is system rule or not ===== #} + CASE WHEN rw.rulename = '_RETURN' THEN True ELSE False END AS system_rule, + CASE WHEN rw.ev_enabled != 'D' THEN True ELSE False END AS enabled, + pg_get_ruledef(rw.oid, true) AS definition +FROM + pg_rewrite rw +JOIN pg_class cl ON cl.oid=rw.ev_class +JOIN pg_namespace nsp ON nsp.oid=cl.relnamespace +LEFT OUTER JOIN pg_description des ON (des.objoid=rw.oid AND des.classoid='pg_rewrite'::regclass) +WHERE + {% if tid %} + ev_class = {{ tid }} + {% elif rid %} + rw.oid = {{ rid }} + {% endif %} +ORDER BY + rw.rulename + {% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/rule_id.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/rule_id.sql new file mode 100644 index 000000000..3dc6dbabf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/rule_id.sql @@ -0,0 +1,9 @@ +{#========Below will provide rule id for last created rule========#} +{% if rule_name %} +SELECT + rw.oid +FROM + pg_rewrite rw +WHERE + rw.rulename={{ rule_name|qtLiteral }} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/update.sql new file mode 100644 index 000000000..099d6e4af --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/rules/sql/update.sql @@ -0,0 +1,33 @@ +{# ===== Update Rule ===== #} +{% if data.name is defined %} +{% set rule_name = data.name %} +{% else %} +{% set rule_name = o_data.name %} +{% endif %} +{% if data.name and data.name != o_data.name %} +ALTER RULE {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.schema, o_data.view) }} RENAME TO {{ conn|qtIdent(data.name) }};{{ '\r\r' }} +{% endif %} +{% if data.event or data.do_instead or data.condition or data.statements %} +CREATE OR REPLACE RULE {{ conn|qtIdent(rule_name) }} AS + ON {% if data.event and data.event != o_data.event %}{{ data.event|upper }}{% else %}{{ o_data.event|upper }}{% endif %} + TO {{ conn|qtIdent(o_data.schema, o_data.view) }} +{% if data.condition and o_data.condition != data.condition %} + WHERE {{ data.condition }} +{% elif data.condition is not defined and o_data.condition %} + WHERE {{ o_data.condition }} +{% endif %} + DO {% if (('do_instead' not in data and o_data.do_instead in ['true', True]) or (data.do_instead in ['true', True])) %}{{ 'INSTEAD' }}{% endif %} +{% if data.statements and data.statements != o_data.statements %} + +{{ data.statements.rstrip(';') }}; +{% elif data.statements is not defined and o_data.statements %} + +{{ o_data.statements.rstrip(';') }}; +{% else %} + NOTHING; +{% endif %} + +{% endif %} +{% set old_comment = o_data.comment|default('', true) %} +{% if (data.comment is defined and (data.comment != old_comment)) %} +COMMENT ON RULE {{ conn|qtIdent(rule_name) }} ON {{ conn|qtIdent(o_data.schema, o_data.view) }} IS {{ data.comment|qtLiteral }};{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js new file mode 100644 index 000000000..ae2b40f55 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js @@ -0,0 +1,954 @@ +define( + [ + 'jquery', 'underscore', 'underscore.string', 'pgadmin', + 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection', + 'pgadmin.node.column', 'pgadmin.node.constraints' + ], +function($, _, S, pgAdmin, pgBrowser, alertify) { + + if (!pgBrowser.Nodes['coll-table']) { + var databases = pgAdmin.Browser.Nodes['coll-table'] = + pgAdmin.Browser.Collection.extend({ + node: 'table', + label: '{{ _('Tables') }}', + type: 'coll-table', + columns: ['name', 'relowner', 'description'] + }); + }; + + if (!pgBrowser.Nodes['table']) { + pgAdmin.Browser.Nodes['table'] = pgBrowser.Node.extend({ + type: 'table', + label: '{{ _('Table') }}', + collection_type: 'coll-table', + hasSQL: true, + hasDepends: true, + sqlAlterHelp: 'sql-altertable.html', + sqlCreateHelp: 'sql-createtable.html', + parent_type: ['schema', 'catalog'], + hasScriptTypes: ['create', 'select', 'insert', 'update', 'delete'], + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_table_on_coll', node: 'coll-table', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 1, label: '{{ _('Table...') }}', + icon: 'wcTabIcon icon-table', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_table', node: 'table', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 1, label: '{{ _('Table...') }}', + icon: 'wcTabIcon icon-table', data: {action: 'create', check: true}, + enable: 'canCreate' + },{ + name: 'create_table__on_schema', node: 'schema', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: '{{ _('Table...') }}', + icon: 'wcTabIcon icon-table', data: {action: 'create', check: false}, + enable: 'canCreate' + },{ + name: 'truncate_table', node: 'table', module: this, + applies: ['object', 'context'], callback: 'truncate_table', + category: 'Truncate', priority: 3, label: '{{ _('Truncate') }}', + icon: 'fa fa-eraser', enable : 'canCreate' + },{ + name: 'truncate_table_cascade', node: 'table', module: this, + applies: ['object', 'context'], callback: 'truncate_table_cascade', + category: 'Truncate', priority: 3, label: '{{ _('Truncate Cascade') }}', + icon: 'fa fa-eraser', enable : 'canCreate' + },{ + // To enable/disable all triggers for the table + name: 'enable_all_triggers', node: 'table', module: this, + applies: ['object', 'context'], callback: 'enable_triggers_on_table', + category: 'Trigger(s)', priority: 4, label: '{{ _('Enable All') }}', + icon: 'fa fa-check', enable : 'canCreate_with_trigger_enable' + },{ + name: 'disable_all_triggers', node: 'table', module: this, + applies: ['object', 'context'], callback: 'disable_triggers_on_table', + category: 'Trigger(s)', priority: 4, label: '{{ _('Disable All') }}', + icon: 'fa fa-times', enable : 'canCreate_with_trigger_disable' + },{ + name: 'reset_table_stats', node: 'table', module: this, + applies: ['object', 'context'], callback: 'reset_table_stats', + category: 'Reset', priority: 4, label: '{{ _('Reset statistics') }}', + icon: 'fa fa-bar-chart', enable : 'canCreate' + } + ]); + }, + canDrop: pgBrowser.Nodes['schema'].canChildDrop, + canDropCascade: pgBrowser.Nodes['schema'].canChildDrop, + callbacks: { + /* Enable trigger(s) on table */ + enable_triggers_on_table: function(args) { + var params = {'enable': true }; + this.callbacks.set_triggers.apply(this, [args, params]); + }, + /* Disable trigger(s) on table */ + disable_triggers_on_table: function(args) { + var params = {'enable': false }; + this.callbacks.set_triggers.apply(this, [args, params]); + }, + set_triggers: function(args, params) { + // This function will send request to enable or + // disable triggers on table level + var input = args || {}; + obj = this, + t = pgBrowser.tree, + i = input.item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined; + if (!d) + return false; + + $.ajax({ + url: obj.generate_url(i, 'set_trigger' , d, true), + type:'PUT', + data: params, + dataType: "json", + success: function(res) { + if (res.success == 1) { + alertify.success("{{ _('" + res.info + "') }}"); + t.unload(i); + t.setInode(i); + t.deselect(i); + setTimeout(function() { + t.select(i); + }, 10); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + msg = S('{{ _(' + err.errormsg + ')}}').value(); + alertify.error("{{ _('" + err.errormsg + "') }}"); + } + } catch (e) {} + t.unload(i); + } + }); + }, + /* Truncate table */ + truncate_table: function(args) { + var params = {'cascade': false }; + this.callbacks.truncate.apply(this, [args, params]); + }, + /* Truncate table with cascade */ + truncate_table_cascade: function(args) { + var params = {'cascade': true }; + this.callbacks.truncate.apply(this, [args, params]); + }, + truncate: function(args, params) { + var input = args || {}; + obj = this, + t = pgBrowser.tree, + i = input.item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined; + + if (!d) + return false; + + alertify.confirm( + S('{{ _('Are you sure you want to truncate table - %%s ?') }}').sprintf(d.label).value(), + function (e) { + if (e) { + var data = d; + $.ajax({ + url: obj.generate_url(i, 'truncate' , d, true), + type:'PUT', + data: params, + dataType: "json", + success: function(res) { + if (res.success == 1) { + alertify.success("{{ _('" + res.info + "') }}"); + t.removeIcon(i); + data.icon = 'icon-table'; + t.addIcon(i, {icon: data.icon}); + t.unload(i); + t.setInode(i); + t.deselect(i); + // Fetch updated data from server + setTimeout(function() { + t.select(i); + }, 10); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + msg = S('{{ _(' + err.errormsg + ')}}').value(); + alertify.error("{{ _('" + err.errormsg + "') }}"); + } + } catch (e) {} + t.unload(i); + } + }); + } + }); + }, + reset_table_stats: function(args) { + var input = args || {}; + obj = this, + t = pgBrowser.tree, + i = input.item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined; + + if (!d) + return false; + + alertify.confirm( + S('{{ _('Are you sure you want to reset table statistics - %%s ?') }}').sprintf(d.label).value(), + function (e) { + if (e) { + var data = d; + $.ajax({ + url: obj.generate_url(i, 'reset' , d, true), + type:'DELETE', + success: function(res) { + if (res.success == 1) { + alertify.success("{{ _('" + res.info + "') }}"); + t.removeIcon(i); + data.icon = 'icon-table'; + t.addIcon(i, {icon: data.icon}); + t.unload(i); + t.setInode(i); + t.deselect(i); + // Fetch updated data from server + setTimeout(function() { + t.select(i); + }, 10); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + msg = S('{{ _(' + err.errormsg + ')}}').value(); + alertify.error("{{ _('" + err.errormsg + "') }}"); + } + } catch (e) {} + t.unload(i); + } + }); + } + }); + } + }, + model: pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + oid: undefined, + spcoid: undefined, + spcname: 'pg_default', + relowner: undefined, + relacl: undefined, + relhasoids: undefined, + relhassubclass: undefined, + reltuples: undefined, + description: undefined, + conname: undefined, + conkey: undefined, + isrepl: undefined, + triggercount: undefined, + relpersistence: undefined, + fillfactor: undefined, + reloftype: undefined, + typname: undefined, + labels: undefined, + providers: undefined, + is_sys_table: undefined, + coll_inherits: [], + hastoasttable: true, + toast_autovacuum_enabled: false, + autovacuum_enabled: false, + primary_key: [] + }, + // Default values! + initialize: function(attrs, args) { + var self = this, + isNew = (_.size(attrs) === 0); + + if (isNew) { + var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user; + var schemaInfo = args.node_info.schema; + + this.set({'relowner': userInfo.name}, {silent: true}); + this.set({'schema': schemaInfo.label}, {silent: true}); + } + pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments); + + }, + schema: [{ + id: 'name', label: '{{ _('Name') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + disabled: 'inSchema' + },{ + id: 'oid', label:'{{ _('OID') }}', cell: 'string', + type: 'text' , mode: ['properties'] + },{ + id: 'relowner', label:'{{ _('Owner') }}', cell: 'string', + type: 'text', mode: ['properties', 'create', 'edit'], + disabled: 'inSchema', control: 'node-list-by-name', + node: 'role', select2: { allowClear: false } + },{ + id: 'schema', label:'{{ _('Schema') }}', cell: 'string', + control: 'node-list-by-name', + type: 'text', mode: ['create', 'edit'], node: 'schema', + disabled: 'inSchema', filter: function(d) { + // If schema name start with pg_* then we need to exclude them + if(d && d.label.match(/^pg_/)) + { + return false; + } + return true; + } + },{ + id: 'spcname', label:'{{ _('Tablespace') }}', cell: 'string', control: 'node-list-by-name', + type: 'text', mode: ['properties', 'create', 'edit'], node: 'tablespace', + disabled: 'inSchema', select2:{allowClear:false}, + filter: function(d) { + // If tablespace name is not "pg_global" then we need to exclude them + if(d && d.label.match(/pg_global/)) + { + return false; + } + return true; + } + },{ + id: 'description', label:'{{ _('Comment') }}', cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + disabled: 'inSchema' + },{ + id: 'typname', label:'{{ _('Of type') }}', cell: 'string', control: 'node-ajax-options', + type: 'text', mode: ['properties', 'create', 'edit'], + disabled: 'checkOfType', url: 'get_oftype', group: '{{ _('Advanced') }}', deps: ['coll_inherits'], + transform: function(data, cell) { + var control = cell || this, + m = control.model; + m.of_types_tables = data; + return data; + }, + control: Backform.NodeAjaxOptionsControl.extend({ + // When of_types changes we need to clear columns collection + onChange: function() { + Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments); + var self = this, + tbl_oid = undefined, + tbl_name = self.model.get('typname'), + data = undefined, + arg = undefined, + column_collection = self.model.get('columns'); + + if (!_.isUndefined(tbl_name) && + tbl_name !== '' && column_collection.length !== 0) { + var msg = '{{ _('Changing of type table will clear columns collection') }}'; + alertify.confirm(msg, function (e) { + if (e) { + // User clicks Ok, lets clear columns collection + column_collection.reset(); + } else { + return this; + } + }); + } else if (!_.isUndefined(tbl_name) && tbl_name === '') { + column_collection.reset(); + } + + // Run Ajax now to fetch columns + if (!_.isUndefined(tbl_name) && tbl_name !== '') { + arg = { 'tname': tbl_name } + data = self.model.fetch_columns_ajax.apply(self, [arg]); + // Add into column collection + column_collection.set(data, { merge:false,remove:false }); + } + } + }) + },{ + id: 'fillfactor', label:'{{ _('Fill factor') }}', cell: 'integer', + type: 'int', mode: ['create', 'edit'], min: 10, max: 100, + disabled: 'inSchema',group: '{{ _('Advanced') }}' + },{ + id: 'relhasoids', label:'{{ _('Has OIDs?') }}', cell: 'switch', + type: 'switch', mode: ['properties', 'create', 'edit'], + disabled: 'inSchema', group: '{{ _('Advanced') }}' + },{ + id: 'relpersistence', label:'{{ _('Unlogged?') }}', cell: 'switch', + type: 'switch', mode: ['properties', 'create', 'edit'], + disabled: 'inSchemaWithModelCheck', + group: '{{ _('Advanced') }}' + },{ + id: 'conname', label:'{{ _('Primary Key') }}', cell: 'string', + type: 'text', mode: ['properties'], + disabled: 'inSchema' + },{ + id: 'reltuples', label:'{{ _('Rows (estimated)') }}', cell: 'string', + type: 'text', mode: ['properties'], + disabled: 'inSchema' + },{ + id: 'rows_cnt', label:'{{ _('Rows (counted)') }}', cell: 'string', + type: 'text', mode: ['properties'], + disabled: 'inSchema' + },{ + id: 'relhassubclass', label:'{{ _('Inherits tables?') }}', cell: 'switch', + type: 'switch', mode: ['properties'], + disabled: 'inSchema' + },{ + id: 'is_sys_table', label:'{{ _('System tabel?') }}', cell: 'switch', + type: 'switch', mode: ['properties'], + disabled: 'inSchema' + },{ + id: 'coll_inherits', label: '{{ _('Inherited from table(s)') }}', + url: 'get_inherits', type: 'array', + disabled: 'checkInheritance', deps: ['typname'], + select2: { multiple: true, allowClear: true, + placeholder: '{{ _('Select to inherit from...') }}'}, + transform: function(data, cell) { + var control = cell || this, + m = control.model; + m.inherited_tables_list = data; + return data; + }, + control: Backform.MultiSelectAjaxControl.extend({ + // When changes we need to add/clear columns collection + onChange: function() { + Backform.MultiSelectAjaxControl.prototype.onChange.apply(this, arguments); + var self = this, + // current table list and previous table list + cTbl_list = self.model.get('coll_inherits') || [], + pTbl_list = self.model.previous('coll_inherits') || []; + + if (!_.isUndefined(cTbl_list)) { + var tbl_name = undefined, + tid = undefined; + + // Add columns logic + // If new table is added in list + if(cTbl_list.length > 1 && cTbl_list.length > pTbl_list.length) { + // Find newly added table from current list + tbl_name = _.difference(cTbl_list, pTbl_list); + tid = this.get_table_oid(tbl_name[0]); + this.add_columns(tid); + } else if (cTbl_list.length == 1) { + // First table added + tid = this.get_table_oid(cTbl_list[0]); + this.add_columns(tid); + } + + // Remove columns logic + if(cTbl_list.length > 0 && cTbl_list.length < pTbl_list.length) { + // Find deleted table from previous list + tbl_name = _.difference(pTbl_list, cTbl_list); + this.remove_columns(tbl_name[0]); + } else if (pTbl_list.length === 1 && cTbl_list.length < 1) { + // We got last table from list + tbl_name = pTbl_list[0]; + this.remove_columns(tbl_name); + } + + } + }, + add_columns: function(tid) { + // Create copy of old model if anything goes wrong at-least we have backup + // Then send AJAX request to fetch table specific columns + var self = this, + url = 'get_columns', + m = self.model.top || self.model, + data = undefined, + old_columns = _.clone(m.get('columns')), + column_collection = m.get('columns'); + + var arg = {'tid': tid} + data = self.model.fetch_columns_ajax.apply(self, [arg]); + + // Update existing column collection + column_collection.set(data, { merge:false,remove:false }); + }, + remove_columns: function(tblname) { + // Remove all the column models for deleted table + var tid = this.get_table_oid(tblname), + column_collection = this.model.get('columns'); + column_collection.remove(column_collection.where({'inheritedid': tid })); + }, + get_table_oid: function(tblname) { + // Here we will fetch the table oid from table name + var tbl_oid = undefined; + // iterate over list to find table oid + _.each(this.model.inherited_tables_list, function(obj) { + if(obj.label === tblname) { + tbl_oid = obj.tid; + } + }); + return tbl_oid; + } + }) + },{ + id: 'inherited_tables_cnt', label:'{{ _('Inherited tables count') }}', cell: 'string', + type: 'text', mode: ['properties'], + disabled: 'inSchema' + },{ + type: 'nested', control: 'fieldset', mode: ['edit', 'create'], + schema:[{ + // Here we will create tab control for columns + id: 'columns', label:'{{ _('Columns') }}', type: 'collection', + group: '{{ _('Columns') }}', + model: pgBrowser.Nodes['column'].model, + subnode: pgBrowser.Nodes['column'].model, + mode: ['create', 'edit'], + disabled: 'inSchema', + canAdd: 'check_grid_add_condition', + canEdit: true, canDelete: true, + // For each row edit/delete button enable/disable + canEditRow: 'check_grid_row_edit_delete', + canDeleteRow: 'check_grid_row_edit_delete', + uniqueCol : ['name'], + columns : ['name' , 'cltype', 'is_primary_key', 'inheritedfrom'], + control: Backform.UniqueColCollectionControl.extend({ + initialize: function() { + Backform.UniqueColCollectionControl.prototype.initialize.apply(this, arguments); + var self = this, + collection = self.model.get(self.field.get('name')); + + collection.on("change:is_primary_key", function(m) { + var primary_key_coll = self.model.get('primary_key'), + column_name = m.get('name'), + primary_key; + + if(m.get('is_primary_key')) { + // Add column to primary key. + if (primary_key_coll.length < 1) { + primary_key = new (primary_key_coll.model)({}, { + top: self.model, + collection: primary_key_coll, + handler: primary_key_coll + }); + primary_key_coll.add(primary_key); + } else { + primary_key = primary_key_coll.first(); + } + // Do not alter existing primary key columns. + if (_.isUndefined(primary_key.get('oid'))) { + var primary_key_column_coll = primary_key.get('columns'), + primary_key_column_exist = primary_key_column_coll.where({column:column_name}); + + if (primary_key_column_exist.length == 0) { + var primary_key_column = new (primary_key_column_coll.model)( + {column: column_name}, { silent: true, + top: self.model, + collection: primary_key_coll, + handler: primary_key_coll + }); + + primary_key_column_coll.add(primary_key_column); + } + + primary_key_column_coll.trigger('pgadmin:multicolumn:updated', primary_key_column_coll); + } + + } else { + // remove column from primary key. + if (primary_key_coll.length > 0) { + var primary_key = primary_key_coll.first(); + // Do not alter existing primary key columns. + if (!_.isUndefined(primary_key.get('oid'))) { + return; + } + + var primary_key_column_coll = primary_key.get('columns'), + removedCols = primary_key_column_coll.where({column:column_name}); + if (removedCols.length > 0) { + primary_key_column_coll.remove(removedCols); + _.each(removedCols, function(m) { + m.destroy(); + }) + if (primary_key_column_coll.length == 0) { + setTimeout(function () { + // There will be only on primary key so remove the first one. + primary_key_coll.remove(primary_key_coll.first()); + /* Ideally above line of code should be "primary_key_coll.reset()". + * But our custom DataCollection (extended from Backbone collection in datamodel.js) + * does not respond to reset event, it only supports add, remove, change events. + * And hence no custom event listeners/validators get called for reset event. + */ + }, 10); + } + } + primary_key_column_coll.trigger('pgadmin:multicolumn:updated', primary_key_column_coll); + } + } + }) + }, + remove: function() { + var collection = this.model.get(this.field.get('name')); + if (collection) { + collection.off("change:is_primary_key"); + } + + Backform.UniqueColCollectionControl.prototype.remove.apply(this, arguments); + } + }), + allowMultipleEmptyRow: false + }] + },{ + type: 'nested', control: 'fieldset', + schema:[{ + // Here we will create tab control for constraints + type: 'nested', control: 'tab', group: '{{ _('Constraints') }}', + mode: ['edit', 'create'], + schema: [{ + id: 'primary_key', label: '{{ _('Primary Key') }}', + model: pgBrowser.Nodes['primary_key'].model, + subnode: pgBrowser.Nodes['primary_key'].model, + editable: false, type: 'collection', + group: '{{ _('Primary Key') }}', mode: ['edit', 'create'], + canEdit: true, canDelete: true, + control: 'unique-col-collection', + columns : ['name', 'columns'], + canAdd: true, + canAddRow: function(m) { + // User can only add one primary key + var columns = m.get('columns'); + + return (m.get('primary_key') && + m.get('primary_key').length < 1 && + _.some(columns.pluck('name'))); + } + },{ + id: 'foreign_key', label: '{{ _('Foreign Key') }}', + model: pgBrowser.Nodes['foreign_key'].model, + subnode: pgBrowser.Nodes['foreign_key'].model, + editable: false, type: 'collection', + group: '{{ _('Foreign Key') }}', mode: ['edit', 'create'], + canEdit: true, canDelete: true, + control: 'unique-col-collection', + canAdd: true, + columns : ['name', 'columns'], + canAddRow: function(m) { + // User can only add if there is at least one column with name. + var columns = m.get('columns'); + return _.some(columns.pluck('name')); + } + },{ + id: 'check_constraint', label: '{{ _('Check Constraint') }}', + model: pgBrowser.Nodes['check_constraints'].model, + subnode: pgBrowser.Nodes['check_constraints'].model, + editable: false, type: 'collection', + group: '{{ _('Check') }}', mode: ['edit', 'create'], + canEdit: true, canDelete: true, + control: 'unique-col-collection', + canAdd: true, + columns : ['name', 'consrc'] + },{ + id: 'unique_constraint', label: '{{ _('Unique Constraint') }}', + model: pgBrowser.Nodes['unique_constraint'].model, + subnode: pgBrowser.Nodes['unique_constraint'].model, + editable: false, type: 'collection', + group: '{{ _('Unique') }}', mode: ['edit', 'create'], + canEdit: true, canDelete: true, + control: 'unique-col-collection', + columns : ['name', 'columns'], + canAdd: true, + canAddRow: function(m) { + // User can only add if there is at least one column with name. + var columns = m.get('columns'); + return _.some(columns.pluck('name')); + } + },{ + id: 'exclude_constraint', label: '{{ _('Exclude Constraint') }}', + model: pgBrowser.Nodes['exclusion_constraint'].model, + subnode: pgBrowser.Nodes['exclusion_constraint'].model, + editable: false, type: 'collection', + group: '{{ _('Exclude') }}', mode: ['edit', 'create'], + canEdit: true, canDelete: true, + control: 'unique-col-collection', + columns : ['name', 'columns', 'constraint'], + canAdd: true, + canAddRow: function(m) { + // User can only add if there is at least one column with name. + var columns = m.get('columns'); + return _.some(columns.pluck('name')); + } + }] + }] + },{ + type: 'nested', control: 'fieldset', label: '{{ _('Like') }}', + group: '{{ _('Advanced') }}', + schema:[{ + id: 'like_relation', label:'{{ _('Relation') }}', cell: 'string', + type: 'text', mode: ['create', 'edit'], + control: 'node-ajax-options', url: 'get_relations', + disabled: 'inSchemaWithModelCheck', group: '{{ _('Like') }}' + },{ + id: 'like_default_value', label:'{{ _('With Default values?') }}', cell: 'switch', + type: 'switch', mode: ['create', 'edit'], + disabled: 'inSchemaWithModelCheck', group: '{{ _('Like') }}' + },{ + id: 'like_constraints', label:'{{ _('With Constraints?') }}', cell: 'switch', + type: 'switch', mode: ['create', 'edit'], + disabled: 'inSchemaWithModelCheck', group: '{{ _('Like') }}' + },{ + id: 'like_indexes', label:'{{ _('With Indexes?') }}', cell: 'switch', + type: 'switch', mode: ['create', 'edit'], + disabled: 'inSchemaWithModelCheck', group: '{{ _('Like') }}' + },{ + id: 'like_storage', label:'{{ _('With Storage?') }}', cell: 'switch', + type: 'switch', mode: ['create', 'edit'], + disabled: 'inSchemaWithModelCheck', group: '{{ _('Like') }}' + },{ + id: 'like_comments', label:'{{ _('With Comments?') }}', cell: 'switch', + type: 'switch', mode: ['create', 'edit'], + disabled: 'inSchemaWithModelCheck', group: '{{ _('Like') }}' + }] + },{ + // Here we will create tab control for auto-vacuum + type: 'nested', control: 'tab', group: '{{ _('Auto vacuum') }}', + mode: ['edit', 'create'], + schema: Backform.VacuumSettingsSchema + },{ + id: 'relacl_str', label:'{{ _('Privileges') }}', cell: 'string', + type: 'text', mode: ['properties'], group: '{{ _('Security') }}', + disabled: 'inSchema' + },{ + id: 'relacl', label: 'Privileges', type: 'collection', + group: '{{ _('Security') }}', control: 'unique-col-collection', + model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({ + privileges: ['a','r','w','d','D','x','t']}), + mode: ['edit', 'create'], canAdd: true, canDelete: true, + uniqueCol : ['grantee'] + },{ + id: 'seclabels', label: '{{ _('Security Labels') }}', + model: pgAdmin.Browser.SecurityModel, editable: false, type: 'collection', + group: '{{ _('Security') }}', mode: ['edit', 'create'], + min_version: 90100, canAdd: true, + canEdit: false, canDelete: true, control: 'unique-col-collection' + },{ + id: 'vacuum_settings_str', label: '{{ _('Storage Settings') }}', + type: 'multiline', group: '{{ _('Advanced') }}', mode: ['properties'] + } + ], + validate: function() { + var err = {}, + changedAttrs = this.changed, + msg = undefined, + name = this.get('name'), + schema = this.get('schema'), + relowner = this.get('relowner'); + + this.errorModel.clear(); + + if (_.isUndefined(name) || _.isNull(name) || + String(name).replace(/^\s+|\s+$/g, '') == '') { + msg = '{{ _('Table name can not be empty.') }}'; + this.errorModel.set('name', msg); + return msg; + } else if (_.isUndefined(schema) || _.isNull(schema) || + String(schema).replace(/^\s+|\s+$/g, '') == '') { + msg = '{{ _('Table schema can not be empty.') }}'; + this.errorModel.set('schema', msg); + return msg; + } else if (_.isUndefined(relowner) || _.isNull(relowner) || + String(relowner).replace(/^\s+|\s+$/g, '') == '') { + msg = '{{ _('Table owner can not be empty.') }}'; + this.errorModel.set('relowner', msg); + return msg; + } + return null; + }, + // We will disable everything if we are under catalog node + inSchema: function() { + if(this.node_info && 'catalog' in this.node_info) + { + return true; + } + return false; + }, + isInheritedTable: function(m) { + if(!m.inSchema.apply(this, [m])) { + if( + (!_.isUndefined(m.get('coll_inherits')) && m.get('coll_inherits').length != 0) + || + (!_.isUndefined(m.get('typname')) && String(m.get('typname')).replace(/^\s+|\s+$/g, '') !== '') + ) { + // Either of_types or coll_inherits has value + return false; + } else { + return true; + } + } else { + return false; + } + }, + // We will disable it if Oftype is defined + checkInheritance: function(m) { + //coll_inherits || typname + if(!m.inSchema.apply(this, [m]) && + ( _.isUndefined(m.get('typname')) || + _.isNull(m.get('typname')) || + String(m.get('typname')).replace(/^\s+|\s+$/g, '') == '')) { + return false; + } + return true; + }, + // Check for column grid when to Add + check_grid_add_condition: function(m) { + var enable_flag = true; + if(!m.inSchema.apply(this, [m])) { + // if of_type then disable add in grid + if (!_.isUndefined(m.get('typname')) && !_.isNull(m.get('typname'))) { + enable_flag = false; + } + } + return enable_flag; + }, + // Check for column grid when to edit/delete (for each row) + check_grid_row_edit_delete: function(m) { + var flag = true; + if(!_.isUndefined(m.get('inheritedfrom')) && + !_.isNull(m.get('inheritedfrom')) && + String(m.get('inheritedfrom')).replace(/^\s+|\s+$/g, '') !== '') { + flag = false; + } + return flag; + }, + // We will disable it if Inheritance is defined + checkOfType: function(m) { + //coll_inherits || typname + if(!m.inSchemaWithModelCheck.apply(this, [m]) && + (_.isUndefined(m.get('coll_inherits')) || + _.isNull(m.get('coll_inherits')) || + String(m.get('coll_inherits')).replace(/^\s+|\s+$/g, '') == '')) { + return false; + } + return true; + }, + // We will check if we are under schema node & in 'create' mode + inSchemaWithModelCheck: function(m) { + if(this.node_info && 'schema' in this.node_info) + { + // We will disbale control if it's in 'edit' mode + if (m.isNew()) { + return false; + } else { + return true; + } + } + return true; + }, + isTableAutoVacuumEnable: function(m) { + // We need to check additional condition to toggle enable/disable + // for table auto-vacuum + if(!m.inSchema.apply(this, [m]) && + m.get('autovacuum_enabled') === true) { + return false; + } + return true; + }, + isToastTableAutoVacuumEnable: function(m) { + // We need to check additional condition to toggle enable/disable + // for toast table auto-vacuum + if(!m.inSchemaWithModelCheck.apply(this, [m]) && + m.get('toast_autovacuum_enabled') == true) { + return false; + } + return true; + }, + fetch_columns_ajax: function(arg) { + var self = this, + url = 'get_columns', + m = self.model.top || self.model, + old_columns = _.clone(m.get('columns')) + data = undefined; + + if (url) { + var node = this.field.get('schema_node'), + node_info = this.field.get('node_info'), + full_url = node.generate_url.apply( + node, [ + null, url, this.field.get('node_data'), + this.field.get('url_with_id') || false, node_info + ]), + cache_level = this.field.get('cache_level') || node.type, + cache_node = this.field.get('cache_node'); + + cache_node = (cache_node && pgAdmin.Browser.Nodes['cache_node']) || node; + + m.trigger('pgadmin:view:fetching', m, self.field); + // Fetching Columns data for the selected table. + $.ajax({ + async: false, + url: full_url, + data: arg, + success: function(res) { + data = cache_node.cache(url, node_info, cache_level, res.data); + }, + error: function() { + m.trigger('pgadmin:view:fetch:error', m, self.field); + } + }); + m.trigger('pgadmin:view:fetched', m, self.field); + data = (data && data.data) || []; + return data; + } + } + }), + canCreate: function(itemData, item, data) { + //If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to create table + if (_.indexOf(['schema'], d._type) > -1) + return true; + + if ('coll-table' == d._type) { + //Check if we are not child of catalog + prev_i = t.hasParent(i) ? t.parent(i) : null; + prev_d = prev_i ? t.itemData(prev_i) : null; + if( prev_d._type == 'catalog') { + return false; + } else { + return true; + } + } + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // by default we do not want to allow create menu + return true; + }, + // Check to whether table has disable trigger(s) + canCreate_with_trigger_enable: function(itemData, item, data) { + if(this.canCreate.apply(this, [itemData, item, data])) { + // We are here means we can create menu, now let's check condition + if(itemData.tigger_count > 0) { + return true; + } else { + return false; + } + } + }, + // Check to whether table has enable trigger(s) + canCreate_with_trigger_disable: function(itemData, item, data) { + if(this.canCreate.apply(this, [itemData, item, data])) { + // We are here means we can create menu, now let's check condition + if(itemData.tigger_count > 0 && itemData.has_enable_triggers > 0) { + return true; + } else { + return false; + } + } + } + }); + + } + + return pgBrowser.Nodes['table']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/acl.sql new file mode 100644 index 000000000..56f1f76aa --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/acl.sql @@ -0,0 +1,46 @@ +{### SQL to fetch privileges for tablespace ###} +SELECT 'relacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname 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 rel.relacl + FROM pg_class rel + LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace + LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p' + LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid + LEFT JOIN pg_type typ ON rel.reloftype=typ.oid + WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ scid }}::oid + AND rel.oid = {{ tid }}::oid + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable + AS is_grantable, (d).privilege_type AS privilege_type FROM (SELECT + aclexplode(rel.relacl) as d + FROM pg_class rel + LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace + LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p' + LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid + LEFT JOIN pg_type typ ON rel.reloftype=typ.oid + WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ scid }}::oid + AND rel.oid = {{ tid }}::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 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/backend_support.sql new file mode 100644 index 000000000..f9b956418 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/create.sql new file mode 100644 index 000000000..c0fb5e449 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/create.sql @@ -0,0 +1,157 @@ +{% import 'macros/schemas/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% import 'macros/variable.macros' as VARIABLE %} +{% import 'column/macros/security.macros' as COLUMN_SECLABLE %} +{% import 'column/macros/privilege.macros' as COLUMN_PRIVILEGE %} +{% import 'table/sql/macros/constraints.macro' as CONSTRAINTS %} +{#===========================================#} +{#====== MAIN TABLE TEMPLATE STARTS HERE ======#} +{#===========================================#} +{# + If user has not provided any details but only name then + add empty bracket with table name +#} +{% set empty_bracket = ""%} +{% if data.coll_inherits|length == 0 and data.columns|length == 0 and not data.typname and not data.like_relation and data.primary_key|length == 0 and data.unique_constraint|length == 0 and data.foreign_key|length == 0 and data.check_constraint|length == 0 and data.exclude_constraint|length == 0 %} +{% set empty_bracket = "\n(\n)"%} +{% endif %} +CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE {{conn|qtIdent(data.schema, data.name)}}{{empty_bracket}} +{% if data.typname %} + OF {{ data.typname }} +{% endif %} +{% if data.like_relation or data.coll_inherits or data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 or data.exclude_constraint|length > 0 %} +( +{% endif %} +{% if data.like_relation %} + LIKE {{ data.like_relation }}{% if data.like_default_value %} + + INCLUDING DEFAULTS{% endif %}{% if data.like_constraints %} + + INCLUDING CONSTRAINTS{% endif %}{% if data.like_indexes %} + + INCLUDING INDEXES{% endif %}{% if data.like_storage %} + + INCLUDING STORAGE{% endif %}{% if data.like_comments %} + + INCLUDING COMMENTS{% endif %}{% if data.columns|length > 0 %}, +{% endif %} + +{% endif %} +{### Add columns ###} +{% if data.columns and data.columns|length > 0 %} +{% for c in data.columns %} +{% if c.name and c.cltype %} +{% if loop.index != 1 %}, +{% endif %} + {{conn|qtIdent(c.name)}} {{c.cltype}}{% if c.attlen %} +({{c.attlen}}{% if c.attprecision%}, {{c.attprecision}}{% endif %}){% endif %}{% if c.hasSqrBracket %} +[]{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval %} DEFAULT {{c.defval}}{% endif %} +{% endif %} +{% endfor %} +{% endif %} +{# Macro to render for constraints #} +{% if data.primary_key|length > 0 %}{% if data.columns|length > 0 %},{% endif %} +{{CONSTRAINTS.PRIMARY_KEY(conn, data.primary_key[0])}}{% endif %}{% if data.unique_constraint|length > 0 %}{% if data.columns|length > 0 or data.primary_key|length > 0 %},{% endif %} +{{CONSTRAINTS.UNIQUE(conn, data.unique_constraint)}}{% endif %}{% if data.foreign_key|length > 0 %}{% if data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 %},{% endif %} +{{CONSTRAINTS.FOREIGN_KEY(conn, data.foreign_key)}}{% endif %}{% if data.check_constraint|length > 0 %}{% if data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 %},{% endif %} +{{CONSTRAINTS.CHECK(conn, data.check_constraint)}}{% endif %}{% if data.exclude_constraint|length > 0 %}{% if data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 %},{% endif %} +{{CONSTRAINTS.EXCLUDE(conn, data.exclude_constraint)}}{% endif %} +{% if data.like_relation or data.coll_inherits or data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 or data.exclude_constraint|length > 0 %} + +) +{% endif %} +{### If we are inheriting it from another table(s) ###} +{% if data.coll_inherits %} + INHERITS ({% for val in data.coll_inherits %}{% if loop.index != 1 %}, {% endif %}{{val}}{% endfor %}) +{% endif %} +WITH ( + OIDS = {% if data.relhasoids %}TRUE{% else %}FALSE{% endif %}{% if data.fillfactor %}, + FILLFACTOR = {{ data.fillfactor }}{% endif %}{% if data.autovacuum_custom %}, + autovacuum_enabled = {% if data.autovacuum_enabled %}TRUE{% else %}FALSE{% endif %}{% endif %}{% if data.toast_autovacuum %}, + toast.autovacuum_enabled = {% if data.toast_autovacuum_enabled %}TRUE{% else %}FALSE{% endif %} +{% endif %}{% if data.autovacuum_enabled and data.vacuum_table|length > 0 %} +{% for opt in data.vacuum_table %}{% if opt.name and opt.value %} +, + {{opt.name}} = {{opt.value}}{% endif %} +{% endfor %}{% endif %}{% if data.toast_autovacuum_enabled and data.vacuum_toast|length > 0 %} +{% for opt in data.vacuum_toast %}{% if opt.name and opt.value %} +, + toast.{{opt.name}} = {{opt.value}}{% endif %} +{% endfor %}{% endif %} + +) +{### SQL for Tablespace ###} +{% if data.spcname %} +TABLESPACE {{ conn|qtIdent(data.spcname) }}; +{% endif %} +{### Alter SQL for Owner ###} +{% if data.relowner %} + +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + OWNER to {{conn|qtIdent(data.relowner)}}; +{% endif %} +{### Security Labels on Table ###} +{% if data.seclabels and data.seclabels|length > 0 %} + +{% for r in data.seclabels %} +{{ SECLABLE.SET(conn, 'TABLE', data.name, r.provider, r.label, data.schema) }} +{% endfor %} +{% endif %} +{### ACL on Table ###} +{% if data.relacl %} + +{% for priv in data.relacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{### SQL for COMMENT ###} +{% if data.description %} +COMMENT ON TABLE {{conn|qtIdent(data.schema, data.name)}} + IS {{data.description|qtLiteral}}; +{% endif %} +{#===========================================#} +{#====== MAIN TABLE TEMPLATE ENDS HERE ======#} +{#===========================================#} +{#===========================================#} +{# COLUMN SPECIFIC TEMPLATES STARTS HERE #} +{#===========================================#} +{% if data.columns and data.columns|length > 0 %} +{% for c in data.columns %} +{% if c.description %} + +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.name, c.name)}} + IS {{c.description|qtLiteral}}; +{% endif %} +{### Add variables to column ###} +{% if c.attoptions and c.attoptions|length > 0 %} + +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + {{ VARIABLE.SET(conn, 'COLUMN', c.name, c.attoptions) }} +{% endif %} +{### ACL ###} +{% if c.attacl and c.attacl|length > 0 %} + +{% for priv in c.attacl %} + {{ COLUMN_PRIVILEGE.APPLY(conn, data.schema, data.name, c.name, priv.grantee, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{### Security Lables ###} +{% if c.seclabels and c.seclabels|length > 0 %} + +{% for r in c.seclabels %} +{{ COLUMN_SECLABLE.APPLY(conn, 'COLUMN',data.schema, data.name, c.name, r.provider, r.label) }} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{#===========================================#} +{# COLUMN SPECIFIC TEMPLATES ENDS HERE #} +{#===========================================#} +{#======================================#} +{# CONSTRAINTS SPECIFIC TEMPLATES #} +{#======================================#} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.primary_key)}} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.unique_constraint)}} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.foreign_key)}} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.check_constraint)}} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.exclude_constraint)}} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..01d0314b6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/delete.sql @@ -0,0 +1 @@ +DROP TABLE {{conn|qtIdent(data.schema, data.name)}}{% if cascade %} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/depend.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/depend.sql new file mode 100644 index 000000000..f5f39e7d0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/depend.sql @@ -0,0 +1,9 @@ +SELECT + ref.relname AS refname, d2.refclassid, dep.deptype AS deptype +FROM pg_depend dep + LEFT JOIN pg_depend d2 ON dep.objid=d2.objid AND dep.refobjid != d2.refobjid + LEFT JOIN pg_class ref ON ref.oid=d2.refobjid + LEFT JOIN pg_attribute att ON d2.refclassid=att.attrelid AND d2.refobjsubid=att.attnum + {{ where }} AND + dep.classid=(SELECT oid FROM pg_class WHERE relname='pg_attrdef') AND + dep.refobjid NOT IN (SELECT d3.refobjid FROM pg_depend d3 WHERE d3.objid=d2.refobjid) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/enable_disable_trigger.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/enable_disable_trigger.sql new file mode 100644 index 000000000..a4ab15401 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/enable_disable_trigger.sql @@ -0,0 +1,2 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + {% if is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER ALL; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_columns_for_table.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_columns_for_table.sql new file mode 100644 index 000000000..3d6bbb7d2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_columns_for_table.sql @@ -0,0 +1,16 @@ +SELECT + a.attname AS name, format_type(a.atttypid, NULL) AS cltype, + quote_ident(n.nspname)||'.'||quote_ident(c.relname) as inheritedfrom, + c.oid as inheritedid +FROM + pg_class c +JOIN + pg_namespace n ON c.relnamespace=n.oid +JOIN + pg_attribute a ON a.attrelid = c.oid AND NOT a.attisdropped AND a.attnum > 0 +WHERE +{% if tid %} + c.oid = {{tid}}::OID +{% else %} + c.relname = {{tname|qtLiteral}} +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_inherits.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_inherits.sql new file mode 100644 index 000000000..6b2560d57 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_inherits.sql @@ -0,0 +1,16 @@ +SELECT c.oid, c.relname , nspname, +CASE WHEN nspname NOT LIKE E'pg\_%' THEN + quote_ident(nspname)||'.'||quote_ident(c.relname) +ELSE quote_ident(c.relname) +END AS inherits +FROM pg_class c +JOIN pg_namespace n +ON n.oid=c.relnamespace +WHERE relkind='r' +{% if not show_system_objects %} +AND (n.nspname NOT LIKE E'pg\_%' AND n.nspname NOT in ('information_schema')) +{% endif %} +{% if tid %} +AND c.oid != tid +{% endif %} +ORDER BY relnamespace, c.relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_oftype.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_oftype.sql new file mode 100644 index 000000000..aed42f26a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_oftype.sql @@ -0,0 +1,6 @@ +SELECT t.oid, + quote_ident(n.nspname)||'.'||quote_ident(t.typname) AS typname + FROM pg_type t, pg_namespace n +WHERE t.typtype='c' AND t.typnamespace=n.oid + AND NOT (n.nspname like 'pg_%' OR n.nspname='information_schema') +ORDER BY typname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_oid.sql new file mode 100644 index 000000000..e9dc7729c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_oid.sql @@ -0,0 +1,5 @@ +SELECT rel.oid as tid +FROM pg_class rel +WHERE rel.relkind IN ('r','s','t') +AND rel.relnamespace = {{ scid }}::oid +AND rel.relname = {{data.name|qtLiteral}} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_relations.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_relations.sql new file mode 100644 index 000000000..431ee882c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_relations.sql @@ -0,0 +1,6 @@ +SELECT c.oid, quote_ident(n.nspname)||'.'||quote_ident(c.relname) AS like_relation +FROM pg_class c, pg_namespace n +WHERE c.relnamespace=n.oid +AND +c.relkind IN ('r', 'v', 'f') +ORDER BY 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_tables_for_constraints.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_tables_for_constraints.sql new file mode 100644 index 000000000..136246349 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_tables_for_constraints.sql @@ -0,0 +1,8 @@ +SELECT cl.oid as value, quote_ident(nspname)||'.'||quote_ident(relname) AS label +FROM pg_namespace nsp, pg_class cl +WHERE relnamespace=nsp.oid AND relkind='r' + AND nsp.nspname NOT LIKE E'pg\_temp\_%' + {% if not show_sysobj %} + AND (nsp.nspname NOT LIKE E'pg\_%' AND nsp.nspname NOT in ('information_schema')) + {% endif %} +ORDER BY nspname, relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_types_where_condition.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_types_where_condition.sql new file mode 100644 index 000000000..fadfc9918 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/get_types_where_condition.sql @@ -0,0 +1,10 @@ +{### Additional where condition for get_types route for column node ###} +typisdefined AND typtype IN ('b', 'c', 'd', 'e', 'r') +AND NOT EXISTS (SELECT 1 FROM pg_class WHERE relnamespace=typnamespace +AND relname = typname AND relkind != 'c') AND +(typname NOT LIKE '_%' OR NOT EXISTS (SELECT 1 FROM pg_class WHERE +relnamespace=typnamespace AND relname = substring(typname FROM 2)::name +AND relkind != 'c')) +{% if not show_system_objects %} +AND nsp.nspname != 'information_schema' +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/nodes.sql new file mode 100644 index 000000000..43f14cbcf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/nodes.sql @@ -0,0 +1,6 @@ +SELECT rel.oid, rel.relname AS name, + (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount, + (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE AND tgenabled = 'O') AS has_enable_triggers +FROM pg_class rel + WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ scid }}::oid + ORDER BY rel.relname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..99727c9e8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/properties.sql @@ -0,0 +1,65 @@ +SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS relacl_str, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + (select nspname FROM pg_namespace WHERE oid = {{scid}}::oid ) as schema, + pg_get_userbyid(rel.relowner) AS relowner, rel.relhasoids, + rel.relhassubclass, rel.reltuples, des.description, con.conname, con.conkey, + EXISTS(select 1 FROM pg_trigger + JOIN pg_proc pt ON pt.oid=tgfoid AND pt.proname='logtrigger' + JOIN pg_proc pc ON pc.pronamespace=pt.pronamespace AND pc.proname='slonyversion' + WHERE tgrelid=rel.oid) AS isrepl, + (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount, + (SELECT ARRAY(SELECT CASE WHEN (nspname NOT LIKE E'pg\_%' AND nspname <> 'public') THEN + quote_ident(nspname)||'.'||quote_ident(c.relname) + ELSE quote_ident(c.relname) END AS inherited_tables + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhparent + JOIN pg_namespace n ON n.oid=c.relnamespace + WHERE i.inhrelid = rel.oid ORDER BY inhseqno)) AS coll_inherits, + (SELECT count(*) + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhparent + JOIN pg_namespace n ON n.oid=c.relnamespace + WHERE i.inhrelid = rel.oid) AS inherited_tables_cnt, + (CASE WHEN rel.relpersistence = 'u' THEN true ELSE false END) AS relpersistence, + substring(array_to_string(rel.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor, + (CASE WHEN (substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') = 'true') + THEN true ELSE false END) AS autovacuum_enabled, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age, + (CASE WHEN (substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') = 'true') + THEN true ELSE false END) AS toast_autovacuum_enabled, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, + array_to_string(rel.reloptions, ',') AS table_vacuum_settings_str, + array_to_string(tst.reloptions, ',') AS toast_table_vacuum_settings_str, + rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, rel.reloftype, typ.typname, + (CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, + -- Added for pgAdmin4 + (CASE WHEN (substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean THEN true ELSE false END) AS autovacuum_custom, + (CASE WHEN (substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean AND rel.reltoastrelid != 0 THEN true ELSE false END) AS toast_autovacuum, + + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS seclabels, + (CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table +FROM pg_class rel + LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p' + LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid + LEFT JOIN pg_type typ ON rel.reloftype=typ.oid +WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ scid }}::oid +{% if tid %} AND rel.oid = {{ tid }}::oid {% endif %} +ORDER BY rel.relname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/reset_stats.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/reset_stats.sql new file mode 100644 index 000000000..36eca0d20 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/reset_stats.sql @@ -0,0 +1 @@ +SELECT pg_stat_reset_single_table_counters({{tid}}) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/sql.sql new file mode 100644 index 000000000..17b35aede --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/sql.sql @@ -0,0 +1 @@ +TAKE ASHESH'S HELP ON THIS TASK :-) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/truncate.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/truncate.sql new file mode 100644 index 000000000..6a276f32b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/truncate.sql @@ -0,0 +1 @@ +TRUNCATE TABLE {{conn|qtIdent(data.schema, data.name)}}{% if cascade %} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/update.sql new file mode 100644 index 000000000..8c64dada3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.1_plus/update.sql @@ -0,0 +1,201 @@ +{% import 'macros/schemas/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% import 'macros/variable.macros' as VARIABLE %} +{#####################################################} +{## Rename table ##} +{#####################################################} +{% if data.name and data.name != o_data.name %} +ALTER TABLE {{conn|qtIdent(o_data.schema, o_data.name)}} + RENAME TO {{conn|qtIdent(data.name)}}; + +{% endif %} +{#####################################################} +{## Change table schema ##} +{#####################################################} +{% if data.schema and data.schema != o_data.schema %} +ALTER TABLE {{conn|qtIdent(o_data.schema, data.name)}} + SET SCHEMA {{conn|qtIdent(data.schema)}}; + +{% endif %} +{#####################################################} +{## Change table owner ##} +{#####################################################} +{% if data.relowner and data.relowner != o_data.relowner %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + OWNER TO {{conn|qtIdent(data.relowner)}}; + +{% endif %} +{#####################################################} +{## Update Inherits table definition ##} +{#####################################################} +{% if data.coll_inherits_added|length > 0 %} +{% for val in data.coll_inherits_added %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + INHERIT {{val}}; + +{% endfor %} +{% endif %} +{% if data.coll_inherits_removed|length > 0 %} +{% for val in data.coll_inherits_removed %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + NO INHERIT {{val}}; + +{% endfor %} +{% endif %} +{#####################################################} +{## Change hasOID attribute of table ##} +{#####################################################} +{% if data.relhasoids is defined and data.relhasoids != o_data.relhasoids %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + SET {% if data.relhasoids %}WITH{% else %}WITHOUT{% endif %} OIDS; + +{% endif %} +{#####################################################} +{## Change tablespace ##} +{#####################################################} +{% if data.spcname and data.spcname != o_data.spcname %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + SET TABLESPACE {{conn|qtIdent(data.spcname)}}; + +{% endif %} +{#####################################################} +{## change fillfactore settings ##} +{#####################################################} +{% if data.fillfactor and data.fillfactor != o_data.fillfactor %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + SET (FILLFACTOR={{data.fillfactor}}); + +{% endif %} +{###############################} +{## Table AutoVacuum settings ##} +{###############################} +{% if o_data.autovacuum_custom and data.autovacuum_custom == false %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} RESET ( + autovacuum_enabled, + autovacuum_analyze_scale_factor, + autovacuum_analyze_threshold, + autovacuum_freeze_max_age, + autovacuum_vacuum_cost_delay, + autovacuum_vacuum_cost_limit, + autovacuum_vacuum_scale_factor, + autovacuum_vacuum_threshold, + autovacuum_freeze_min_age, + autovacuum_freeze_table_age +); +{% elif data.autovacuum_enabled != o_data.autovacuum_enabled %} +{% if data.autovacuum_enabled and o_data.autovacuum_enabled == false %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( + autovacuum_enabled = true{% elif data.autovacuum_enabled == false and o_data.autovacuum_enabled %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( + autovacuum_enabled = false{% endif %} +{% if (data.autovacuum_enabled or o_data.autovacuum_enabled )and data.vacuum_table and data.vacuum_table.changed|length > 0 %} +{% for opt in data.vacuum_table.changed %}{% if opt.name and opt.value %} +{% if flag or (data.autovacuum_enabled and o_data.autovacuum_enabled == false) or (data.autovacuum_enabled == false and o_data.autovacuum_enabled) %} +, +{% else %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( +{% set flag = true %} +{% endif %} + {{opt.name}} = {{opt.value}}{% endif %} +{% if loop.index == data.vacuum_table.changed|length and (flag or (data.autovacuum_enabled and o_data.autovacuum_enabled == false) or (data.autovacuum_enabled == false and o_data.autovacuum_enabled))%} + +); +{% endif %} +{% endfor %} +{% elif (data.autovacuum_enabled and o_data.autovacuum_enabled == false) or (data.autovacuum_enabled == false and o_data.autovacuum_enabled) %} + +); +{% endif %} +{% endif %} +{#####################################} +{## Toast table AutoVacuum settings ##} +{#####################################} +{% if o_data.toast_autovacuum and data.toast_autovacuum == false %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} RESET ( + toast.autovacuum_enabled, + toast.autovacuum_freeze_max_age, + toast.autovacuum_vacuum_cost_delay, + toast.autovacuum_vacuum_cost_limit, + toast.autovacuum_vacuum_scale_factor, + toast.autovacuum_vacuum_threshold, + toast.autovacuum_freeze_min_age, + toast.autovacuum_freeze_table_age, + toast.autovacuum_analyze_threshold, + toast.autovacuum_analyze_scale_factor +); +{% elif data.toast_autovacuum_enabled != o_data.toast_autovacuum_enabled %} +{% if data.toast_autovacuum_enabled and o_data.toast_autovacuum_enabled == false %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( + toast.autovacuum_enabled = true{% elif data.toast_autovacuum_enabled == false and o_data.toast_autovacuum_enabled %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( + toast.autovacuum_enabled = false{% endif %} +{% if (data.toast_autovacuum_enabled or o_data.toast_autovacuum_enabled )and data.vacuum_toast and data.vacuum_toast.changed|length > 0 %} +{% for opt in data.vacuum_toast.changed %}{% if opt.name and opt.value %} +{% if flag or (data.toast_autovacuum_enabled and o_data.toast_autovacuum_enabled == false) or (data.toast_autovacuum_enabled == false and o_data.toast_autovacuum_enabled) %} +, +{% else %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( +{% set flag = true %} +{% endif %} + toast.{{opt.name}} = {{opt.value}}{% endif %} +{% if loop.index == data.vacuum_toast.changed|length and (flag or (data.toast_autovacuum_enabled and o_data.toast_autovacuum_enabled == false) or (data.toast_autovacuum_enabled == false and o_data.toast_autovacuum_enabled))%} + +); +{% endif %} +{% endfor %} +{% elif (data.toast_autovacuum_enabled and o_data.toast_autovacuum_enabled == false) or (data.toast_autovacuum_enabled == false and o_data.toast_autovacuum_enabled) %} + +); +{% endif %} +{% endif %} +{#####################################################} +{## Change table comments ##} +{#####################################################} +{% if data.description is defined and data.description != o_data.description %} +COMMENT ON TABLE {{conn|qtIdent(data.schema, data.name)}} + IS {{data.description|qtLiteral}}; + +{% endif %} +{#####################################################} +{## Update table Privileges ##} +{#####################################################} +{% if data.relacl %} +{% if 'deleted' in data.relacl %} +{% for priv in data.relacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.relacl %} +{% for priv in data.relacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.relacl %} +{% for priv in data.relacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{#####################################################} +{## Update table SecurityLabel ##} +{#####################################################} +{% if data.seclabels and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABEL.UNSET(conn, 'TABLE', data.name, r.provider, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.SET(conn, 'TABLE', data.name, r.provider, r.label, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.SET(conn, 'TABLE', data.name, r.provider, r.label, data.schema) }} +{% endfor %} +{% endif %} + +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/acl.sql new file mode 100644 index 000000000..56f1f76aa --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/acl.sql @@ -0,0 +1,46 @@ +{### SQL to fetch privileges for tablespace ###} +SELECT 'relacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname 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 rel.relacl + FROM pg_class rel + LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace + LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p' + LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid + LEFT JOIN pg_type typ ON rel.reloftype=typ.oid + WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ scid }}::oid + AND rel.oid = {{ tid }}::oid + ) acl, + (SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable + AS is_grantable, (d).privilege_type AS privilege_type FROM (SELECT + aclexplode(rel.relacl) as d + FROM pg_class rel + LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace + LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p' + LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid + LEFT JOIN pg_type typ ON rel.reloftype=typ.oid + WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ scid }}::oid + AND rel.oid = {{ tid }}::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 \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/backend_support.sql new file mode 100644 index 000000000..f9b956418 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/backend_support.sql @@ -0,0 +1,18 @@ +SELECT + CASE WHEN nsp.nspname IN ('sys', 'dbo', 'information_schema') THEN true ELSE false END AS dbSupport +FROM pg_namespace nsp +WHERE nsp.oid={{scid}}::int +AND ( + (nspname = 'pg_catalog' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'pgagent' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname = 'information_schema' AND EXISTS + (SELECT 1 FROM pg_class WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1)) + OR (nspname LIKE '_%' AND EXISTS + (SELECT 1 FROM pg_proc WHERE proname='slonyversion' AND pronamespace = nsp.oid LIMIT 1)) +) +AND + nspname NOT LIKE E'pg\\temp\\%' +AND + nspname NOT LIKE E'pg\\toast_temp\\%' \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/create.sql new file mode 100644 index 000000000..c0fb5e449 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/create.sql @@ -0,0 +1,157 @@ +{% import 'macros/schemas/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% import 'macros/variable.macros' as VARIABLE %} +{% import 'column/macros/security.macros' as COLUMN_SECLABLE %} +{% import 'column/macros/privilege.macros' as COLUMN_PRIVILEGE %} +{% import 'table/sql/macros/constraints.macro' as CONSTRAINTS %} +{#===========================================#} +{#====== MAIN TABLE TEMPLATE STARTS HERE ======#} +{#===========================================#} +{# + If user has not provided any details but only name then + add empty bracket with table name +#} +{% set empty_bracket = ""%} +{% if data.coll_inherits|length == 0 and data.columns|length == 0 and not data.typname and not data.like_relation and data.primary_key|length == 0 and data.unique_constraint|length == 0 and data.foreign_key|length == 0 and data.check_constraint|length == 0 and data.exclude_constraint|length == 0 %} +{% set empty_bracket = "\n(\n)"%} +{% endif %} +CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE {{conn|qtIdent(data.schema, data.name)}}{{empty_bracket}} +{% if data.typname %} + OF {{ data.typname }} +{% endif %} +{% if data.like_relation or data.coll_inherits or data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 or data.exclude_constraint|length > 0 %} +( +{% endif %} +{% if data.like_relation %} + LIKE {{ data.like_relation }}{% if data.like_default_value %} + + INCLUDING DEFAULTS{% endif %}{% if data.like_constraints %} + + INCLUDING CONSTRAINTS{% endif %}{% if data.like_indexes %} + + INCLUDING INDEXES{% endif %}{% if data.like_storage %} + + INCLUDING STORAGE{% endif %}{% if data.like_comments %} + + INCLUDING COMMENTS{% endif %}{% if data.columns|length > 0 %}, +{% endif %} + +{% endif %} +{### Add columns ###} +{% if data.columns and data.columns|length > 0 %} +{% for c in data.columns %} +{% if c.name and c.cltype %} +{% if loop.index != 1 %}, +{% endif %} + {{conn|qtIdent(c.name)}} {{c.cltype}}{% if c.attlen %} +({{c.attlen}}{% if c.attprecision%}, {{c.attprecision}}{% endif %}){% endif %}{% if c.hasSqrBracket %} +[]{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval %} DEFAULT {{c.defval}}{% endif %} +{% endif %} +{% endfor %} +{% endif %} +{# Macro to render for constraints #} +{% if data.primary_key|length > 0 %}{% if data.columns|length > 0 %},{% endif %} +{{CONSTRAINTS.PRIMARY_KEY(conn, data.primary_key[0])}}{% endif %}{% if data.unique_constraint|length > 0 %}{% if data.columns|length > 0 or data.primary_key|length > 0 %},{% endif %} +{{CONSTRAINTS.UNIQUE(conn, data.unique_constraint)}}{% endif %}{% if data.foreign_key|length > 0 %}{% if data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 %},{% endif %} +{{CONSTRAINTS.FOREIGN_KEY(conn, data.foreign_key)}}{% endif %}{% if data.check_constraint|length > 0 %}{% if data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 %},{% endif %} +{{CONSTRAINTS.CHECK(conn, data.check_constraint)}}{% endif %}{% if data.exclude_constraint|length > 0 %}{% if data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 %},{% endif %} +{{CONSTRAINTS.EXCLUDE(conn, data.exclude_constraint)}}{% endif %} +{% if data.like_relation or data.coll_inherits or data.columns|length > 0 or data.primary_key|length > 0 or data.unique_constraint|length > 0 or data.foreign_key|length > 0 or data.check_constraint|length > 0 or data.exclude_constraint|length > 0 %} + +) +{% endif %} +{### If we are inheriting it from another table(s) ###} +{% if data.coll_inherits %} + INHERITS ({% for val in data.coll_inherits %}{% if loop.index != 1 %}, {% endif %}{{val}}{% endfor %}) +{% endif %} +WITH ( + OIDS = {% if data.relhasoids %}TRUE{% else %}FALSE{% endif %}{% if data.fillfactor %}, + FILLFACTOR = {{ data.fillfactor }}{% endif %}{% if data.autovacuum_custom %}, + autovacuum_enabled = {% if data.autovacuum_enabled %}TRUE{% else %}FALSE{% endif %}{% endif %}{% if data.toast_autovacuum %}, + toast.autovacuum_enabled = {% if data.toast_autovacuum_enabled %}TRUE{% else %}FALSE{% endif %} +{% endif %}{% if data.autovacuum_enabled and data.vacuum_table|length > 0 %} +{% for opt in data.vacuum_table %}{% if opt.name and opt.value %} +, + {{opt.name}} = {{opt.value}}{% endif %} +{% endfor %}{% endif %}{% if data.toast_autovacuum_enabled and data.vacuum_toast|length > 0 %} +{% for opt in data.vacuum_toast %}{% if opt.name and opt.value %} +, + toast.{{opt.name}} = {{opt.value}}{% endif %} +{% endfor %}{% endif %} + +) +{### SQL for Tablespace ###} +{% if data.spcname %} +TABLESPACE {{ conn|qtIdent(data.spcname) }}; +{% endif %} +{### Alter SQL for Owner ###} +{% if data.relowner %} + +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + OWNER to {{conn|qtIdent(data.relowner)}}; +{% endif %} +{### Security Labels on Table ###} +{% if data.seclabels and data.seclabels|length > 0 %} + +{% for r in data.seclabels %} +{{ SECLABLE.SET(conn, 'TABLE', data.name, r.provider, r.label, data.schema) }} +{% endfor %} +{% endif %} +{### ACL on Table ###} +{% if data.relacl %} + +{% for priv in data.relacl %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{### SQL for COMMENT ###} +{% if data.description %} +COMMENT ON TABLE {{conn|qtIdent(data.schema, data.name)}} + IS {{data.description|qtLiteral}}; +{% endif %} +{#===========================================#} +{#====== MAIN TABLE TEMPLATE ENDS HERE ======#} +{#===========================================#} +{#===========================================#} +{# COLUMN SPECIFIC TEMPLATES STARTS HERE #} +{#===========================================#} +{% if data.columns and data.columns|length > 0 %} +{% for c in data.columns %} +{% if c.description %} + +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.name, c.name)}} + IS {{c.description|qtLiteral}}; +{% endif %} +{### Add variables to column ###} +{% if c.attoptions and c.attoptions|length > 0 %} + +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + {{ VARIABLE.SET(conn, 'COLUMN', c.name, c.attoptions) }} +{% endif %} +{### ACL ###} +{% if c.attacl and c.attacl|length > 0 %} + +{% for priv in c.attacl %} + {{ COLUMN_PRIVILEGE.APPLY(conn, data.schema, data.name, c.name, priv.grantee, priv.without_grant, priv.with_grant) }} +{% endfor %} +{% endif %} +{### Security Lables ###} +{% if c.seclabels and c.seclabels|length > 0 %} + +{% for r in c.seclabels %} +{{ COLUMN_SECLABLE.APPLY(conn, 'COLUMN',data.schema, data.name, c.name, r.provider, r.label) }} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{#===========================================#} +{# COLUMN SPECIFIC TEMPLATES ENDS HERE #} +{#===========================================#} +{#======================================#} +{# CONSTRAINTS SPECIFIC TEMPLATES #} +{#======================================#} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.primary_key)}} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.unique_constraint)}} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.foreign_key)}} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.check_constraint)}} +{{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.exclude_constraint)}} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/delete.sql new file mode 100644 index 000000000..01d0314b6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/delete.sql @@ -0,0 +1 @@ +DROP TABLE {{conn|qtIdent(data.schema, data.name)}}{% if cascade %} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/depend.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/depend.sql new file mode 100644 index 000000000..f5f39e7d0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/depend.sql @@ -0,0 +1,9 @@ +SELECT + ref.relname AS refname, d2.refclassid, dep.deptype AS deptype +FROM pg_depend dep + LEFT JOIN pg_depend d2 ON dep.objid=d2.objid AND dep.refobjid != d2.refobjid + LEFT JOIN pg_class ref ON ref.oid=d2.refobjid + LEFT JOIN pg_attribute att ON d2.refclassid=att.attrelid AND d2.refobjsubid=att.attnum + {{ where }} AND + dep.classid=(SELECT oid FROM pg_class WHERE relname='pg_attrdef') AND + dep.refobjid NOT IN (SELECT d3.refobjid FROM pg_depend d3 WHERE d3.objid=d2.refobjid) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/enable_disable_trigger.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/enable_disable_trigger.sql new file mode 100644 index 000000000..a4ab15401 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/enable_disable_trigger.sql @@ -0,0 +1,2 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.name) }} + {% if is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER ALL; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_columns_for_table.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_columns_for_table.sql new file mode 100644 index 000000000..3d6bbb7d2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_columns_for_table.sql @@ -0,0 +1,16 @@ +SELECT + a.attname AS name, format_type(a.atttypid, NULL) AS cltype, + quote_ident(n.nspname)||'.'||quote_ident(c.relname) as inheritedfrom, + c.oid as inheritedid +FROM + pg_class c +JOIN + pg_namespace n ON c.relnamespace=n.oid +JOIN + pg_attribute a ON a.attrelid = c.oid AND NOT a.attisdropped AND a.attnum > 0 +WHERE +{% if tid %} + c.oid = {{tid}}::OID +{% else %} + c.relname = {{tname|qtLiteral}} +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_inherits.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_inherits.sql new file mode 100644 index 000000000..6b2560d57 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_inherits.sql @@ -0,0 +1,16 @@ +SELECT c.oid, c.relname , nspname, +CASE WHEN nspname NOT LIKE E'pg\_%' THEN + quote_ident(nspname)||'.'||quote_ident(c.relname) +ELSE quote_ident(c.relname) +END AS inherits +FROM pg_class c +JOIN pg_namespace n +ON n.oid=c.relnamespace +WHERE relkind='r' +{% if not show_system_objects %} +AND (n.nspname NOT LIKE E'pg\_%' AND n.nspname NOT in ('information_schema')) +{% endif %} +{% if tid %} +AND c.oid != tid +{% endif %} +ORDER BY relnamespace, c.relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_oftype.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_oftype.sql new file mode 100644 index 000000000..aed42f26a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_oftype.sql @@ -0,0 +1,6 @@ +SELECT t.oid, + quote_ident(n.nspname)||'.'||quote_ident(t.typname) AS typname + FROM pg_type t, pg_namespace n +WHERE t.typtype='c' AND t.typnamespace=n.oid + AND NOT (n.nspname like 'pg_%' OR n.nspname='information_schema') +ORDER BY typname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_oid.sql new file mode 100644 index 000000000..e9dc7729c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_oid.sql @@ -0,0 +1,5 @@ +SELECT rel.oid as tid +FROM pg_class rel +WHERE rel.relkind IN ('r','s','t') +AND rel.relnamespace = {{ scid }}::oid +AND rel.relname = {{data.name|qtLiteral}} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_relations.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_relations.sql new file mode 100644 index 000000000..431ee882c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_relations.sql @@ -0,0 +1,6 @@ +SELECT c.oid, quote_ident(n.nspname)||'.'||quote_ident(c.relname) AS like_relation +FROM pg_class c, pg_namespace n +WHERE c.relnamespace=n.oid +AND +c.relkind IN ('r', 'v', 'f') +ORDER BY 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_tables_for_constraints.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_tables_for_constraints.sql new file mode 100644 index 000000000..136246349 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_tables_for_constraints.sql @@ -0,0 +1,8 @@ +SELECT cl.oid as value, quote_ident(nspname)||'.'||quote_ident(relname) AS label +FROM pg_namespace nsp, pg_class cl +WHERE relnamespace=nsp.oid AND relkind='r' + AND nsp.nspname NOT LIKE E'pg\_temp\_%' + {% if not show_sysobj %} + AND (nsp.nspname NOT LIKE E'pg\_%' AND nsp.nspname NOT in ('information_schema')) + {% endif %} +ORDER BY nspname, relname \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_types_where_condition.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_types_where_condition.sql new file mode 100644 index 000000000..fadfc9918 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/get_types_where_condition.sql @@ -0,0 +1,10 @@ +{### Additional where condition for get_types route for column node ###} +typisdefined AND typtype IN ('b', 'c', 'd', 'e', 'r') +AND NOT EXISTS (SELECT 1 FROM pg_class WHERE relnamespace=typnamespace +AND relname = typname AND relkind != 'c') AND +(typname NOT LIKE '_%' OR NOT EXISTS (SELECT 1 FROM pg_class WHERE +relnamespace=typnamespace AND relname = substring(typname FROM 2)::name +AND relkind != 'c')) +{% if not show_system_objects %} +AND nsp.nspname != 'information_schema' +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/nodes.sql new file mode 100644 index 000000000..43f14cbcf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/nodes.sql @@ -0,0 +1,6 @@ +SELECT rel.oid, rel.relname AS name, + (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount, + (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE AND tgenabled = 'O') AS has_enable_triggers +FROM pg_class rel + WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ scid }}::oid + ORDER BY rel.relname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/properties.sql new file mode 100644 index 000000000..99727c9e8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/properties.sql @@ -0,0 +1,65 @@ +SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS relacl_str, + (CASE WHEN length(spc.spcname) > 0 THEN spc.spcname ELSE 'pg_default' END) as spcname, + (select nspname FROM pg_namespace WHERE oid = {{scid}}::oid ) as schema, + pg_get_userbyid(rel.relowner) AS relowner, rel.relhasoids, + rel.relhassubclass, rel.reltuples, des.description, con.conname, con.conkey, + EXISTS(select 1 FROM pg_trigger + JOIN pg_proc pt ON pt.oid=tgfoid AND pt.proname='logtrigger' + JOIN pg_proc pc ON pc.pronamespace=pt.pronamespace AND pc.proname='slonyversion' + WHERE tgrelid=rel.oid) AS isrepl, + (SELECT count(*) FROM pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount, + (SELECT ARRAY(SELECT CASE WHEN (nspname NOT LIKE E'pg\_%' AND nspname <> 'public') THEN + quote_ident(nspname)||'.'||quote_ident(c.relname) + ELSE quote_ident(c.relname) END AS inherited_tables + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhparent + JOIN pg_namespace n ON n.oid=c.relnamespace + WHERE i.inhrelid = rel.oid ORDER BY inhseqno)) AS coll_inherits, + (SELECT count(*) + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhparent + JOIN pg_namespace n ON n.oid=c.relnamespace + WHERE i.inhrelid = rel.oid) AS inherited_tables_cnt, + (CASE WHEN rel.relpersistence = 'u' THEN true ELSE false END) AS relpersistence, + substring(array_to_string(rel.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor, + (CASE WHEN (substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') = 'true') + THEN true ELSE false END) AS autovacuum_enabled, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_vacuum_scale_factor, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS autovacuum_analyze_scale_factor, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age, + substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age, + (CASE WHEN (substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') = 'true') + THEN true ELSE false END) AS toast_autovacuum_enabled, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_vacuum_scale_factor, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9]*[.][0-9]*)') AS toast_autovacuum_analyze_scale_factor, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age, + substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, + array_to_string(rel.reloptions, ',') AS table_vacuum_settings_str, + array_to_string(tst.reloptions, ',') AS toast_table_vacuum_settings_str, + rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, rel.reloftype, typ.typname, + (CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, + -- Added for pgAdmin4 + (CASE WHEN (substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean THEN true ELSE false END) AS autovacuum_custom, + (CASE WHEN (substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)'))::boolean AND rel.reltoastrelid != 0 THEN true ELSE false END) AS toast_autovacuum, + + (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS seclabels, + (CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table +FROM pg_class rel + LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace + LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass) + LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p' + LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid + LEFT JOIN pg_type typ ON rel.reloftype=typ.oid +WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = {{ scid }}::oid +{% if tid %} AND rel.oid = {{ tid }}::oid {% endif %} +ORDER BY rel.relname; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/reset_stats.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/reset_stats.sql new file mode 100644 index 000000000..36eca0d20 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/reset_stats.sql @@ -0,0 +1 @@ +SELECT pg_stat_reset_single_table_counters({{tid}}) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/sql.sql new file mode 100644 index 000000000..17b35aede --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/sql.sql @@ -0,0 +1 @@ +TAKE ASHESH'S HELP ON THIS TASK :-) \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/truncate.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/truncate.sql new file mode 100644 index 000000000..6a276f32b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/truncate.sql @@ -0,0 +1 @@ +TRUNCATE TABLE {{conn|qtIdent(data.schema, data.name)}}{% if cascade %} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/update.sql new file mode 100644 index 000000000..8c64dada3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/9.5_plus/update.sql @@ -0,0 +1,201 @@ +{% import 'macros/schemas/security.macros' as SECLABLE %} +{% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% import 'macros/variable.macros' as VARIABLE %} +{#####################################################} +{## Rename table ##} +{#####################################################} +{% if data.name and data.name != o_data.name %} +ALTER TABLE {{conn|qtIdent(o_data.schema, o_data.name)}} + RENAME TO {{conn|qtIdent(data.name)}}; + +{% endif %} +{#####################################################} +{## Change table schema ##} +{#####################################################} +{% if data.schema and data.schema != o_data.schema %} +ALTER TABLE {{conn|qtIdent(o_data.schema, data.name)}} + SET SCHEMA {{conn|qtIdent(data.schema)}}; + +{% endif %} +{#####################################################} +{## Change table owner ##} +{#####################################################} +{% if data.relowner and data.relowner != o_data.relowner %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + OWNER TO {{conn|qtIdent(data.relowner)}}; + +{% endif %} +{#####################################################} +{## Update Inherits table definition ##} +{#####################################################} +{% if data.coll_inherits_added|length > 0 %} +{% for val in data.coll_inherits_added %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + INHERIT {{val}}; + +{% endfor %} +{% endif %} +{% if data.coll_inherits_removed|length > 0 %} +{% for val in data.coll_inherits_removed %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + NO INHERIT {{val}}; + +{% endfor %} +{% endif %} +{#####################################################} +{## Change hasOID attribute of table ##} +{#####################################################} +{% if data.relhasoids is defined and data.relhasoids != o_data.relhasoids %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + SET {% if data.relhasoids %}WITH{% else %}WITHOUT{% endif %} OIDS; + +{% endif %} +{#####################################################} +{## Change tablespace ##} +{#####################################################} +{% if data.spcname and data.spcname != o_data.spcname %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + SET TABLESPACE {{conn|qtIdent(data.spcname)}}; + +{% endif %} +{#####################################################} +{## change fillfactore settings ##} +{#####################################################} +{% if data.fillfactor and data.fillfactor != o_data.fillfactor %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + SET (FILLFACTOR={{data.fillfactor}}); + +{% endif %} +{###############################} +{## Table AutoVacuum settings ##} +{###############################} +{% if o_data.autovacuum_custom and data.autovacuum_custom == false %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} RESET ( + autovacuum_enabled, + autovacuum_analyze_scale_factor, + autovacuum_analyze_threshold, + autovacuum_freeze_max_age, + autovacuum_vacuum_cost_delay, + autovacuum_vacuum_cost_limit, + autovacuum_vacuum_scale_factor, + autovacuum_vacuum_threshold, + autovacuum_freeze_min_age, + autovacuum_freeze_table_age +); +{% elif data.autovacuum_enabled != o_data.autovacuum_enabled %} +{% if data.autovacuum_enabled and o_data.autovacuum_enabled == false %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( + autovacuum_enabled = true{% elif data.autovacuum_enabled == false and o_data.autovacuum_enabled %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( + autovacuum_enabled = false{% endif %} +{% if (data.autovacuum_enabled or o_data.autovacuum_enabled )and data.vacuum_table and data.vacuum_table.changed|length > 0 %} +{% for opt in data.vacuum_table.changed %}{% if opt.name and opt.value %} +{% if flag or (data.autovacuum_enabled and o_data.autovacuum_enabled == false) or (data.autovacuum_enabled == false and o_data.autovacuum_enabled) %} +, +{% else %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( +{% set flag = true %} +{% endif %} + {{opt.name}} = {{opt.value}}{% endif %} +{% if loop.index == data.vacuum_table.changed|length and (flag or (data.autovacuum_enabled and o_data.autovacuum_enabled == false) or (data.autovacuum_enabled == false and o_data.autovacuum_enabled))%} + +); +{% endif %} +{% endfor %} +{% elif (data.autovacuum_enabled and o_data.autovacuum_enabled == false) or (data.autovacuum_enabled == false and o_data.autovacuum_enabled) %} + +); +{% endif %} +{% endif %} +{#####################################} +{## Toast table AutoVacuum settings ##} +{#####################################} +{% if o_data.toast_autovacuum and data.toast_autovacuum == false %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} RESET ( + toast.autovacuum_enabled, + toast.autovacuum_freeze_max_age, + toast.autovacuum_vacuum_cost_delay, + toast.autovacuum_vacuum_cost_limit, + toast.autovacuum_vacuum_scale_factor, + toast.autovacuum_vacuum_threshold, + toast.autovacuum_freeze_min_age, + toast.autovacuum_freeze_table_age, + toast.autovacuum_analyze_threshold, + toast.autovacuum_analyze_scale_factor +); +{% elif data.toast_autovacuum_enabled != o_data.toast_autovacuum_enabled %} +{% if data.toast_autovacuum_enabled and o_data.toast_autovacuum_enabled == false %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( + toast.autovacuum_enabled = true{% elif data.toast_autovacuum_enabled == false and o_data.toast_autovacuum_enabled %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( + toast.autovacuum_enabled = false{% endif %} +{% if (data.toast_autovacuum_enabled or o_data.toast_autovacuum_enabled )and data.vacuum_toast and data.vacuum_toast.changed|length > 0 %} +{% for opt in data.vacuum_toast.changed %}{% if opt.name and opt.value %} +{% if flag or (data.toast_autovacuum_enabled and o_data.toast_autovacuum_enabled == false) or (data.toast_autovacuum_enabled == false and o_data.toast_autovacuum_enabled) %} +, +{% else %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET ( +{% set flag = true %} +{% endif %} + toast.{{opt.name}} = {{opt.value}}{% endif %} +{% if loop.index == data.vacuum_toast.changed|length and (flag or (data.toast_autovacuum_enabled and o_data.toast_autovacuum_enabled == false) or (data.toast_autovacuum_enabled == false and o_data.toast_autovacuum_enabled))%} + +); +{% endif %} +{% endfor %} +{% elif (data.toast_autovacuum_enabled and o_data.toast_autovacuum_enabled == false) or (data.toast_autovacuum_enabled == false and o_data.toast_autovacuum_enabled) %} + +); +{% endif %} +{% endif %} +{#####################################################} +{## Change table comments ##} +{#####################################################} +{% if data.description is defined and data.description != o_data.description %} +COMMENT ON TABLE {{conn|qtIdent(data.schema, data.name)}} + IS {{data.description|qtLiteral}}; + +{% endif %} +{#####################################################} +{## Update table Privileges ##} +{#####################################################} +{% if data.relacl %} +{% if 'deleted' in data.relacl %} +{% for priv in data.relacl.deleted %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in data.relacl %} +{% for priv in data.relacl.changed %} +{{ PRIVILEGE.UNSETALL(conn, 'TABLE', priv.grantee, data.name, data.schema) }} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in data.relacl %} +{% for priv in data.relacl.added %} +{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }} +{% endfor %} +{% endif %} +{% endif %} +{#####################################################} +{## Update table SecurityLabel ##} +{#####################################################} +{% if data.seclabels and data.seclabels|length > 0 %} +{% set seclabels = data.seclabels %} +{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %} +{% for r in seclabels.deleted %} +{{ SECLABEL.UNSET(conn, 'TABLE', data.name, r.provider, data.schema) }} +{% endfor %} +{% endif %} +{% if 'added' in seclabels and seclabels.added|length > 0 %} +{% for r in seclabels.added %} +{{ SECLABLE.SET(conn, 'TABLE', data.name, r.provider, r.label, data.schema) }} +{% endfor %} +{% endif %} +{% if 'changed' in seclabels and seclabels.changed|length > 0 %} +{% for r in seclabels.changed %} +{{ SECLABLE.SET(conn, 'TABLE', data.name, r.provider, r.label, data.schema) }} +{% endfor %} +{% endif %} + +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/macros/constraints.macro b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/macros/constraints.macro new file mode 100644 index 000000000..fe7389a21 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/sql/macros/constraints.macro @@ -0,0 +1,103 @@ +{##########################} +{# Macros for Constraints #} +{##########################} +{# CREATE MODE ONLY #} +{##########################} +{% macro PRIMARY_KEY(conn, data) -%} +{% if data.columns|length > 0 %} + + {% if data.name %}CONSTRAINT {{conn|qtIdent(data.name)}} {% endif %}PRIMARY KEY ({% for c in data.columns%} +{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.column)}}{% endfor %}){% if data.fillfactor %} + + WITH (FILLFACTOR={{data.fillfactor}}){% endif %} +{% if data.spcname and data.spcname != "pg_default" %} + + USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %} +{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} INITIALLY DEFERRED{% endif%}{% endif%} +{% endif %} +{%- endmacro %} +{% macro UNIQUE(conn, unique_data) -%} +{% for data in unique_data %} +{% if data.columns|length > 0 %}{% if loop.index !=1 %},{% endif %} + + {% if data.name %}CONSTRAINT {{conn|qtIdent(data.name)}} {% endif %}UNIQUE ({% for c in data.columns%} +{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.column)}}{% endfor %}){% if data.fillfactor %} + + WITH (FILLFACTOR={{data.fillfactor}}){% endif %} +{% if data.spcname and data.spcname != "pg_default" %} + + USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %} +{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} INITIALLY DEFERRED{% endif%}{% endif%} +{% endif %} +{% endfor %} +{%- endmacro %} +{% macro CHECK(conn, check_data) -%} +{% for data in check_data %}{% if loop.index !=1 %},{% endif %} + + {% if data.name %}CONSTRAINT {{ conn|qtIdent(data.name) }} {% endif%}CHECK ({{ data.consrc }}){% if data.convalidated %} + NOT VALID{% endif %}{% if data.connoinherit %} NO INHERIT{% endif %} +{% endfor %} +{%- endmacro %} +{% macro FOREIGN_KEY(conn, foreign_key_data) -%} +{% for data in foreign_key_data %}{% if loop.index != 1 %},{% endif %} + + {% if data.name %}CONSTRAINT {{conn|qtIdent(data.name)}} {% endif %}FOREIGN KEY ({% for columnobj in data.columns %}{% if loop.index != 1 %} +, {% endif %}{{ conn|qtIdent(columnobj.local_column)}}{% endfor %}) + REFERENCES {{ conn|qtIdent(data.remote_schema, data.remote_table) }} ({% for columnobj in data.columns %}{% if loop.index != 1 %} +, {% endif %}{{ conn|qtIdent(columnobj.referenced)}}{% endfor %}) {% if data.confmatchtype %}MATCH FULL{% else %}MATCH SIMPLE{% endif%} + + ON UPDATE{% if data.confupdtype == 'a' %} + NO ACTION{% elif data.confupdtype == 'r' %} + RESTRICT{% elif data.confupdtype == 'c' %} + CASCADE{% elif data.confupdtype == 'n' %} + SET NULL{% elif data.confupdtype == 'd' %} + SET DEFAULT{% endif %} + + ON DELETE{% if data.confdeltype == 'a' %} + NO ACTION{% elif data.confdeltype == 'r' %} + RESTRICT{% elif data.confdeltype == 'c' %} + CASCADE{% elif data.confdeltype == 'n' %} + SET NULL{% elif data.confdeltype == 'd' %} + SET DEFAULT{% endif %} +{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} + INITIALLY DEFERRED{% endif%} +{% endif%} +{% if data.convalidated %} + + NOT VALID{% endif%} +{% endfor %} +{%- endmacro %} +{% macro EXCLUDE(conn, exclude_data) -%} +{% for data in exclude_data %}{% if loop.index != 1 %},{% endif %} + + {% if data.name %}CONSTRAINT {{ conn|qtIdent(data.name) }} {% endif%}EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} ({% for col in data.columns %}{% if loop.index != 1 %}, + {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %} + + WITH (FILLFACTOR={{data.fillfactor}}){% endif %} + {% if data.spcname and data.spcname != "pg_default" %} + + USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %} +{% if data.condeferrable %} + + DEFERRABLE{% if data.condeferred %} + INITIALLY DEFERRED{% endif%} +{% endif%}{% if data.constraint %} WHERE ({{data.constraint}}){% endif%} +{% endfor %} +{%- endmacro %} +{##########################} +{# COMMENTS ONLY #} +{##########################} +{% macro CONSTRAINT_COMMENTS(conn, schema, table, data) -%} +{% for d in data %} +{% if d.name and d.comment %} +COMMENT ON CONSTRAINT {{ conn|qtIdent(d.name) }} ON {{ conn|qtIdent(schema, table) }} + IS {{ d.comment|qtLiteral }}; +{% endif %} +{% endfor %} +{%- endmacro %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/alter.sql new file mode 100644 index 000000000..93f323e1a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/alter.sql @@ -0,0 +1,9 @@ +{## Alter index to use cluster type ##} +{% if data.indisclustered %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + CLUSTER ON {{conn|qtIdent(data.name)}}; +{% endif %} +{## Changes description ##} +{% if data.description %} +COMMENT ON INDEX {{conn|qtIdent(data.name)}} + IS {{data.description|qtLiteral}};{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/backend_support.sql new file mode 100644 index 000000000..bb5e8d803 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/backend_support.sql @@ -0,0 +1,9 @@ +{#=============Checks if it is materialized view========#} +{% if vid %} +SELECT + CASE WHEN c.relkind = 'm' THEN False ELSE True END As m_view +FROM + pg_class c +WHERE + c.oid = {{ vid }}::oid +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/create.sql new file mode 100644 index 000000000..b619f71ba --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/create.sql @@ -0,0 +1,27 @@ +{### Set a flag which allows us to put OR between events ###} +{% set or_flag = False %} +CREATE{% if data.is_constraint_trigger %} CONSTRAINT{% endif %} TRIGGER {{ conn|qtIdent(data.name) }} + {{data.fires}} {% if data.evnt_insert %}INSERT{% set or_flag = True %} +{% endif %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} +{% endif %}{% if data.evnt_turncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} +{% endif %}{% if data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE {% if data.columns|length > 0 %}OF {% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c.column) }}{% endfor %}{% endif %} +{% endif %} + + ON {{ conn|qtIdent(data.schema, data.table) }} +{% if data.tgdeferrable %} + DEFERRABLE{% if data.tginitdeferred %} INITIALLY DEFERRED{% endif %} +{% endif %} + FOR EACH{% if data.is_row_trigger %} ROW{% else %} STATEMENT{% endif %} +{% if data.whenclause %} + + WHEN {{ data.whenclause }}{% endif %} + + {% if data.code %}{{ data.code }}{% else %}EXECUTE PROCEDURE {{ data.tfunction }}{% if data.tgargs %}({{ data.tgargs }}){% else %}(){% endif%}{% endif%}; + +{% if data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{data.description|qtLiteral}}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/delete.sql new file mode 100644 index 000000000..4c6e82b28 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/delete.sql @@ -0,0 +1 @@ +DROP TRIGGER {{conn|qtIdent(data.name)}} ON {{conn|qtIdent(data.nspname, data.relname )}}{% if cascade %} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/enable_disable_trigger.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/enable_disable_trigger.sql new file mode 100644 index 000000000..b7009272d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/enable_disable_trigger.sql @@ -0,0 +1,2 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + {% if data.is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER {{ conn|qtIdent(data.name) }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_columns.sql new file mode 100644 index 000000000..c74c68b6a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_columns.sql @@ -0,0 +1,6 @@ +SELECT att.attname as name +FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attnum IN ({{ clist }}) + AND att.attisdropped IS FALSE + ORDER BY att.attnum \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_oid.sql new file mode 100644 index 000000000..cf30257bb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_oid.sql @@ -0,0 +1,5 @@ +SELECT t.oid +FROM pg_trigger t + WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND tgname = {{data.name|qtLiteral}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_parent.sql new file mode 100644 index 000000000..5dd5d3ce0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_parent.sql @@ -0,0 +1,5 @@ +SELECT nsp.nspname AS schema ,rel.relname AS table +FROM pg_class rel + JOIN pg_namespace nsp + ON rel.relnamespace = nsp.oid::int + WHERE rel.oid = {{tid}}::int \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_triggerfunctions.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_triggerfunctions.sql new file mode 100644 index 000000000..6134e0ecd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/get_triggerfunctions.sql @@ -0,0 +1,11 @@ +SELECT quote_ident(nspname) || '.' || quote_ident(proname) AS tfunctions +FROM pg_proc p, pg_namespace n, pg_language l + WHERE p.pronamespace = n.oid + AND p.prolang = l.oid + -- PGOID_TYPE_TRIGGER = 2279 + AND l.lanname != 'edbspl' AND prorettype = 2279 + -- If Show SystemObjects is not true + {% if not show_system_objects %} + AND (nspname NOT LIKE E'pg\_%' AND nspname NOT in ('information_schema')) + {% endif %} + ORDER BY nspname ASC, proname ASC diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/nodes.sql new file mode 100644 index 000000000..095ada369 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/nodes.sql @@ -0,0 +1,5 @@ +SELECT t.oid, t.tgname as name, (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/properties.sql new file mode 100644 index 000000000..535627bbe --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/properties.sql @@ -0,0 +1,23 @@ +SELECT t.oid,t.tgname AS name, t.xmin, t.*, relname, CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname, des.description, l.lanname, p.prosrc, p.proname AS tfunction, + COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) EXECUTE PROCEDURE'), + substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \\$trigger')) AS whenclause, + -- We need to convert tgargs column bytea datatype to array datatype + (string_to_array(encode(tgargs, 'escape'), '\000')::text[])[1:tgnargs] AS tgargs, +{% if datlastsysoid %} + (CASE WHEN t.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_trigger, +{% endif %} + (CASE WHEN tgconstraint != 0::OID THEN true ElSE false END) AS is_constarint, + (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + JOIN pg_class cl ON cl.oid=tgrelid + JOIN pg_namespace na ON na.oid=relnamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass) + LEFT OUTER JOIN pg_proc p ON p.oid=t.tgfoid + LEFT OUTER JOIN pg_language l ON l.oid=p.prolang +WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} +ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/update.sql new file mode 100644 index 000000000..fa648091f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/trigger/sql/9.1_plus/update.sql @@ -0,0 +1,8 @@ +{% if data.name and o_data.name != data.name %} +ALTER TRIGGER {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} +{% if data.description is defined and o_data.description != data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{data.description|qtLiteral}}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py new file mode 100644 index 000000000..634483ab3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py @@ -0,0 +1,944 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Trigger Node """ + +from flask import render_template, make_response, request, jsonify +from flask.ext.babel import gettext +from pgadmin.utils.ajax import make_json_response, \ + make_response as ajax_response, internal_server_error +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.browser.collection import CollectionNodeModule +import pgadmin.browser.server_groups.servers.databases as database +from pgadmin.browser.server_groups.servers.databases.schemas.utils import \ + trigger_definition as _trigger_definition +from pgadmin.utils.ajax import precondition_required +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from functools import wraps +import json + + +class TriggerModule(CollectionNodeModule): + """ + class TriggerModule(CollectionNodeModule) + + A module class for Trigger node derived from CollectionNodeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the Trigger and it's base module. + + * get_nodes(gid, sid, did, scid, tid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for trigger, when any of the server node is + initialized. + """ + + NODE_TYPE = 'trigger' + COLLECTION_LABEL = gettext("Triggers") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the TriggerModule and it's base module. + + Args: + *args: + **kwargs: + """ + self.min_ver = None + self.max_ver = None + super(TriggerModule, self).__init__(*args, **kwargs) + + def BackendSupported(self, manager, **kwargs): + """ + Load this module if vid is view, we will not load it under + material view + """ + if super(TriggerModule, self).BackendSupported(manager, **kwargs): + conn = manager.connection(did=kwargs['did']) + # If DB is not connected then return error to browser + if not conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + if 'vid' not in kwargs: + return True + + template_path = 'trigger/sql/9.1_plus' + SQL = render_template("/".join( + [template_path, 'backend_support.sql']), vid=kwargs['vid']) + status, res = conn.execute_scalar(SQL) + # check if any errors + if not status: + return internal_server_error(errormsg=res) + # Check vid is view not material view + # then true, othewise false + return res + + def get_nodes(self, gid, sid, did, scid, **kwargs): + """ + Generate the collection node + """ + assert('tid' in kwargs or 'vid' in kwargs) + yield self.generate_browser_collection_node( + kwargs['tid'] if 'tid' in kwargs else kwargs['vid'] + ) + + @property + def script_load(self): + """ + Load the module script for server, when any of the server-group node is + initialized. + """ + return database.DatabaseModule.NODE_TYPE + + @property + def node_inode(self): + """ + Load the module node as a leaf node + """ + return True + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "trigger/css/trigger.css", + node_type=self.node_type + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + +blueprint = TriggerModule(__name__) + + +class TriggerView(PGChildNodeView): + """ + This class is responsible for generating routes for Trigger node + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the TriggerView and it's base view. + + * module_js() + - This property defines (if javascript) exists for this node. + Override this property for your own logic + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * list() + - This function is used to list all the Trigger nodes within that + collection. + + * nodes() + - This function will used to create all the child node within that + collection, Here it will create all the Trigger node. + + * properties(gid, sid, did, scid, tid, trid) + - This function will show the properties of the selected Trigger node + + * create(gid, sid, did, scid, tid) + - This function will create the new Trigger object + + * update(gid, sid, did, scid, tid, trid) + - This function will update the data for the selected Trigger node + + * delete(self, gid, sid, scid, tid, trid): + - This function will drop the Trigger object + + * enable(self, gid, sid, scid, tid, trid): + - This function will enable/disable Trigger object + + * msql(gid, sid, did, scid, tid, trid) + - This function is used to return modified SQL for the selected + Trigger node + + * get_sql(data, scid, tid, trid) + - This function will generate sql from model data + + * sql(gid, sid, did, scid, tid, trid): + - This function will generate sql to show it in sql pane for the + selected Trigger node. + + * dependency(gid, sid, did, scid, tid, trid): + - This function will generate dependency list show it in dependency + pane for the selected Trigger node. + + * dependent(gid, sid, did, scid, tid, trid): + - This function will generate dependent list to show it in dependent + pane for the selected Trigger node. + + * get_trigger_functions(gid, sid, did, scid, tid, trid): + - This function will return list of trigger functions available + via AJAX response + + * _column_details(tid, clist):: + - This function will fetch the columns for trigger + + * _trigger_definition(data): + - This function will set additional trigger definitions in + AJAX response + """ + + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [ + {'type': 'int', 'id': 'trid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create'} + ], + 'delete': [{'delete': 'delete'}], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'module.js': [{}, {}, {'get': 'module_js'}], + 'get_triggerfunctions': [{'get': 'get_trigger_functions'}, + {'get': 'get_trigger_functions'}], + 'enable': [{'put': 'enable_disable_trigger'}] + }) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + # If DB not connected then return error to browser + if not self.conn.connected(): + return precondition_required( + gettext( + "Connection to the server has been lost!" + ) + ) + + # We need datlastsysoid to check if current trigger is system trigger + self.datlastsysoid = self.manager.db_info[kwargs['did']]['datlastsysoid'] + + # we will set template path for sql scripts + self.template_path = 'trigger/sql/9.1_plus' + # Store server type + self.server_type = self.manager.server_type + # We need parent's name eg table name and schema name + # when we create new trigger in update we can fetch it using + # property sql + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=kwargs['tid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + self.schema = row['schema'] + self.table = row['table'] + + # Here we are storing trigger definition + # We will use it to check trigger type definition + self.trigger_definition = { + 'TRIGGER_TYPE_ROW': (1 << 0), + 'TRIGGER_TYPE_BEFORE': (1 << 1), + 'TRIGGER_TYPE_INSERT': (1 << 2), + 'TRIGGER_TYPE_DELETE': (1 << 3), + 'TRIGGER_TYPE_UPDATE': (1 << 4), + 'TRIGGER_TYPE_TRUNCATE': (1 << 5), + 'TRIGGER_TYPE_INSTEAD': (1 << 6) + } + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def get_trigger_functions(self, gid, sid, did, scid, tid, trid=None): + """ + This function will return list of trigger functions available + via AJAX response + """ + res = [{'label': '', 'value': ''}] + + # TODO: REMOVE True Condition , it's just for testing + # If server type is EDB-PPAS then we also need to add + # inline edb-spl along with options fetched by below sql + + if self.server_type == 'ppas': + res.append({ + 'label': 'Inline EDB-SPL', + 'value': 'Inline EDB-SPL' + }) + try: + SQL = render_template("/".join([self.template_path, + 'get_triggerfunctions.sql']), + show_system_objects=self.blueprint.show_system_objects + ) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=res) + + for row in rset['rows']: + res.append( + {'label': row['tfunctions'], + 'value': row['tfunctions']} + ) + return make_json_response( + data=res, + status=200 + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def list(self, gid, sid, did, scid, tid): + """ + This function is used to list all the trigger nodes within that collection. + + Args: + gid: Server group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of available trigger nodes + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), tid=tid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, tid): + """ + This function will used to create all the child node within that collection. + Here it will create all the trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of available trigger child nodes + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), tid=tid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon="icon-trigger" if row['is_enable_trigger'] + else "icon-trigger-bad" + )) + + return make_json_response( + data=res, + status=200 + ) + + def _column_details(self, tid, clist): + """ + This functional will fetch list of column for trigger + + Args: + tid: Table OID + clist: List of columns + + Returns: + Updated properties data with column + """ + + SQL = render_template("/".join([self.template_path, + 'get_columns.sql']), + tid=tid, clist=clist) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + # 'tgattr' contains list of columns from table used in trigger + columns = [] + + for row in rset['rows']: + columns.append({'column': row['name']}) + + return columns + + def _trigger_definition(self, data): + """ + This functional will set the trigger definition + + Args: + data: Properties data + + Returns: + Updated properties data with trigger definition + """ + + # Fires event definition + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_BEFORE']: + data['fires'] = 'BEFORE' + elif data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_INSTEAD']: + data['fires'] = 'INSTEAD OF' + else: + data['fires'] = 'AFTER' + + # Trigger of type definition + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_ROW']: + data['is_row_trigger'] = True + else: + data['is_row_trigger'] = False + + # Event definition + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_INSERT']: + data['evnt_insert'] = True + else: + data['evnt_insert'] = False + + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_DELETE']: + data['evnt_delete'] = True + else: + data['evnt_delete'] = False + + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_UPDATE']: + data['evnt_update'] = True + else: + data['evnt_update'] = False + + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_TRUNCATE']: + data['evnt_turncate'] = True + else: + data['evnt_turncate'] = False + + return data + + @check_precondition + def properties(self, gid, sid, did, scid, tid, trid): + """ + This function will show the properties of the selected trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + + Returns: + JSON of selected trigger node + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + # Making copy of output for future use + data = dict(res['rows'][0]) + if data['tgnargs'] > 1: + # We know that trigger has more than 1 arguments, let's join them + # and convert it as string + data['tgargs'] = ', '.join(data['tgargs']) + + if len(data['tgattr']) > 1: + columns = ', '.join(data['tgattr'].split(' ')) + data['columns'] = self._column_details(tid, columns) + + data = self._trigger_definition(data) + + return ajax_response( + response=data, + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid, tid): + """ + This function will creates new the trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + data = request.form if request.form else json.loads( + request.data.decode() + ) + + for k, v in data.items(): + try: + data[k] = json.loads(v) + except (ValueError, TypeError): + data[k] = v + + required_args = { + 'name': 'Name', + 'tfunction': 'Trigger function' + } + + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext("Couldn't find the required parameter (%s)." % \ + required_args[arg]) + ) + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + try: + SQL = render_template("/".join([self.template_path, + 'create.sql']), + data=data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # we need oid to to add object in tree at browser + SQL = render_template("/".join([self.template_path, + 'get_oid.sql']), + tid=tid, data=data) + status, trid = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=tid) + + return jsonify( + node=self.blueprint.generate_browser_node( + trid, + scid, + data['name'], + icon="icon-trigger" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, trid): + """ + This function will updates existing the trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + # Below will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + + try: + # We will first fetch the trigger name for current request + # so that we create template for dropping trigger + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = dict(res['rows'][0]) + + SQL = render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn, cascade=cascade) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Trigger is dropped"), + data={ + 'id': trid, + 'tid': tid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, tid, trid): + """ + This function will updates existing the trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + data = request.form if request.form else json.loads(request.data.decode()) + + try: + SQL = self.get_sql(scid, tid, trid, data) + if SQL and SQL.strip('\n') and SQL.strip(' '): + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Trigger updated", + data={ + 'id': trid, + 'tid': tid, + 'scid': scid + } + ) + else: + return make_json_response( + success=1, + info="Nothing to update", + data={ + 'id': trid, + 'tid': tid, + 'scid': scid + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, tid, trid=None): + """ + This function will generates modified sql for trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID (When working with existing trigger) + """ + data = dict() + for k, v in request.args.items(): + try: + data[k] = json.loads(v) + except ValueError: + data[k] = v + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + try: + SQL = self.get_sql(scid, tid, trid, data) + + if SQL and SQL.strip('\n') and SQL.strip(' '): + return make_json_response( + data=SQL, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_sql(self, scid, tid, trid, data): + """ + This function will genrate sql from model data + """ + if trid is not None: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + old_data = dict(res['rows'][0]) + + # If name is not present in data then + # we will fetch it from old data, we also need schema & table name + if 'name' not in data: + data['name'] = old_data['name'] + + if old_data['tgnargs'] > 1: + # We know that trigger has more than 1 arguments, let's join them + old_data['tgargs'] = ', '.join(old_data['tgargs']) + + if len(old_data['tgattr']) > 1: + columns = ', '.join(old_data['tgattr'].split(' ')) + old_data['columns'] = self._column_details(tid, columns) + + old_data = self._trigger_definition(old_data) + + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data, conn=self.conn + ) + else: + required_args = { + 'name': 'Name', + 'tfunction': 'Trigger function' + } + + for arg in required_args: + if arg not in data: + return gettext('-- incomplete definition') + + # If the request for new object which do not have did + SQL = render_template("/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn) + return SQL + + @check_precondition + def sql(self, gid, sid, did, scid, tid, trid): + """ + This function will generates reverse engineered sql for trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + try: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + if data['tgnargs'] > 1: + # We know that trigger has more than 1 arguments, let's join them + data['tgargs'] = ', '.join(data['tgargs']) + + if len(data['tgattr']) > 1: + columns = ', '.join(data['tgattr'].split(' ')) + data['columns'] = self._column_details(tid, columns) + + data = self._trigger_definition(data) + + SQL = self.get_sql(scid, tid, None, data) + + sql_header = "-- Trigger: {0}\n\n-- ".format(data['name']) + sql_header += render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn) + + SQL = sql_header + '\n\n' + SQL.strip('\n') + + # If trigger is disbaled then add sql code for the same + if not data['is_enable_trigger']: + SQL += '\n\n' + SQL += render_template("/".join([self.template_path, + 'enable_disable_trigger.sql']), + data=data, conn=self.conn) + + return ajax_response(response=SQL) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def enable_disable_trigger(self, gid, sid, did, scid, tid, trid): + """ + This function will enable OR disable the current trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + + data = request.form if request.form else json.loads(request.data.decode()) + + # Convert str 'true' to boolean type + is_enable_flag = json.loads(data['enable']) + + try: + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + o_data = dict(res['rows'][0]) + + # If enable is set to true means we need SQL to enable + # current trigger which is disabled already so we need to + # alter the 'is_enable_trigger' flag so that we can render + # correct SQL for operation + o_data['is_enable_trigger'] = is_enable_flag + + # Adding parent into data dict, will be using it while creating sql + o_data['schema'] = self.schema + o_data['table'] = self.table + + SQL = render_template("/".join([self.template_path, + 'enable_disable_trigger.sql']), + data=o_data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Trigger updated", + data={ + 'id': trid, + 'tid': tid, + 'scid': scid + } + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + + @check_precondition + def dependents(self, gid, sid, did, scid, tid, trid): + """ + This function get the dependents and return ajax response + for the trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + dependents_result = self.get_dependents( + self.conn, trid + ) + + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid, trid): + """ + This function get the dependencies and return ajax response + for the trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + + """ + dependencies_result = self.get_dependencies( + self.conn, trid + ) + + return ajax_response( + response=dependencies_result, + status=200 + ) + +TriggerView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/coll-trigger.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/coll-trigger.png new file mode 100644 index 0000000000000000000000000000000000000000..3c339406fa325dad67f59141a6a08a5334debcda GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFv5AX?b1=6kiPU=rwT=MDBga5xP z_8ouw|L>&#A2Xj{cR#w-VBI{+?aRZi97(#*xCE$_u_VYZn8D%MjWi&Kv%n*=7)X17 zvD?XPJ0K&^)5S4_<9f0{kWc~xb80dxhx5@;Pr)+*-bQ9j-mAoSvNTT3G+F96CDTtU zys(goD^vGXfVVgEs_XYmg7@y()6wYo{DM|*UmqVUo8r>Dvy9XIn6#3t7H`cGJ>lb~ z`u6T@g>5h9G)5dcu;fSs3qwIU#U}a)#Wn!vrU}|MxFz=n-DijU5`6-!cl@JXEmS7D))h1R3W)KahriZQpYGCkm L^>bP0l+XkKAR%?3 literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/trigger-bad.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/trigger-bad.png new file mode 100644 index 0000000000000000000000000000000000000000..6cc2fe96c8f234a73672cc73fd272a15fc7de4f9 GIT binary patch literal 610 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMf&j6ngS0LTG@1*|3#U-B}o%;Xd z@BjbbfBg9M>-UfEKmPptck0#i>q$vR85k~vM;h66vGt;?!=F#Zb zZ{_7j+`X5s+_2Bq>0@|!L|y;4va)YQMPKvs_EKeX=}P-AfLdmo>7uC8zF>|a}0_%=`3<>K}sDCoVn_gfd&*XHK03=LmuYd7cA zdvL>2>S4={E+nQaGTEbw%343W5;d-f{dp#&b*2lcZ1ns=Wr`2LXZ@Y}!gZH&=I zLRA-+#NU!T_D{s!WsY3O^U7b0%|TU)i%;fkeASY}^oDQwY2NeVd+dG)aM5p$oE-1tl3!?oFZs`=O(lN+cwV$Ps+oeiATx_obU^~(ExO-YKdz^NlIc#s#S7P zDv)9@GB7mMH89pSum~|UvNANcGO*M(Ftai+xcqEI5{ic0{FKbJO57S2?H0HP)WG2B L>gTe~DWM4fI7SDA literal 0 HcmV?d00001 diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/trigger.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/static/img/trigger.png new file mode 100644 index 0000000000000000000000000000000000000000..3b413e4a648c999c0dcc005aaaf02a710be4414b GIT binary patch literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPHF3h)VW1=6kiPU=rwT=MDBssBIz z{{Mgc|Bu?=uTvji_Bye{WYZ$U_4Az$Y>2*ja-Gx8WkA)8B|(0{3=Yq3qyagc1s;*b zK-vS0-A-oP0U3dwE{-7_*OL<(nB7!eIhs%2;5b!K7}#Lo*a4r5?=RWIaqXXEsv zK*Lo_Tq8= 1 ) { + return false; + } else { + return true; + } + } + } + return true; + } + }), + // Below function will enable right click menu for creating column + canCreate: function(itemData, item, data) { + // If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var t = pgBrowser.tree, i = item, d = itemData, parents = []; + // To iterate over tree to check parent node + while (i) { + // If it is schema then allow user to c reate table + if (_.indexOf(['schema'], d._type) > -1) + return true; + parents.push(d._type); + i = t.hasParent(i) ? t.parent(i) : null; + d = i ? t.itemData(i) : null; + } + // If node is under catalog then do not allow 'create' menu + if (_.indexOf(parents, 'catalog') > -1) { + return false; + } else { + return true; + } + }, + // Check to whether trigger is disable ? + canCreate_with_trigger_enable: function(itemData, item, data) { + if(this.canCreate.apply(this, [itemData, item, data])) { + // We are here means we can create menu, now let's check condition + if(itemData.icon === 'icon-trigger-bad') { + return true; + } else { + return false; + } + } + }, + // Check to whether trigger is enable ? + canCreate_with_trigger_disable: function(itemData, item, data) { + if(this.canCreate.apply(this, [itemData, item, data])) { + // We are here means we can create menu, now let's check condition + if(itemData.icon === 'icon-trigger') { + return true; + } else { + return false; + } + } + } + }); + } + + return pgBrowser.Nodes['trigger']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/datatype/sql/9.1_plus/get_types.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/datatype/sql/9.1_plus/get_types.sql index b8952d269..f465a46f2 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/datatype/sql/9.1_plus/get_types.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/datatype/sql/9.1_plus/get_types.sql @@ -5,7 +5,8 @@ FROM 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 + (SELECT COUNT(1) FROM pg_type t2 WHERE t2.typname = t.typname) > 1 AS isdup, + CASE WHEN t.typcollation != 0 THEN TRUE ELSE FALSE END AS is_collatable FROM pg_type t JOIN diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/macros/schemas/privilege.macros b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/macros/schemas/privilege.macros index 183ec2f1e..dc18a3119 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/macros/schemas/privilege.macros +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/macros/schemas/privilege.macros @@ -6,6 +6,10 @@ GRANT {{ privs|join(', ') }} ON {{ type }} {{ conn|qtIdent(schema, param) }} TO {{ conn|qtIdent(role) }}; {% endif %} {% if with_grant_privs %} +{% if privs %} +{# This empty if is to add new line in between #} + +{% endif %} GRANT {{ with_grant_privs|join(', ') }} ON {{ type }} {{ conn|qtIdent(schema, param) }} TO {{ conn|qtIdent(role) }} WITH GRANT OPTION; {% endif %} {%- endmacro %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schema/js/schema.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schema/js/schema.js index dc393d540..210f3e68d 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schema/js/schema.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/schema/js/schema.js @@ -4,6 +4,256 @@ define( 'pgadmin.browser.collection', 'pgadmin.browser.server.privilege'], function($, _, S, pgAdmin, pgBrowser, Backform, alertify) { + + + // VaccumSettings Collection to display all settings parameters as Grid + var VacuumCollectionControl = Backform.VacuumCollectionControl = + Backform.Control.extend({ + + grid_columns:undefined, + + initialize: function() { + Backform.Control.prototype.initialize.apply(this, arguments); + var self = this, + m = this.model; + url = self.field.get('url'); + + if (url && m.isNew()) { + var node = self.field.get('node'), + node_data = self.field.get('node_data'), + node_info = self.field.get('node_info'), + full_url = node.generate_url.apply( + node, [ + null, url, node_data, false, node_info + ]), + data; + m.trigger('pgadmin-view:fetching', m, self.field); + + // fetch default values for autovacuum fields + $.ajax({ + async: false, + url: full_url, + success: function (res) { + data = res; + }, + error: function() { + m.trigger('pgadmin-view:fetch:error', m, self.field); + } + }); + m.trigger('pgadmin-view:fetched', m, self.field); + + // Add fetched models into collection + if (data && _.isArray(data)) { + m.get(self.field.get('name')).reset(data, {silent: true}); + } + } + }, + + render: function() { + var self = this, + m = this.model, + attributes = self.field.attributes; + + // remove grid + if(self.grid) { + self.grid.remove(); + delete self.grid; + self.grid = undefined; + } + + self.$el.empty(); + + var gridHeader = _.template([ + '
', + ' ', + '
'].join("\n")), + gridBody = $('
').append( + gridHeader(attributes) + ); + + // Initialize a new Grid instance + var grid = self.grid = new Backgrid.Grid({ + columns: self.grid_columns, + collection: self.model.get(self.field.get('name')), + className: "backgrid table-bordered" + }); + + // render grid + self.$el.append($(gridBody).append(grid.render().$el)); + + return self; + } + }); + + // We will use this function in VacuumSettings Control + // to convert data type on the fly + var cellFunction = Backform.cellFunction = function(model) { + var self = this, + m = model, + vartype = model.get('column_type'); + + switch(vartype) { + case "integer": + return Backgrid.IntegerCell; + break; + case "number": + return Backgrid.NumberCell; + break; + case "string": + return Backgrid.StringCell; + break; + default: + return Backgrid.Cell; + break; + } + }; + + // Define Security Model with fields and validation for VacuumSettings Control + var VacuumTableModel = Backform.VacuumTableModel = pgAdmin.Browser.Node.Model.extend({ + defaults: { + name: undefined, + setting: undefined, + label:undefined, + value: undefined, + column_type: undefined + }, + + toJSON: function(){ + var d = pgAdmin.Browser.Node.Model.prototype.toJSON.apply(this); + delete d.label; + delete d.setting; + delete d.column_type; + return d; + } + }); + + // Extend the browser's collection class for VacuumSettingsModel + var VacuumSettingsSchema = Backform.VacuumSettingsSchema = + [{ + id: 'autovacuum_custom', label: '{{ _("Custom auto-vacuum?") }}', + group: '{{ _("Table") }}', mode: ['edit', 'create'], + type: 'switch', + disabled: function(m) { + if(!m.top.inSchema.apply(this, [m])) { + return false; + } + return true; + } + },{ + id: 'autovacuum_enabled', label: '{{ _("Enabled?") }}', + group: '{{ _("Table") }}', mode: ['edit', 'create'], + type: 'switch', + deps: ['autovacuum_custom'], + disabled: function(m) { + if(!m.top.inSchema.apply(this, [m]) && + m.get('autovacuum_custom') == true) { + return false; + } + + // We also need to unset rest of all + setTimeout(function() { + m.set('autovacuum_enabled', false); + }, 10); + return true; + } + },{ + id: 'vacuum_table', label: '{{ _("Vacuum Table") }}', + model: Backform.VacuumTableModel, editable: false, type: 'collection', + canEdit: true, group: '{{ _("Table") }}', + mode: ['edit', 'create'], url: 'get_table_vacuum', + control: Backform.VacuumCollectionControl.extend({ + grid_columns :[ + { + name: 'label', label: '{{ _("Label") }}', + headerCell: Backgrid.Extension.CustomHeaderCell, + cell: 'string', editable: false, cellHeaderClasses:'width_percent_40' + }, + { + name: 'value', label: '{{ _("Value") }}', + cellHeaderClasses:'width_percent_30', + cellFunction: Backform.cellFunction, editable: function(m) { + return m.handler.get('autovacuum_enabled'); + }, headerCell: Backgrid.Extension.CustomHeaderCell + }, + { + name: 'setting', label: '{{ _("Default value") }}', + cellHeaderClasses:'width_percent_30', + headerCell: Backgrid.Extension.CustomHeaderCell, + cellFunction: Backform.cellFunction, editable: false + } + ] + }), + deps: ['autovacuum_enabled'] + },{ + id: 'toast_autovacuum', label: '{{ _("Custom auto-vaccum?") }}', + group: '{{ _("Toast Table") }}', mode: ['edit', 'create'], + type: 'switch', + disabled: function(m) { + // We need to check additional condition to toggle enable/disable + // for table auto-vacuum + if(!m.top.inSchema.apply(this, [m]) && m.isNew()) { + return false; + } else if(!m.top.inSchema.apply(this, [m]) && + (m.get('toast_autovacuum_enabled') === true || + m.top.get('hastoasttable') === true)) { + return false; + } + return true; + } + },{ + id: 'toast_autovacuum_enabled', label: '{{ _("Enabled?") }}', + group: '{{ _("Toast Table") }}', mode: ['edit', 'create'], + type: 'switch', + deps:['toast_autovacuum'], + disabled: function(m) { + // If in schema & in create mode then enable it + if(!m.top.inSchema.apply(this, [m]) && + m.get('toast_autovacuum') === true) { + return false; + } + + if (m.isNew() || m.get('hastoasttable')) { + // we also need to unset rest of all + setTimeout(function() { + m.set('toast_autovacuum_enabled', false); + }, 10); + } + return true; + } + },{ + id: 'vacuum_toast', label: '{{ _("Vacuum Toast Table") }}', + model: Backform.VacuumTableModel, type: 'collection', editable: function(m) { + return m.isNew(); + }, + canEdit: true, group: '{{ _("Toast Table") }}', + mode: ['properties', 'edit', 'create'], url: 'get_toast_table_vacuum', + control: Backform.VacuumCollectionControl.extend({ + grid_columns :[ + { + name: 'label', label: '{{ _("Label") }}', + headerCell: Backgrid.Extension.CustomHeaderCell, + cell: 'string', editable: false, cellHeaderClasses:'width_percent_40' + }, + { + name: 'value', label: '{{ _("Value") }}', + cellHeaderClasses:'width_percent_30', + headerCell: Backgrid.Extension.CustomHeaderCell, + cellFunction: Backform.cellFunction, editable: function(m) { + return m.handler.get('toast_autovacuum_enabled'); + } + }, + { + name: 'setting', label: '{{ _("Default value") }}', + cellHeaderClasses:'width_percent_30', + headerCell: Backgrid.Extension.CustomHeaderCell, + cellFunction: Backform.cellFunction, editable: false + } + ] + }), + deps: ['toast_autovacuum_enabled'] + } + ]; + // Extend the browser's collection class for SecurityLabel control var SecurityModel = Backform.SecurityModel = pgAdmin.Browser.Node.Model.extend({ defaults: { diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/vacuum_settings/sql/vacuum_defaults.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/vacuum_settings/sql/vacuum_defaults.sql new file mode 100644 index 000000000..e60598cdb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/vacuum_settings/sql/vacuum_defaults.sql @@ -0,0 +1,2 @@ +{# ============= Fetch list of default values for autovacuum parameters =============== #} +SELECT name, setting::numeric AS setting FROM pg_settings WHERE name IN({{ columns }}) ORDER BY name diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/vacuum_settings/vacuum_fields.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/vacuum_settings/vacuum_fields.json new file mode 100644 index 000000000..83571f4b1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/templates/vacuum_settings/vacuum_fields.json @@ -0,0 +1,39 @@ +{# ===== Define name, label and column type of vacuum settings ===== #} +{# Usage: + + 'Type": + { + "key": ["label", "column_type" + } + + Where + - "Type" either table/toast + - "key" refers to the name of columns we fetch in properties.sql + - "label" It is the name of properties to display in grid. + - "column_type" Type of column, we have to provide the grid what type of value to rendered +#} +{# ===== Define name, label and column type of vacuum settings ===== #} +{ + "table": + { + "autovacuum_vacuum_threshold": ["autovacuum_vacuum_threshold", "VACCUM base threshold", "integer"], + "autovacuum_analyze_threshold": ["autovacuum_analyze_threshold", "ANALYZE base threshold", "integer"], + "autovacuum_vacuum_scale_factor": ["autovacuum_vacuum_scale_factor", "VACCUM scale factor", "number"], + "autovacuum_analyze_scale_factor": ["autovacuum_analyze_scale_factor", "ANALYZE scale factor", "number"], + "autovacuum_vacuum_cost_delay": ["autovacuum_vacuum_cost_delay", "VACCUM cost delay", "integer"], + "autovacuum_vacuum_cost_limit": ["autovacuum_vacuum_cost_limit", "VACCUM cost limit", "integer"], + "autovacuum_freeze_max_age": ["autovacuum_freeze_max_age", "FREEZE maximum age", "integer"], + "vacuum_freeze_min_age": ["autovacuum_freeze_min_age", "FREEZE minimum age", "integer"], + "vacuum_freeze_table_age": ["autovacuum_freeze_table_age", "FREEZE table age", "integer"] + }, + "toast": + { + "autovacuum_vacuum_threshold": ["autovacuum_vacuum_threshold", "VACCUM base threshold", "integer"], + "autovacuum_vacuum_scale_factor": ["autovacuum_vacuum_scale_factor", "VACCUM scale factor", "number"], + "autovacuum_vacuum_cost_delay": ["autovacuum_vacuum_cost_delay", "VACCUM cost delay", "integer"], + "autovacuum_vacuum_cost_limit": ["autovacuum_vacuum_cost_limit", "VACCUM cost limit", "integer"], + "autovacuum_freeze_max_age": ["autovacuum_freeze_max_age", "FREEZE maximum age", "integer"], + "vacuum_freeze_min_age": ["autovacuum_freeze_min_age", "FREEZE minimum age", "integer"], + "vacuum_freeze_table_age": ["autovacuum_freeze_table_age", "FREEZE table age", "integer"] + } +} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py index 29c79367d..17a35b67b 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py @@ -134,7 +134,8 @@ class DataTypeReader: res.append({ 'label': row['typname'], 'value': row['typname'], 'typval': typeval, 'precision': precision, - 'length': length, 'min_val': min_val, 'max_val': max_val + 'length': length, 'min_val': min_val, 'max_val': max_val, + 'is_collatable': row['is_collatable'] }) except Exception as e: @@ -338,4 +339,156 @@ def parse_rule_definition(res): res_data['condition'] = condition except Exception as e: return internal_server_error(errormsg=str(e)) - return res_data \ No newline at end of file + return res_data + + +class VacuumSettings: + """ + VacuumSettings Class. + + This class includes common utilities to fetch and parse + vacuum defaults settings. + + Methods: + ------- + * get_vacuum_table_settings(conn): + - Returns vacuum table defaults settings. + + * get_vacuum_toast_settings(conn): + - Returns vacuum toast defaults settings. + + * parse_vacuum_data(conn, result, type): + - Returns result of an associated array + of fields name, label, value and column_type. + It adds name, label, column_type properties of table/toast + vacuum into the array and returns it. + args: + * conn - It is db connection object + * result - Resultset of vacuum data + * type - table/toast vacuum type + + """ + def __init__(self): + pass + + def get_vacuum_table_settings(self, conn): + """ + Fetch the default values for autovacuum + fields, return an array of + - label + - name + - setting + values + """ + + # returns an array of name & label values + vacuum_fields = render_template("vacuum_settings/vacuum_fields.json") + + vacuum_fields = json.loads(vacuum_fields) + + # returns an array of setting & name values + vacuum_fields_keys = "'"+"','".join( + vacuum_fields['table'].keys())+"'" + SQL = render_template('vacuum_settings/sql/vacuum_defaults.sql', + columns=vacuum_fields_keys) + status, res = conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + row_name = row['name'] + row['name'] = vacuum_fields['table'][row_name][0] + row['label'] = vacuum_fields['table'][row_name][1] + row['column_type'] = vacuum_fields['table'][row_name][2] + + return res + + def get_vacuum_toast_settings(self, conn): + """ + Fetch the default values for autovacuum + fields, return an array of + - label + - name + - setting + values + """ + + # returns an array of name & label values + vacuum_fields = render_template("vacuum_settings/vacuum_fields.json") + + vacuum_fields = json.loads(vacuum_fields) + + # returns an array of setting & name values + vacuum_fields_keys = "'"+"','".join( + vacuum_fields['toast'].keys())+"'" + SQL = render_template('vacuum_settings/sql/vacuum_defaults.sql', + columns=vacuum_fields_keys) + status, res = conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + for row in res['rows']: + row_name = row['name'] + row['name'] = vacuum_fields['toast'][row_name][0] + row['label'] = vacuum_fields['toast'][row_name][1] + row['column_type'] = vacuum_fields['table'][row_name][2] + + return res + + def parse_vacuum_data(self, conn, result, type): + """ + This function returns result of an associated array + of fields name, label, value and column_type. + It adds name, label, column_type properties of table/toast + vacuum into the array and returns it. + args: + * conn - It is db connection object + * result - Resultset of vacuum data + * type - table/toast vacuum type + """ + + # returns an array of name & label values + vacuum_fields = render_template("vacuum_settings/vacuum_fields.json") + + vacuum_fields = json.loads(vacuum_fields) + + # returns an array of setting & name values + vacuum_fields_keys = "'"+"','".join( + vacuum_fields[type].keys()) + "'" + SQL = render_template('vacuum_settings/sql/vacuum_defaults.sql', + columns=vacuum_fields_keys) + status, res = conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + if type is 'table': + for row in res['rows']: + row_name = row['name'] + row['name'] = vacuum_fields[type][row_name][0] + row['label'] = vacuum_fields[type][row_name][1] + row['column_type'] = vacuum_fields[type][row_name][2] + if result[row['name']] is not None: + if row['column_type'] == 'number': + value = float(result[row['name']]) + else: + value = int(result[row['name']]) + row['value'] = row['setting'] = value + + elif type is 'toast': + for row in res['rows']: + row_old_name = row['name'] + row_name = 'toast_{0}'.format(vacuum_fields[type][row_old_name][0]) + row['name'] = vacuum_fields[type][row_old_name][0] + row['label'] = vacuum_fields[type][row_old_name][1] + row['column_type'] = vacuum_fields[type][row_old_name][2] + if result[row_name] and result[row_name] is not None: + if row['column_type'] == 'number': + value = float(result[row_name]) + else: + value = int(result[row_name]) + row['value'] = row['setting'] = value + + return res['rows'] \ No newline at end of file