From 8b7eeca545249fd57f888de4a1c87ce3e634d907 Mon Sep 17 00:00:00 2001 From: Anil Sahoo <122535205+anilsahoo20@users.noreply.github.com> Date: Thu, 25 May 2023 16:25:12 +0530 Subject: [PATCH] Implement new PostgreSQL 15 features in publication dialog and SQL. #5868 --- docs/en_US/images/publication_options.png | Bin 0 -> 23400 bytes docs/en_US/images/publication_tables.png | Bin 0 -> 39368 bytes docs/en_US/publication_dialog.rst | 32 +- .../databases/publications/__init__.py | 262 ++++++- .../publications/static/js/publication.js | 11 +- .../publications/static/js/publication.ui.js | 231 +++++- .../publications/sql/15_plus/create.sql | 26 + .../sql/15_plus/get_all_schemas.sql | 5 + .../sql/15_plus/get_pub_schemas.sql | 4 + .../publications/sql/15_plus/get_tables.sql | 6 + .../publications/sql/15_plus/update.sql | 67 ++ .../sql/default/get_all_columns.sql | 7 + .../sql/default/get_all_tables.sql | 22 +- .../publications/sql/default/get_tables.sql | 10 +- .../13_plus/alter_publication_add_tables.sql | 7 + .../alter_publication_add_tables_msql.sql | 2 + .../13_plus/alter_publication_drop_tables.sql | 7 + .../alter_publication_drop_tables_msql.sql | 2 + .../13_plus/alter_publication_event_msql.sql | 4 +- .../tests/13_plus/alter_publication_msql.sql | 3 +- .../13_plus/create_publication_few_tables.sql | 7 + .../create_publication_few_tables_msql.sql | 3 + .../publications/tests/13_plus/test.json | 89 ++- .../tests/15_plus/alter_publication.sql | 7 + .../15_plus/alter_publication_add_schemas.sql | 7 + .../alter_publication_add_schemas_msql.sql | 2 + .../15_plus/alter_publication_add_tables.sql | 7 + .../alter_publication_add_tables_columns.sql | 7 + ...er_publication_add_tables_columns_msql.sql | 2 + ...r_publication_add_tables_columns_where.sql | 7 + ...lication_add_tables_columns_where_msql.sql | 2 + .../alter_publication_add_tables_msql.sql | 2 + .../alter_publication_add_tables_where.sql | 7 + ...lter_publication_add_tables_where_msql.sql | 2 + .../alter_publication_drop_schemas.sql | 7 + .../alter_publication_drop_schemas_msql.sql | 2 + .../15_plus/alter_publication_drop_tables.sql | 7 + .../alter_publication_drop_tables_msql.sql | 2 + .../tests/15_plus/alter_publication_event.sql | 7 + .../15_plus/alter_publication_event_msql.sql | 2 + .../tests/15_plus/alter_publication_msql.sql | 2 + .../alter_publication_set_tables_columns.sql | 7 + ...er_publication_set_tables_columns_msql.sql | 2 + ...r_publication_set_tables_columns_where.sql | 7 + ...lication_set_tables_columns_where_msql.sql | 2 + .../alter_publication_set_tables_where.sql | 7 + ...lter_publication_set_tables_where_msql.sql | 2 + .../tests/15_plus/create_publication.sql | 7 + .../create_publication_few_schemas.sql | 7 + .../create_publication_few_schemas_msql.sql | 3 + .../15_plus/create_publication_few_tables.sql | 7 + .../create_publication_few_tables_columns.sql | 7 + ...te_publication_few_tables_columns_msql.sql | 3 + ...e_publication_few_tables_columns_where.sql | 7 + ...lication_few_tables_columns_where_msql.sql | 3 + .../create_publication_few_tables_msql.sql | 3 + .../create_publication_few_tables_only.sql | 7 + ...reate_publication_few_tables_only_msql.sql | 3 + .../create_publication_few_tables_schemas.sql | 7 + ...te_publication_few_tables_schemas_msql.sql | 3 + .../create_publication_few_tables_where.sql | 7 + ...eate_publication_few_tables_where_msql.sql | 3 + ...e_publication_few_tables_where_schemas.sql | 7 + ...lication_few_tables_where_schemas_msql.sql | 3 + .../tests/15_plus/create_publication_msql.sql | 3 + .../publications/tests/15_plus/test.json | 582 +++++++++++++++ .../default/alter_publication_add_tables.sql | 7 + .../alter_publication_add_tables_msql.sql | 2 + .../default/alter_publication_drop_tables.sql | 7 + .../alter_publication_drop_tables_msql.sql | 2 + .../default/alter_publication_event_msql.sql | 4 +- .../tests/default/alter_publication_msql.sql | 3 +- .../default/create_publication_few_tables.sql | 7 + .../create_publication_few_tables_msql.sql | 3 + .../publications/tests/default/test.json | 88 ++- .../tests/publication_test_data.json | 701 +++++++++++++++++- .../tests/test_publication_create.py | 10 +- .../tests/test_publication_delete.py | 4 +- .../tests/test_publication_delete_multiple.py | 5 +- .../tests/test_publication_get.py | 4 +- .../tests/test_publication_put.py | 45 +- .../tests/test_publication_put_add_schema.py | 89 +++ .../tests/test_publication_put_add_table.py | 116 +++ .../tests/test_publication_put_drop_schema.py | 100 +++ .../tests/test_publication_put_drop_table.py | 121 +++ .../test_publication_put_update_schema.py | 101 +++ .../test_publication_put_update_table.py | 184 +++++ .../tests/test_publication_sql.py | 3 +- .../databases/publications/tests/utils.py | 102 ++- .../static/js/components/QueryThresholds.jsx | 2 +- .../schema_ui_files/publication.ui.spec.js | 10 +- 91 files changed, 3105 insertions(+), 202 deletions(-) create mode 100644 docs/en_US/images/publication_options.png create mode 100644 docs/en_US/images/publication_tables.png create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_all_schemas.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_pub_schemas.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_columns.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_add_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_add_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_drop_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_drop_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/create_publication_few_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/create_publication_few_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_schemas.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_schemas_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_where.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_where_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_where.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_where_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_schemas.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_schemas_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_event.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_event_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_where.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_where_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_where.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_where_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_schemas.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_schemas_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_where.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_where_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_only.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_only_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_schemas.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_schemas_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_schemas.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_schemas_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/test.json create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_add_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_add_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_drop_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_drop_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/create_publication_few_tables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/create_publication_few_tables_msql.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_add_schema.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_add_table.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_drop_schema.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_drop_table.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_update_schema.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_update_table.py diff --git a/docs/en_US/images/publication_options.png b/docs/en_US/images/publication_options.png new file mode 100644 index 0000000000000000000000000000000000000000..263b3255dadda92e31e5ad76a2ea1fcc5b18095a GIT binary patch literal 23400 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

1nIjt-g_7t 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 F{tq~``1}9> literal 0 HcmV?d00001 diff --git a/docs/en_US/images/publication_tables.png b/docs/en_US/images/publication_tables.png new file mode 100644 index 0000000000000000000000000000000000000000..a5d29cfd0bc7999e4b6a143cc4c430f4eabed877 GIT binary patch literal 39368 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 U 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 literal 0 HcmV?d00001 diff --git a/docs/en_US/publication_dialog.rst b/docs/en_US/publication_dialog.rst index cb0dff1b3..bfafa3e9a 100644 --- a/docs/en_US/publication_dialog.rst +++ b/docs/en_US/publication_dialog.rst @@ -6,9 +6,9 @@ `Logical replication `_ uses a *publish* and *subscribe* model with one or more *subscribers* subscribing to one or more *publications* on a publisher node. -Use the *publication* dialog to create a publication. A publication is a set of changes generated from a table or a group of tables, and might also be described as a change set or replication set. +Use the *publication* dialog to create a publication. A publication is a set of changes generated from a table or a group of tables or a schema or a group of schemas, and might also be described as a change set or replication set. -The *publication* dialog organizes the development of a publication through the following dialog tabs: *General* and *Definition*. The *SQL* tab displays the SQL code generated by dialog selections. +The *publication* dialog organizes the development of a publication through the following dialog tabs: *General* and *Definition*. The *SQL* tab displays the SQL code generated by dialog selections. In place of *Definition* tab *Tables* and *Options* tab will come for PostgreSQL version >= 15. .. image:: images/publication_general.png :alt: Publication dialog general tab @@ -32,6 +32,32 @@ Use the *Definition* tab to set properties for the publication: * Specify a table or list of tables separated by a comma in *Tables* field to replicate all the listed table. * Use the *With* section to determine which DML operations will be published by the new publication to the subscribers. Move the switch next to *INSERT*, *UPDATE*, *DELETE*, or *TRUNCATE* to *No* if you do not want to replicate any of these DML operations from Publication to Subscription. By default, all the switches are set to *Yes* allowing all the DML operations. +With PostgreSQL 15 forward, the *Tables* and *Options* tab will be visible. + +Click the *Tables* tab to continue. + +.. image:: images/publication_tables.png + :alt: Publication dialog tables tab + :align: center + +Use the *Tables* tab to set table properties for the publication: + +* Move the switch next to *All tables?* to *Yes* to replicate all the tables of the database, including tables created in the future. +* Move the switch next to *Only table?* to *Yes* to replicate only the listed tables excluding all its descendant tables. +* Specify a schema or list of schemas separated by a comma in *Tables In Schema* field to replicate all the listed schemas. This field will be disabled if any columns are selected for tables in subsequent field. +* Specify a table or list of tables to replicate all the listed table along with specific columns and/or WHERE clause to filter rows. + +Click the *Options* tab to continue. + +.. image:: images/publication_options.png + :alt: Publication dialog options tab + :align: center + +Use the *Options* tab to set option properties for the publication: + +* Use the *With* section to determine which DML operations will be published by the new publication to the subscribers. Move the switch next to *INSERT*, *UPDATE*, *DELETE*, or *TRUNCATE* to *No* if you do not want to replicate any of these DML operations from Publication to Subscription. By default, all the switches are set to *Yes* allowing all the DML operations. + + .. note:: A published table must have a “replica identity” configured in order to be able to replicate UPDATE and DELETE operations. You can change with ALTER TABLE statement. For more information on replica identity see `Logical Replication Publication `_. Click the *SQL* tab to continue. @@ -44,7 +70,7 @@ The following is an example of the sql command generated by user selections in the *Publication* dialog: .. image:: images/publication_sql.png - :alt: Publication dialog sql tab + :alt: Publication dialog sql tab for PostgreSQL version <= 14 :align: center The example creates a publication named *pub1* that is owned by *postgres*. It diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/publications/__init__.py index d10c995db..0b323ccfe 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/__init__.py @@ -12,7 +12,7 @@ import json from functools import wraps from pgadmin.browser.server_groups.servers import databases -from flask import render_template, request, jsonify +from flask import render_template, request, jsonify, current_app as app from flask_babel import gettext from pgadmin.browser.collection import CollectionNodeModule from pgadmin.browser.utils import PGChildNodeView @@ -21,8 +21,6 @@ from pgadmin.utils.ajax import make_json_response, internal_server_error, \ from pgadmin.utils.driver import get_driver from config import PG_DEFAULT_DRIVER from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare -from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry -from urllib.parse import unquote class PublicationModule(CollectionNodeModule): @@ -192,7 +190,9 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): 'stats': [{'get': 'statistics'}], 'dependency': [{'get': 'dependencies'}], 'dependent': [{'get': 'dependents'}], + 'get_schemas': [{}, {'get': 'get_schemas'}], 'get_tables': [{}, {'get': 'get_tables'}], + 'get_all_columns': [{}, {'get': 'get_all_columns'}], 'delete': [{'delete': 'delete'}, {'delete': 'delete'}] }) @@ -253,22 +253,16 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): return internal_server_error(errormsg=res) for rows in res['rows']: if not rows['all_table']: - get_name_sql = render_template( - "/".join([self.template_path, self._DELETE_SQL]), - pbid=rows['oid'], conn=self.conn - ) - status, pname = self.conn.execute_scalar(get_name_sql) table_sql = render_template( "/".join([self.template_path, self._GET_TABLE_FOR_PUBLICATION]), - pname=pname + pbid=rows['oid'] ) - pub_table = [] status, table_res = self.conn.execute_dict(table_sql) - for table in table_res['rows']: - pub_table.append(table['pubtable']) + pub_table = \ + [table['table_name'] for table in table_res['rows']] pub_table = ", ".join(str(elem) for elem in pub_table) @@ -385,24 +379,42 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): return False, gone(self._NOT_FOUND_PUB_INFORMATION) if not res['rows'][0]['all_table']: - get_name_sql = render_template( - "/".join([self.template_path, self._DELETE_SQL]), - pbid=pbid, conn=self.conn - ) - status, pname = self.conn.execute_scalar(get_name_sql) + if self.manager.version >= 150000: + schema_name_sql = render_template( + "/".join([self.template_path, 'get_pub_schemas.sql']), + pbid=pbid + ) + status, snames_list_res = self.conn.execute_dict( + schema_name_sql) + + if len(snames_list_res['rows']) != 0: + res['rows'][0]['pubschema'] = \ + [sname_dict['sname'] for sname_dict + in snames_list_res['rows']] + table_sql = render_template( "/".join([self.template_path, self._GET_TABLE_FOR_PUBLICATION]), - pname=pname + pbid=pbid ) pub_table = [] + pub_table_names_list = [] status, table_res = self.conn.execute_dict(table_sql) for table in table_res['rows']: - pub_table.append(table['pubtable']) + pub_table_names_list.append(table['table_name']) + if 'columns' in table and 'where' in table: + pub_table.append({ + 'table_name': table['table_name'], + 'columns': table['columns'], + 'where': table['where'], + }) + else: + pub_table.append(table['table_name']) res['rows'][0]['pubtable'] = pub_table + res['rows'][0]['pubtable_names'] = pub_table_names_list return True, res['rows'][0] @@ -630,21 +642,74 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): """ drop_table_data = [] add_table_data = [] + update_table_data = [] drop_table = False add_table = False + update_table = False - for table in old_data['pubtable']: - if 'pubtable' in data and table not in data['pubtable']: - drop_table_data.append(table) - drop_table = True + if self.manager.version < 150000: + for table in old_data['pubtable']: + if 'pubtable' in data and table not in data['pubtable']: + drop_table_data.append(table) + drop_table = True - if 'pubtable' in data: - for table in data['pubtable']: - if table not in old_data['pubtable']: + if 'pubtable' in data: + for table in data['pubtable']: + if table not in old_data['pubtable']: + add_table_data.append(table) + add_table = True + elif self.manager.version >= 150000: + if 'pubtable' in data and 'deleted' in data['pubtable']: + for table in data['pubtable']['deleted']: + drop_table_data.append(table) + drop_table = True + + if 'pubtable' in data and 'changed' in data['pubtable']: + update_table_data = [*old_data['pubtable']] + for index, table1 in enumerate(old_data['pubtable']): + for table2 in data['pubtable']['changed']: + if table1['table_name'] == table2['table_name']: + update_table_data[index] = table2 + update_table = True + break + + if 'pubtable' in data and 'added' in data['pubtable']: + for table in data['pubtable']['added']: add_table_data.append(table) add_table = True - return drop_table, add_table, drop_table_data, add_table_data + return drop_table, add_table, update_table, drop_table_data, \ + add_table_data, update_table_data + + def _get_schema_details_to_add_and_delete(self, old_data, data): + """ + This function returns the schemas which need to add and delete + :param old_data: + :param data: + :return: + """ + drop_schema_data = [] + add_schema_data = [] + drop_schema = False + add_schema = False + + if 'pubschema' in old_data: + for schema in old_data['pubschema']: + if 'pubschema' in data and schema not in data['pubschema']: + drop_schema_data.append(schema) + drop_schema = True + + if 'pubschema' in data: + for schema in data['pubschema']: + if 'pubschema' in old_data and \ + schema not in old_data['pubschema']: + add_schema_data.append(schema) + add_schema = True + elif 'pubschema' not in old_data: + add_schema_data.append(schema) + add_schema = True + + return drop_schema, add_schema, drop_schema_data, add_schema_data def get_sql(self, data, pbid=None): """ @@ -668,11 +733,33 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): if len(res['rows']) == 0: return gone(self._NOT_FOUND_PUB_INFORMATION) - old_data = self._get_old_table_data(res['rows'][0]['name'], res) + snames_list = [] - drop_table, add_table, drop_table_data, add_table_data = \ + if self.manager.version >= 150000: + schema_name_sql = render_template( + "/".join([self.template_path, 'get_pub_schemas.sql']), + pbid=pbid + ) + status, snames_list_res = self.conn.execute_dict( + schema_name_sql) + + if len(snames_list_res['rows']) != 0: + snames_list = [sname_dict['sname'] for sname_dict + in snames_list_res['rows']] + + old_data = self._get_old_table_data(res['rows'][0]['name'], res, + snames_list) + + if len(snames_list) != 0: + old_data['pubschema'] = snames_list + + drop_table, add_table, update_table, drop_table_data, \ + add_table_data, update_table_data = \ self._get_table_details_to_add_and_delete(old_data, data) + drop_schema, add_schema, drop_schema_data, add_schema_data = \ + self._get_schema_details_to_add_and_delete(old_data, data) + for arg in required_args: if arg not in data: data[arg] = old_data[arg] @@ -684,7 +771,10 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): "/".join([self.template_path, self._UPDATE_SQL]), data=data, o_data=old_data, conn=self.conn, drop_table=drop_table, drop_table_data=drop_table_data, - add_table=add_table, add_table_data=add_table_data + add_table=add_table, add_table_data=add_table_data, + update_table=update_table, update_table_data=update_table_data, + drop_schema=drop_schema, drop_schema_data=drop_schema_data, + add_schema=add_schema, add_schema_data=add_schema_data, ) return sql.strip('\n'), data['name'] if 'name' in data \ else old_data['name'] @@ -695,6 +785,41 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): data=data, conn=self.conn) return sql.strip('\n'), data['name'] + @check_precondition + def get_schemas(self, gid, sid, did): + """ + This function will return the list of schemas for the specified + server group id, server id and database id. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + """ + res = [] + + sql = render_template("/".join([self.template_path, + 'get_all_schemas.sql']), + show_sys_objects=self.blueprint. + show_system_objects, + server_type=self.manager.server_type + ) + status, rset = self.conn.execute_2darray(sql) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + { + 'label': row['nspname'], + 'value': row['nspname'], + } + ) + return make_json_response( + data=res, + status=200 + ) + @check_precondition def get_tables(self, gid, sid, did): """ @@ -715,12 +840,14 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): ) status, rset = self.conn.execute_2darray(sql) if not status: - return internal_server_error(errormsg=res) + return internal_server_error(errormsg=rset) + for row in rset['rows']: res.append( { 'label': row['table'], - 'value': row['table'] + 'value': row['table'], + 'tid': row['tid'] } ) return make_json_response( @@ -728,24 +855,64 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): status=200 ) - def _get_old_table_data(self, pname, res): + @check_precondition + def get_all_columns(self, gid, sid, did): + """ + This function returns the columns list. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + tid: Table ID + """ + data = request.args + + res = [] + + sql = render_template("/".join([self.template_path, + 'get_all_columns.sql']), + tid=data['tid'], conn=self.conn) + status, rset = self.conn.execute_2darray(sql) + if not status: + return internal_server_error(errormsg=rset) + for row in rset['rows']: + res.append( + { + 'label': row['column'], + 'value': row['column'], + } + ) + return make_json_response( + data=res, + status=200 + ) + + def _get_old_table_data(self, pname, res, exempt_schema_list=[]): """ This function return table details before update :param pname: :param res: :return:old_data """ - table_sql = render_template( - "/".join([self.template_path, self._GET_TABLE_FOR_PUBLICATION]), - pname=pname + "/".join([self.template_path, + self._GET_TABLE_FOR_PUBLICATION]), + pbid=res['rows'][0]['oid'] ) pub_table = [] status, table_res = self.conn.execute_dict(table_sql) for table in table_res['rows']: - pub_table.append(table['pubtable']) + if 'columns' in table and 'where' in table: + pub_table.append({ + 'table_name': table['table_name'], + 'columns': table['columns'], + 'where': table['where'], + }) + else: + pub_table.append(table['table_name']) res['rows'][0]['pubtable'] = pub_table @@ -781,6 +948,20 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): if len(res['rows']) == 0: return gone(self._NOT_FOUND_PUB_INFORMATION) + snames_list = [] + + if self.manager.version >= 150000: + schema_name_sql = render_template( + "/".join([self.template_path, 'get_pub_schemas.sql']), + pbid=pbid + ) + status, snames_list_res = self.conn.execute_dict( + schema_name_sql) + + if len(snames_list_res['rows']) != 0: + snames_list = [sname_dict['sname'] for sname_dict + in snames_list_res['rows']] + get_name_sql = render_template( "/".join([self.template_path, self._DELETE_SQL]), pbid=pbid, conn=self.conn @@ -788,7 +969,10 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): status, pname = self.conn.execute_scalar(get_name_sql) # Get old table details - old_data = self._get_old_table_data(pname, res) + old_data = self._get_old_table_data(pname, res, snames_list) + + if len(snames_list) != 0: + old_data['pubschema'] = snames_list sql = render_template("/".join([self.template_path, self._CREATE_SQL]), @@ -940,5 +1124,5 @@ class PublicationView(PGChildNodeView, SchemaDiffObjectCompare): return sql -SchemaDiffRegistry(blueprint.node_type, PublicationView, 'Database') +# SchemaDiffRegistry(blueprint.node_type, PublicationView, 'Database') PublicationView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.js b/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.js index 46a5c3979..e0f497479 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.js +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.js @@ -7,7 +7,7 @@ // ////////////////////////////////////////////////////////////// -import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../static/js/node_ajax'; +import { getNodeAjaxOptions,getNodeListByName } from '../../../../../../static/js/node_ajax'; import PublicationSchema from './publication.ui'; define('pgadmin.node.publication', [ @@ -23,7 +23,6 @@ define('pgadmin.node.publication', [ label: gettext('Publications'), type: 'coll-publication', columns: ['name', 'pubowner', 'pubtable', 'all_table'], - }); } @@ -40,6 +39,7 @@ define('pgadmin.node.publication', [ canDrop: true, canDropCascade: true, hasDepends: true, + width: pgBrowser.stdW.md + 'px', Init: function() { @@ -69,12 +69,13 @@ define('pgadmin.node.publication', [ data: {action: 'create'}, }]); }, - - + getSchema: function(treeNodeInfo, itemNodeData){ return new PublicationSchema( { - publicationTable: ()=>getNodeAjaxOptions('get_tables', this, treeNodeInfo, itemNodeData), + allTables: ()=>getNodeAjaxOptions('get_tables', this, treeNodeInfo, itemNodeData), + allSchemas:()=>getNodeAjaxOptions('get_schemas', this, treeNodeInfo, itemNodeData), + getColumns: (params)=>getNodeAjaxOptions('get_all_columns', this, treeNodeInfo, itemNodeData,{urlParams: params, useCache:false}), role:()=>getNodeListByName('role', treeNodeInfo, itemNodeData), },{ node_info: treeNodeInfo.server, diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.ui.js b/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.ui.js index 9410891b4..29fe4c9ca 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.ui.js @@ -10,7 +10,6 @@ import gettext from 'sources/gettext'; import BaseUISchema from 'sources/SchemaView/base_schema.ui'; import _ from 'lodash'; - export class DefaultWithSchema extends BaseUISchema { constructor(node_info) { super(); @@ -33,21 +32,126 @@ export class DefaultWithSchema extends BaseUISchema { },{ id: 'evnt_truncate', label: gettext('TRUNCATE'), type: 'switch', group: gettext('With'), - visible: function() { - return !_.isUndefined(this.node_info['node_info']) - && !_.isUndefined(this.node_info['node_info'].version) - && this.node_info['node_info'].version >= 110000; - }, - + min_version: 110000, },{ id: 'publish_via_partition_root', label: gettext('Publish via root?'), type: 'switch', group: gettext('With'), - visible: function() { - return !_.isUndefined(this.node_info['node_info']) - && !_.isUndefined(this.node_info['node_info'].version) - && this.node_info['node_info'].version >= 130000; + min_version: 130000, + }, + ]; + } +} + +export class PublicationTableSchema extends BaseUISchema { + constructor(allTables,getColumns) { + super({ + table_name: undefined, + where: undefined, + columns:undefined, + }); + this.allTables = allTables; + this.getColumns=getColumns; + this.allTablesOptions = []; + this.varTypes = {}; + this.allReadOnly = false; + } + + isConnected(state) { + return Boolean(state.connected); + } + + getPlaceHolderMsg(variable) { + let msg = ''; + if (variable?.min_server_version && variable?.max_server_version) { + msg = gettext('%s <= Supported version >= %s', variable?.max_server_version, variable?.min_server_version); + } else if (variable?.min_server_version) { + msg = gettext('Supported version >= %s', variable?.min_server_version); + } else if (variable?.max_server_version) { + msg = gettext('Supported version <= %s', variable?.max_server_version); + } + return msg; + } + + getTableOid(tabName) { + // Here we will fetch the table oid from table name + // iterate over list to find table oid + for(const t of this.allTablesOptions) { + if(t.label === tabName) { + return t.tid; + } + } + } + + isTableName(state){ + return Boolean(state.table_name); + } + + get baseFields() { + let obj = this; + return [ + { + id: 'table_name', + label: gettext('Table Name'), + type: 'select', + noEmpty: true, + disabled:function (state) { + return !obj.isNew(state); + }, + editable: function (state) { + return obj.isNew(state) || !obj.allReadOnly; + }, + cell: () => ({ + cell: 'select', + options: this.allTables, + optionsLoaded: (options) => { + obj.allTablesOptions=options; + }, + controlProps: { allowClear: false }, + }), }, - }]; + { + id: 'columns', + label: gettext('Columns'), + type: 'select', + deps:['table_name'], + disabled: (state) => !obj.isTableName(state), + depChange: (state) => { + if(!state.table_name) { + return { + columns: null, + }; + } + }, + editable: function (state) { + return obj.isNew(state) || !obj.allReadOnly; + }, + cell: (state) => { + let tid = obj.getTableOid(state.table_name); + return{ + cell: 'select', + options: (state.table_name && tid) ? ()=>obj.getColumns({tid: tid}) : [], + optionsReloadBasis: tid, + controlProps: { allowClear: true, multiple: true}, + }; + }, + }, + { + id: 'where', + label: gettext('Where'), + type: 'sql', + deps: ['table_name'], + disabled: (state) => !obj.isTableName(state), + editable: function (state) { + return obj.isNew(state) || !obj.allReadOnly; + }, + cell: () => ({ + cell: 'sql', + controlProps: { + lineWrapping: true, + } + }), + }, + ]; } } @@ -55,9 +159,11 @@ export default class PublicationSchema extends BaseUISchema { constructor(fieldOptions={}, node_info={}, initValues={}) { super({ name: undefined, - pubowner: (node_info) ? node_info['node_info'].user.name: undefined, - pubtable: undefined, - all_table: undefined, + pubowner: (node_info) ? node_info['node_info']?.user.name: undefined, + pubtable: [], + pubtable_names: [], + pubschema: undefined, + all_table: false, evnt_insert:true, evnt_delete:true, evnt_update:true, @@ -69,11 +175,16 @@ export default class PublicationSchema extends BaseUISchema { this.fieldOptions = { role: [], - publicationTable: [], + allTables: [], + allSchemas:[], ...fieldOptions, }; this.node_info = node_info; + this.paramSchema = new PublicationTableSchema(this.fieldOptions.allTables, this.fieldOptions.getColumns); + this.version=!_.isUndefined(this.node_info['node_info']) && !_.isUndefined(this.node_info['node_info'].version) && this.node_info['node_info'].version; + } + get idAttribute() { return 'oid'; } @@ -81,7 +192,9 @@ export default class PublicationSchema extends BaseUISchema { isAllTable(state) { let allTable = state.all_table; if(allTable){ - state.pubtable = ''; + state.pubtable = []; + state.pubtable_names = ''; + state.pubschema = undefined; return true; } return false; @@ -101,16 +214,40 @@ export default class PublicationSchema extends BaseUISchema { return true; } + isColumn(state){ + let table=state.pubtable, columnsList=[]; + if(!_.isUndefined(table) && table.length > 0){ + table?.forEach(i=>{ + if(i.columns!=undefined && i.columns.length!==0){ + columnsList.push(i.columns); + } + }); + if(columnsList?.length > 0){ + state.pubschema=undefined; + return true; + } + return false; + } + } + + isConnected(state) { + return Boolean(state.connected); + } + + getVersion(){ + return ( + !_.isUndefined(this.node_info['node_info']) && + !_.isUndefined(this.node_info['node_info'].version) && + this.node_info['node_info'].version + ); + } + get baseFields() { let obj = this; return [{ id: 'name', label: gettext('Name'), type: 'text', mode: ['properties', 'create', 'edit'], noEmpty: true, - visible: function() { - return !_.isUndefined(this.node_info['node_info']) - && !_.isUndefined(this.node_info['node_info'].version) - && this.node_info['node_info'].version >= 100000; - }, + min_version: 100000, },{ id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'], type: 'text', @@ -123,26 +260,56 @@ export default class PublicationSchema extends BaseUISchema { mode: ['edit', 'properties', 'create'], controlProps: { allowClear: false}, },{ id: 'all_table', label: gettext('All tables?'), type: 'switch', - group: gettext('Definition'), mode: ['edit', 'properties', 'create'], deps: ['name'], + group: this.version < 150000 ? gettext('Definition') : gettext('Tables'), mode: ['edit', 'properties', 'create'], deps: ['name'], readonly: (state) => {return !obj.isNew(state);}, },{ id: 'only_table', label: gettext('Only table?'), type: 'switch', - group: gettext('Definition'), mode: ['edit', 'create'], - deps: ['name', 'pubtable', 'all_table'], readonly: obj.isTable, + group: this.version < 150000 ? gettext('Definition') : gettext('Tables'), mode: ['edit', 'create'], + deps: ['name', 'pubtable', 'all_table'], readonly: (state) => { + if(obj.isNew(state)) + return obj.isTable(state); + else + return true; + }, helpMessageMode: ['edit', 'create'], helpMessage: gettext('If ONLY is specified before the table name, only that table is added to the publication. If ONLY is not specified, the table and all its descendant tables (if any) are added.'), },{ - id: 'pubtable', label: gettext('Tables'), type: 'select', + id: 'pubschema', label: gettext('Tables in Schema'), type: 'select', controlProps: { allowClear: true, multiple: true, creatable: true }, - options: this.fieldOptions.publicationTable, - group: gettext('Definition'), mode: ['edit', 'create', 'properties'], + options: this.fieldOptions.allSchemas, deps: ['all_table','pubtable'], + disabled: (state)=>{return obj.isColumn(state) || obj.isAllTable(state);}, + group: this.version < 150000 ? null : gettext('Tables'), mode: ['edit', 'create', 'properties'], + min_version: 150000, + }, + { + id: 'pubtable_names', label: gettext('Tables'), cell: 'string', + type: (state)=>{ + let table= (!_.isUndefined(state?.pubtable_names) && state?.pubtable_names.length > 0) && state?.pubtable_names; + return { + type: 'select', + options: table, + controlProps: { allowClear: true, multiple: true, creatable: true }, + }; + }, + group: this.version < 150000? gettext('Definition') : gettext('Tables'), mode: ['properties'], deps: ['all_table'], disabled: obj.isAllTable, - },{ + }, + { + id: 'pubtable', label: this.version < 150000 ? gettext('Tables') : gettext(''), + type: this.version < 150000 ? 'select' : 'collection', + controlProps: this.version < 150000 ? { allowClear: true, multiple: true, creatable: true } : null, + options: this.version < 150000 ? this.fieldOptions.allTables : null, + group: this.version < 150000 ? gettext('Definition') : gettext('Tables'), mode: ['edit', 'create'], + deps: ['all_table'], disabled: obj.isAllTable, schema: this.version < 150000 ? null : this.paramSchema, + uniqueCol: this.version < 150000 ? null : ['table_name'], + canAdd: this.version < 150000 ? null : (state)=> !obj.isConnected(state), + canDelete: this.version<150000?null : (state)=> !obj.isConnected(state), + }, + { type: 'nested-fieldset', mode: ['create','edit', 'properties'], - label: gettext('With'), group: gettext('Definition'), + label: gettext('With'), group: this.version < 150000 ? gettext('Definition') : gettext('Options'), schema : new DefaultWithSchema(this.node_info), }, ]; } -} - +} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/create.sql new file mode 100644 index 000000000..a85e735ee --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/create.sql @@ -0,0 +1,26 @@ +{% if data.evnt_delete or data.evnt_update or data.evnt_truncate %} +{% set add_comma_after_insert = 'insert' %} +{% endif %} +{% if data.evnt_truncate %} +{% set add_comma_after_delete = 'delete' %} +{% endif %} +{% if data.evnt_delete or data.evnt_truncate%} +{% set add_comma_after_update = 'update' %} +{% endif %} +{% if data.publish_via_partition_root%} +{% set add_comma_after_truncate = 'truncate' %} +{% endif %} +{### Create PUBLICATION ###} +CREATE PUBLICATION {{ conn|qtIdent(data.name) }} +{% if data.all_table %} + FOR ALL TABLES +{% elif data.pubtable or data.pubschema %} + FOR {% if data.pubtable %}TABLE {% if data.only_table%}ONLY {% endif %}{% for pub_table in data.pubtable %}{% if loop.index != 1 %}, {% endif %}{{pub_table['table_name']}}{% if pub_table['columns'] %} ({% for column in pub_table['columns'] %}{% if loop.index != 1 %}, {% endif %}{{column}}{% endfor %}){% endif %}{% if pub_table['where'] %} WHERE ({{pub_table['where']}}){% endif %}{% endfor %}{% endif %}{% if data.pubtable and data.pubschema %},{% endif %} +{% if data.pubschema %} + TABLES IN SCHEMA {% for pub_schema in data.pubschema %}{% if loop.index != 1 %}, {% endif %}{{ pub_schema }}{% endfor %} +{% endif %} + +{% endif %} +{% if data.evnt_insert or data.evnt_update or data.evnt_delete or data.evnt_truncate %} + WITH (publish = '{% if data.evnt_insert %}insert{% if add_comma_after_insert == 'insert' %}, {% endif %}{% endif %}{% if data.evnt_update %}update{% if add_comma_after_update == 'update' %}, {% endif %}{% endif %}{% if data.evnt_delete %}delete{% if add_comma_after_delete == 'delete' %}, {% endif %}{% endif %}{% if data.evnt_truncate %}truncate{% endif %}', publish_via_partition_root = {{ data.publish_via_partition_root|lower }}); +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_all_schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_all_schemas.sql new file mode 100644 index 000000000..fa5a34cbf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_all_schemas.sql @@ -0,0 +1,5 @@ +select nspname from pg_catalog.pg_namespace c WHERE + c.nspname NOT LIKE 'pg\_%' + AND c.nspname NOT IN ('information_schema') +ORDER BY + 1; diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_pub_schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_pub_schemas.sql new file mode 100644 index 000000000..d6def061a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_pub_schemas.sql @@ -0,0 +1,4 @@ +SELECT n.nspname AS sname +FROM pg_catalog.pg_publication_namespace pubnsp +JOIN pg_catalog.pg_namespace n ON pubnsp.pnnspid = n.oid +WHERE pnpubid = {{pbid}} :: oid; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_tables.sql new file mode 100644 index 000000000..ca3d718e6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/get_tables.sql @@ -0,0 +1,6 @@ +SELECT pg_catalog.quote_ident(n.nspname) || '.' || pg_catalog.quote_ident(cls.relname) AS table_name, + (SELECT array_agg(attname) FROM pg_attribute att WHERE attrelid = prel.prrelid AND attnum IN (SELECT unnest(prattrs) FROM pg_publication_rel WHERE oid = prel.oid ) ) AS columns, + pg_catalog.pg_get_expr(prel.prqual, prel.prrelid) AS where + FROM pg_publication_rel prel + JOIN pg_class cls ON cls.oid = prel.prrelid + JOIN pg_catalog.pg_namespace n ON cls.relnamespace = n.oid WHERE prel.prpubid = {{pbid}} :: oid; diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/update.sql new file mode 100644 index 000000000..4a8c02149 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/15_plus/update.sql @@ -0,0 +1,67 @@ +{% if data.evnt_delete or data.evnt_update or data.evnt_truncate %} +{% set add_comma_after_insert = 'insert' %} +{% endif %} +{% if data.evnt_truncate %} +{% set add_comma_after_delete = 'delete' %} +{% endif %} +{% if data.evnt_delete or data.evnt_truncate%} +{% set add_comma_after_update = 'update' %} +{% endif %} +{### Alter publication owner ###} +{% if data.pubowner %} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} + OWNER TO {{ conn|qtIdent(data.pubowner) }}; + +{% endif %} +{### Alter publication event ###} +{% if (data.evnt_insert is defined and data.evnt_insert != o_data.evnt_insert) or (data.evnt_update is defined and data.evnt_update != o_data.evnt_update) or (data.evnt_delete is defined and data.evnt_delete != o_data.evnt_delete) or (data.evnt_truncate is defined and data.evnt_truncate != o_data.evnt_truncate) %} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} SET + (publish = '{% if data.evnt_insert %}insert{% if add_comma_after_insert == 'insert' %}, {% endif %}{% endif %}{% if data.evnt_update %}update{% if add_comma_after_update == 'update' %}, {% endif %}{% endif %}{% if data.evnt_delete %}delete{% if add_comma_after_delete == 'delete' %}, {% endif %}{% endif %}{% if data.evnt_truncate %}truncate{% endif %}'); + +{% endif %} +{### Alter publication partition root ###} +{% if data.publish_via_partition_root is defined and data.publish_via_partition_root != o_data.publish_via_partition_root%} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} SET + (publish_via_partition_root = {{ data.publish_via_partition_root|lower }}); + +{% endif %} +{### Alter drop publication table ###} +{% if drop_table %} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} + DROP TABLE {% if data.only_table%}ONLY {% endif %}{% for pub_table in drop_table_data %}{% if loop.index != 1 %}, {% endif %}{{ pub_table['table_name'] or pub_table }}{% endfor %}; + +{% endif %} + +{### Alter drop publication schema ###} +{% if drop_schema %} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} + DROP TABLES IN SCHEMA {% for pub_schema in drop_schema_data %}{% if loop.index != 1 %}, {% endif %}{{ pub_schema }}{% endfor %}; + +{% endif %} + +{### Alter update publication table ###} +{% if update_table %} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} + SET TABLE {% for pub_table in update_table_data %}{% if loop.index != 1 %}, TABLE {% endif %}{{ pub_table['table_name'] or pub_table }}{% if pub_table['columns'] %} ({% for column in pub_table['columns'] %}{% if loop.index != 1 %}, {% endif %}{{ column }}{% endfor %}){% endif %}{% if pub_table['where'] %} WHERE ({{pub_table['where']}}){% endif %}{% endfor %}; + +{% endif %} + +{### Alter publication table ###} +{% if add_table %} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} + ADD TABLE {% if data.only_table%}ONLY {% endif %}{% for pub_table in add_table_data %}{% if loop.index != 1 %}, {% endif %}{{ pub_table['table_name'] or pub_table }}{% if pub_table['columns'] %} ({% for column in pub_table['columns'] %}{% if loop.index != 1 %}, {% endif %}{{ column }}{% endfor %}){% endif %}{% if pub_table['where'] %} WHERE ({{pub_table['where']}}){% endif %}{% endfor %}; + +{% endif %} + +{### Alter add publication schema ###} +{% if add_schema %} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} + ADD TABLES IN SCHEMA {% for pub_schema in add_schema_data %}{% if loop.index != 1 %}, {% endif %}{{ pub_schema }}{% endfor %}; + +{% endif %} + +{### Alter publication name ###} +{% if data.name != o_data.name %} +ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_columns.sql new file mode 100644 index 000000000..54f4720aa --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_columns.sql @@ -0,0 +1,7 @@ +SELECT + pg_catalog.quote_ident(attname) as column +FROM + pg_attribute +WHERE + attrelid = '{{ tid }}' :: regclass + and attstattarget =-1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_tables.sql index bb6b9e48e..717445730 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_tables.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_tables.sql @@ -1,7 +1,15 @@ -SELECT pg_catalog.quote_ident(c.table_schema)||'.'||pg_catalog.quote_ident(c.table_name) AS table -FROM information_schema.tables c -WHERE c.table_type = 'BASE TABLE' - AND c.table_schema NOT LIKE 'pg\_%' - AND c.table_schema NOT LIKE 'pgagent' - AND c.table_schema NOT LIKE 'sys' - AND c.table_schema NOT IN ('information_schema') ORDER BY 1; +SELECT + pg_catalog.quote_ident(c.table_schema)|| '.' || pg_catalog.quote_ident(c.table_name) AS table, + ( + pg_catalog.quote_ident(c.table_schema)|| '.' || pg_catalog.quote_ident(c.table_name) + ):: regclass :: oid as tid +FROM + information_schema.tables c +WHERE + c.table_type = 'BASE TABLE' + AND c.table_schema NOT LIKE 'pg\_%' + AND c.table_schema NOT LIKE 'pgagent' + AND c.table_schema NOT LIKE 'sys' + AND c.table_schema NOT IN ('information_schema') +ORDER BY + 1; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_tables.sql index 668b4eba5..8cf5f2099 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_tables.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_tables.sql @@ -1,6 +1,4 @@ -SELECT pg_catalog.quote_ident(pgb_table.schemaname)||'.'||pg_catalog.quote_ident(pgb_table.tablename) -AS pubtable, -pg_catalog.quote_ident(pgb_table.schemaname)||'.'||pg_catalog.quote_ident(pgb_table.tablename) -AS proptable - FROM pg_catalog.pg_publication_tables pgb_table WHERE pubname = '{{ pname }}' -AND pgb_table.schemaname NOT LIKE 'pgagent'; +SELECT pg_catalog.quote_ident(n.nspname) || '.' || pg_catalog.quote_ident(cls.relname) AS table_name + FROM pg_publication_rel prel + JOIN pg_class cls ON cls.oid = prel.prrelid + JOIN pg_catalog.pg_namespace n ON cls.relnamespace = n.oid WHERE prel.prpubid = {{pbid}} :: oid; diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_add_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_add_tables.sql new file mode 100644 index 000000000..6a568e378 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_add_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_add_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_add_tables_msql.sql new file mode 100644 index 000000000..85a57af08 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_add_tables_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + ADD TABLE public.test_table_publication_2; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_drop_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_drop_tables.sql new file mode 100644 index 000000000..917913dab --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_drop_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication_2 + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_drop_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_drop_tables_msql.sql new file mode 100644 index 000000000..0978c49a3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_drop_tables_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + DROP TABLE public.test_table_publication; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_event_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_event_msql.sql index 61cd73d7e..5000a11a9 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_event_msql.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_event_msql.sql @@ -1,2 +1,2 @@ -ALTER PUBLICATION alterd_publication_event SET -(publish = 'insert, update', publish_via_partition_root = false); +ALTER PUBLICATION alterd_publication SET + (publish = 'insert, update, delete'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_msql.sql index 30869d2a8..f799b60df 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_msql.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/alter_publication_msql.sql @@ -1 +1,2 @@ -ALTER PUBLICATION test_publication_to_alter RENAME TO alterd_publication; +ALTER PUBLICATION test_publication_create + RENAME TO alterd_publication; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/create_publication_few_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/create_publication_few_tables.sql new file mode 100644 index 000000000..f1713b9b1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/create_publication_few_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/create_publication_few_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/create_publication_few_tables_msql.sql new file mode 100644 index 000000000..63e7e8b51 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/create_publication_few_tables_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/test.json b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/test.json index 1876eb9d7..1cb54e3c6 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/test.json +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/13_plus/test.json @@ -6,7 +6,7 @@ "endpoint": "NODE-table.obj", "sql_endpoint": "NODE-table.sql_id", "data": { - "name": "test_publication", + "name": "test_table_publication", "columns": [ { "name": "emp_id", @@ -28,6 +28,30 @@ }, "store_object_id": true }, + { + "type": "create", + "name": "Create Second Table For Publication", + "endpoint": "NODE-table.obj", + "sql_endpoint": "NODE-table.sql_id", + "data": { + "name": "test_table_publication_2", + "columns": [ + { + "name": "dept_id", + "cltype": "integer", + "is_primary_key": true + }, + { + "name": "dept_name", + "cltype": "text" + } + ], + "is_partitioned": false, + "schema": "public", + "spcname": "pg_default" + }, + "store_object_id": true + }, { "type": "create", "name": "Create Publication", @@ -53,27 +77,84 @@ "name": "Alter Publication name", "endpoint": "NODE-publication.obj_id", "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", "data": { "name": "alterd_publication" }, - "expected_sql_file": "alter_publication.sql" + "expected_sql_file": "alter_publication.sql", + "expected_msql_file": "alter_publication_msql.sql" }, { "type": "alter", "name": "Alter Publication event", "endpoint": "NODE-publication.obj_id", "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", "data": { "evnt_delete": true }, - "expected_sql_file": "alter_publication_event.sql" + "expected_sql_file": "alter_publication_event.sql", + "expected_msql_file": "alter_publication_event_msql.sql" }, { "type": "delete", "name": "Drop publication", "endpoint": "NODE-publication.delete_id", "data": { - "name": "alterd_publication_event" + "name": "alterd_publication" + } + }, + { + "type": "create", + "name": "Create Publication for few tables", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": false, + "pubtable": ["public.test_table_publication"] + }, + "expected_sql_file": "create_publication_few_tables.sql", + "expected_msql_file": "create_publication_few_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via adding new tables", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": ["public.test_table_publication", "public.test_table_publication_2"] + }, + "expected_sql_file": "alter_publication_add_tables.sql", + "expected_msql_file": "alter_publication_add_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via deleting tables", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": ["public.test_table_publication_2"] + }, + "expected_sql_file": "alter_publication_drop_tables.sql", + "expected_msql_file": "alter_publication_drop_tables_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" } } ] diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication.sql new file mode 100644 index 000000000..69e4e112b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication.sql @@ -0,0 +1,7 @@ +-- Publication: alterd_publication + +-- DROP PUBLICATION IF EXISTS alterd_publication; + +CREATE PUBLICATION alterd_publication + FOR ALL TABLES + WITH (publish = 'insert, update', publish_via_partition_root = false); diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_schemas.sql new file mode 100644 index 000000000..4f44598d9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_schemas.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLES IN SCHEMA test_schema_publication, test_schema_publication_2 + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_schemas_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_schemas_msql.sql new file mode 100644 index 000000000..c06d984c5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_schemas_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + ADD TABLES IN SCHEMA test_schema_publication_2; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables.sql new file mode 100644 index 000000000..31ab428ab --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns.sql new file mode 100644 index 000000000..21252b513 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 (dept_id) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_msql.sql new file mode 100644 index 000000000..e18375af5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + ADD TABLE public.test_table_publication_2 (dept_id); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_where.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_where.sql new file mode 100644 index 000000000..1dde4fa25 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_where.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 (dept_id) WHERE ((dept_id = 2)) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_where_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_where_msql.sql new file mode 100644 index 000000000..e0ebd4566 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_columns_where_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + ADD TABLE public.test_table_publication_2 (dept_id) WHERE (dept_id=2); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_msql.sql new file mode 100644 index 000000000..85a57af08 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + ADD TABLE public.test_table_publication_2; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_where.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_where.sql new file mode 100644 index 000000000..dd3d72488 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_where.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 WHERE ((dept_id = 2)) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_where_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_where_msql.sql new file mode 100644 index 000000000..9897f30e2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_add_tables_where_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + ADD TABLE public.test_table_publication_2 WHERE (dept_id=2); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_schemas.sql new file mode 100644 index 000000000..8444eafb6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_schemas.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLES IN SCHEMA test_schema_publication_2 + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_schemas_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_schemas_msql.sql new file mode 100644 index 000000000..cf8259154 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_schemas_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + DROP TABLES IN SCHEMA test_schema_publication; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_tables.sql new file mode 100644 index 000000000..7c806b089 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_tables_msql.sql new file mode 100644 index 000000000..5d5cba35c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_drop_tables_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + DROP TABLE public.test_table_publication_2; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_event.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_event.sql new file mode 100644 index 000000000..7eb05bdac --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_event.sql @@ -0,0 +1,7 @@ +-- Publication: alterd_publication + +-- DROP PUBLICATION IF EXISTS alterd_publication; + +CREATE PUBLICATION alterd_publication + FOR ALL TABLES + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_event_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_event_msql.sql new file mode 100644 index 000000000..a616189a2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_event_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION alterd_publication SET + (publish = 'insert, update, delete, truncate'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_msql.sql new file mode 100644 index 000000000..f799b60df --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + RENAME TO alterd_publication; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns.sql new file mode 100644 index 000000000..c42c6bb88 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 (dept_name) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_msql.sql new file mode 100644 index 000000000..b2d37325c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + SET TABLE public.test_table_publication, TABLE public.test_table_publication_2 (dept_name); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_where.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_where.sql new file mode 100644 index 000000000..cbe382337 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_where.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 (dept_name) WHERE ((dept_name = 'test'::text)) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_where_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_where_msql.sql new file mode 100644 index 000000000..cf1efd0e3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_columns_where_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + SET TABLE public.test_table_publication, TABLE public.test_table_publication_2 (dept_name) WHERE (dept_name='test'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_where.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_where.sql new file mode 100644 index 000000000..de0918090 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_where.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 WHERE ((dept_name = 'test'::text)) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_where_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_where_msql.sql new file mode 100644 index 000000000..83fa27ef0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/alter_publication_set_tables_where_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + SET TABLE public.test_table_publication, TABLE public.test_table_publication_2 WHERE (dept_name='test'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication.sql new file mode 100644 index 000000000..266553d7b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR ALL TABLES + WITH (publish = 'insert, update', publish_via_partition_root = false); diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_schemas.sql new file mode 100644 index 000000000..be8498ce8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_schemas.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLES IN SCHEMA test_schema_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_schemas_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_schemas_msql.sql new file mode 100644 index 000000000..caa4333c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_schemas_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLES IN SCHEMA test_schema_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables.sql new file mode 100644 index 000000000..7c806b089 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns.sql new file mode 100644 index 000000000..9594b98a3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication (emp_id, name) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_msql.sql new file mode 100644 index 000000000..cc095ec98 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication (emp_id, name) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_where.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_where.sql new file mode 100644 index 000000000..8f1a98111 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_where.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication (emp_id, name) WHERE (((emp_id = 2) AND (name = 'test'::text))) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_where_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_where_msql.sql new file mode 100644 index 000000000..a8f72b4ff --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_columns_where_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication (emp_id, name) WHERE (emp_id=2 and name='test') + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_msql.sql new file mode 100644 index 000000000..878660992 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_only.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_only.sql new file mode 100644 index 000000000..7c806b089 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_only.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_only_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_only_msql.sql new file mode 100644 index 000000000..0cff3b522 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_only_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE ONLY public.test_table_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_schemas.sql new file mode 100644 index 000000000..f34aeb82b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_schemas.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, TABLES IN SCHEMA test_schema_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_schemas_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_schemas_msql.sql new file mode 100644 index 000000000..ecc7399e1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_schemas_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, TABLES IN SCHEMA test_schema_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where.sql new file mode 100644 index 000000000..85c23bae6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication WHERE (((emp_id = 2) AND (name = 'test'::text))) + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_msql.sql new file mode 100644 index 000000000..980fdd686 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication WHERE (emp_id=2 and name='test') + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_schemas.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_schemas.sql new file mode 100644 index 000000000..c376b165d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_schemas.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication WHERE (((emp_id = 2) AND (name = 'test'::text))), TABLES IN SCHEMA test_schema_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_schemas_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_schemas_msql.sql new file mode 100644 index 000000000..d3d91a615 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_few_tables_where_schemas_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication WHERE (emp_id=2 and name='test'), TABLES IN SCHEMA test_schema_publication + WITH (publish = 'insert, update, delete, truncate', publish_via_partition_root = false); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_msql.sql new file mode 100644 index 000000000..27a371d68 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/create_publication_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR ALL TABLES + WITH (publish = 'insert, update', publish_via_partition_root = false); diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/test.json b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/test.json new file mode 100644 index 000000000..795173776 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/15_plus/test.json @@ -0,0 +1,582 @@ +{ + "scenarios": [ + { + "type": "create", + "name": "Create Table For Publication", + "endpoint": "NODE-table.obj", + "sql_endpoint": "NODE-table.sql_id", + "data": { + "name": "test_table_publication", + "columns": [ + { + "name": "emp_id", + "cltype": "integer", + "is_primary_key": true + }, + { + "name": "name", + "cltype": "text" + }, + { + "name": "salary", + "cltype": "bigint" + } + ], + "is_partitioned": false, + "schema": "public", + "spcname": "pg_default" + }, + "store_object_id": true + }, + { + "type": "create", + "name": "Create Second Table For Publication", + "endpoint": "NODE-table.obj", + "sql_endpoint": "NODE-table.sql_id", + "data": { + "name": "test_table_publication_2", + "columns": [ + { + "name": "dept_id", + "cltype": "integer", + "is_primary_key": true + }, + { + "name": "dept_name", + "cltype": "text" + } + ], + "is_partitioned": false, + "schema": "public", + "spcname": "pg_default" + }, + "store_object_id": true + }, + { + "type": "create", + "name": "Create Schema For Publication", + "endpoint": "NODE-schema.obj", + "sql_endpoint": "NODE-schema.sql_id", + "data": { + "name": "test_schema_publication" + }, + "store_object_id": true + }, + { + "type": "create", + "name": "Create Second Schema For Publication", + "endpoint": "NODE-schema.obj", + "sql_endpoint": "NODE-schema.sql_id", + "data": { + "name": "test_schema_publication_2" + }, + "store_object_id": true + }, + { + "type": "create", + "name": "Create Publication for all tables with insert and update", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": true, + "only_table": false, + "pubtable": "", + "pubschema": "" + }, + "expected_sql_file": "create_publication.sql", + "expected_msql_file": "create_publication_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication name for all tables", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "name": "alterd_publication" + }, + "expected_sql_file": "alter_publication.sql", + "expected_msql_file": "alter_publication_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication event for all tables", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "evnt_delete": true, + "evnt_truncate": true + }, + "expected_sql_file": "alter_publication_event.sql", + "expected_msql_file": "alter_publication_event_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for all tables", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "alterd_publication" + } + }, + { + "type": "create", + "name": "Create Publication for few tables", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": false, + "pubtable": [{ + "table_name": "public.test_table_publication" + }], + "pubschema": "" + }, + "expected_sql_file": "create_publication_few_tables.sql", + "expected_msql_file": "create_publication_few_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via adding new tables", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "added": [{ + "table_name": "public.test_table_publication_2" + }] + } + }, + "expected_sql_file": "alter_publication_add_tables.sql", + "expected_msql_file": "alter_publication_add_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via deleting tables with", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "deleted": [{ + "table_name": "public.test_table_publication_2" + }] + } + }, + "expected_sql_file": "alter_publication_drop_tables.sql", + "expected_msql_file": "alter_publication_drop_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via adding new tables with columns", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "added": [{ + "table_name": "public.test_table_publication_2", + "columns": ["dept_id"] + }] + } + }, + "expected_sql_file": "alter_publication_add_tables_columns.sql", + "expected_msql_file": "alter_publication_add_tables_columns_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via updating tables with columns", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "changed": [{ + "table_name": "public.test_table_publication_2", + "columns": ["dept_name"] + }] + } + }, + "expected_sql_file": "alter_publication_set_tables_columns.sql", + "expected_msql_file": "alter_publication_set_tables_columns_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via deleting tables with", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "deleted": [{ + "table_name": "public.test_table_publication_2" + }] + } + }, + "expected_sql_file": "alter_publication_drop_tables.sql", + "expected_msql_file": "alter_publication_drop_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via adding new tables with where", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "added": [{ + "table_name": "public.test_table_publication_2", + "where": "dept_id=2" + }] + } + }, + "expected_sql_file": "alter_publication_add_tables_where.sql", + "expected_msql_file": "alter_publication_add_tables_where_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via updating tables with where", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "changed": [{ + "table_name": "public.test_table_publication_2", + "where": "dept_name='test'" + }] + } + }, + "expected_sql_file": "alter_publication_set_tables_where.sql", + "expected_msql_file": "alter_publication_set_tables_where_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via deleting tables with", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "deleted": [{ + "table_name": "public.test_table_publication_2" + }] + } + }, + "expected_sql_file": "alter_publication_drop_tables.sql", + "expected_msql_file": "alter_publication_drop_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via adding new tables with columns and where", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "added": [{ + "table_name": "public.test_table_publication_2", + "columns": ["dept_id"], + "where": "dept_id=2" + }] + } + }, + "expected_sql_file": "alter_publication_add_tables_columns_where.sql", + "expected_msql_file": "alter_publication_add_tables_columns_where_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via updating tables with columns and where", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": { + "changed": [{ + "table_name": "public.test_table_publication_2", + "columns": ["dept_name"], + "where": "dept_name='test'" + }] + } + }, + "expected_sql_file": "alter_publication_set_tables_columns_where.sql", + "expected_msql_file": "alter_publication_set_tables_columns_where_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" + } + }, + { + "type": "create", + "name": "Create Publication for few schemas", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": false, + "pubtable": "", + "pubschema": ["test_schema_publication"] + }, + "expected_sql_file": "create_publication_few_schemas.sql", + "expected_msql_file": "create_publication_few_schemas_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few schemas via adding new schemas", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubschema": ["test_schema_publication","test_schema_publication_2"] + }, + "expected_sql_file": "alter_publication_add_schemas.sql", + "expected_msql_file": "alter_publication_add_schemas_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few schemas via deleting schemas", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubschema": ["test_schema_publication_2"] + }, + "expected_sql_file": "alter_publication_drop_schemas.sql", + "expected_msql_file": "alter_publication_drop_schemas_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few schemas", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" + } + }, + { + "type": "create", + "name": "Create Publication for few tables with only", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": true, + "pubtable": [{ + "table_name": "public.test_table_publication" + }], + "pubschema": "" + }, + "expected_sql_file": "create_publication_few_tables_only.sql", + "expected_msql_file": "create_publication_few_tables_only_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables with only", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" + } + }, + { + "type": "create", + "name": "Create Publication for few tables and few schemas", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": false, + "pubtable": [{ + "table_name": "public.test_table_publication" + }], + "pubschema": ["test_schema_publication"] + }, + "expected_sql_file": "create_publication_few_tables_schemas.sql", + "expected_msql_file": "create_publication_few_tables_schemas_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables and few schemas", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" + } + }, + { + "type": "create", + "name": "Create Publication for few tables with columns", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": false, + "pubtable": [{ + "table_name": "public.test_table_publication", + "columns": ["emp_id", "name"] + }], + "pubschema": "" + }, + "expected_sql_file": "create_publication_few_tables_columns.sql", + "expected_msql_file": "create_publication_few_tables_columns_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables with columns", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" + } + }, + { + "type": "create", + "name": "Create Publication for few tables with where", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": false, + "pubtable": [{ + "table_name": "public.test_table_publication", + "where": "emp_id=2 and name='test'" + }], + "pubschema": "" + }, + "expected_sql_file": "create_publication_few_tables_where.sql", + "expected_msql_file": "create_publication_few_tables_where_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables with where", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" + } + }, + { + "type": "create", + "name": "Create Publication for few tables with columns and where", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": false, + "pubtable": [{ + "table_name": "public.test_table_publication", + "columns": ["emp_id", "name"], + "where": "emp_id=2 and name='test'" + }], + "pubschema": "" + }, + "expected_sql_file": "create_publication_few_tables_columns_where.sql", + "expected_msql_file": "create_publication_few_tables_columns_where_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables with columns and where", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" + } + }, + { + "type": "create", + "name": "Create Publication for few tables with where and few schemas", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "publish_via_partition_root": false, + "all_table": false, + "only_table": false, + "pubtable": [{ + "table_name": "public.test_table_publication", + "where": "emp_id=2 and name='test'" + }], + "pubschema": ["test_schema_publication"] + }, + "expected_sql_file": "create_publication_few_tables_where_schemas.sql", + "expected_msql_file": "create_publication_few_tables_where_schemas_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables with where and few schemas", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" + } + } + ] +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_add_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_add_tables.sql new file mode 100644 index 000000000..311a3e966 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_add_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication, public.test_table_publication_2 + WITH (publish = 'insert, update, delete, truncate'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_add_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_add_tables_msql.sql new file mode 100644 index 000000000..85a57af08 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_add_tables_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + ADD TABLE public.test_table_publication_2; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_drop_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_drop_tables.sql new file mode 100644 index 000000000..c3bea100e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_drop_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication_2 + WITH (publish = 'insert, update, delete, truncate'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_drop_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_drop_tables_msql.sql new file mode 100644 index 000000000..0978c49a3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_drop_tables_msql.sql @@ -0,0 +1,2 @@ +ALTER PUBLICATION test_publication_create + DROP TABLE public.test_table_publication; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_event_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_event_msql.sql index f01594da2..5000a11a9 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_event_msql.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_event_msql.sql @@ -1,2 +1,2 @@ -ALTER PUBLICATION alterd_publication_event SET -(publish = 'insert, update'); +ALTER PUBLICATION alterd_publication SET + (publish = 'insert, update, delete'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_msql.sql index 30869d2a8..f799b60df 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_msql.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/alter_publication_msql.sql @@ -1 +1,2 @@ -ALTER PUBLICATION test_publication_to_alter RENAME TO alterd_publication; +ALTER PUBLICATION test_publication_create + RENAME TO alterd_publication; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/create_publication_few_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/create_publication_few_tables.sql new file mode 100644 index 000000000..4b208c597 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/create_publication_few_tables.sql @@ -0,0 +1,7 @@ +-- Publication: test_publication_create + +-- DROP PUBLICATION IF EXISTS test_publication_create; + +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication + WITH (publish = 'insert, update, delete, truncate'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/create_publication_few_tables_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/create_publication_few_tables_msql.sql new file mode 100644 index 000000000..629ac3abb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/create_publication_few_tables_msql.sql @@ -0,0 +1,3 @@ +CREATE PUBLICATION test_publication_create + FOR TABLE public.test_table_publication + WITH (publish = 'insert, update, delete, truncate'); \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/test.json b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/test.json index 459119c5d..8f09ae05b 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/test.json +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/default/test.json @@ -6,7 +6,7 @@ "endpoint": "NODE-table.obj", "sql_endpoint": "NODE-table.sql_id", "data": { - "name": "test_publication", + "name": "test_table_publication", "columns": [ { "name": "emp_id", @@ -28,6 +28,30 @@ }, "store_object_id": true }, + { + "type": "create", + "name": "Create Second Table For Publication", + "endpoint": "NODE-table.obj", + "sql_endpoint": "NODE-table.sql_id", + "data": { + "name": "test_table_publication_2", + "columns": [ + { + "name": "dept_id", + "cltype": "integer", + "is_primary_key": true + }, + { + "name": "dept_name", + "cltype": "text" + } + ], + "is_partitioned": false, + "schema": "public", + "spcname": "pg_default" + }, + "store_object_id": true + }, { "type": "create", "name": "Create Publication", @@ -52,27 +76,83 @@ "name": "Alter Publication name", "endpoint": "NODE-publication.obj_id", "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", "data": { "name": "alterd_publication" }, - "expected_sql_file": "alter_publication.sql" + "expected_sql_file": "alter_publication.sql", + "expected_msql_file": "alter_publication_msql.sql" }, { "type": "alter", "name": "Alter Publication event", "endpoint": "NODE-publication.obj_id", "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", "data": { "evnt_delete": true }, - "expected_sql_file": "alter_publication_event.sql" + "expected_sql_file": "alter_publication_event.sql", + "expected_msql_file": "alter_publication_event_msql.sql" }, { "type": "delete", "name": "Drop publication", "endpoint": "NODE-publication.delete_id", "data": { - "name": "alterd_publication_event" + "name": "alterd_publication" + } + }, + { + "type": "create", + "name": "Create Publication for few tables", + "endpoint": "NODE-publication.obj", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql", + "data": { + "name": "test_publication_create", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": true, + "evnt_truncate": true, + "pubowner": "postgres", + "all_table": false, + "only_table": false, + "pubtable": ["public.test_table_publication"] + }, + "expected_sql_file": "create_publication_few_tables.sql", + "expected_msql_file": "create_publication_few_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via adding new tables", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": ["public.test_table_publication", "public.test_table_publication_2"] + }, + "expected_sql_file": "alter_publication_add_tables.sql", + "expected_msql_file": "alter_publication_add_tables_msql.sql" + }, + { + "type": "alter", + "name": "Alter Publication for few tables via deleting tables", + "endpoint": "NODE-publication.obj_id", + "sql_endpoint": "NODE-publication.sql_id", + "msql_endpoint": "NODE-publication.msql_id", + "data": { + "pubtable": ["public.test_table_publication_2"] + }, + "expected_sql_file": "alter_publication_drop_tables.sql", + "expected_msql_file": "alter_publication_drop_tables_msql.sql" + }, + { + "type": "delete", + "name": "Drop publication for few tables", + "endpoint": "NODE-publication.delete_id", + "data": { + "name": "test_publication_create" } } ] diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/publication_test_data.json b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/publication_test_data.json index 1b91e7c44..ddfd20ca4 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/publication_test_data.json +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/publication_test_data.json @@ -1,7 +1,7 @@ { "add_publication": [ { - "name": "Create publication with insert and update", + "name": "Create publication with insert and update and all tables", "url": "/browser/publication/obj/", "is_positive_test": true, "test_data": { @@ -13,7 +13,8 @@ "publish_via_partition_root": false, "pubowner": "postgres", "all_table": true, - "pubtable": "" + "pubtable": "", + "pubschema": "" }, "mocking_required": false, "mock_data": {}, @@ -35,7 +36,8 @@ "publish_via_partition_root": false, "pubowner": "postgres", "all_table": false, - "pubtable": "PLACE_HOLDER" + "pubtable": "PLACE_HOLDER", + "pubschema": "" }, "mocking_required": false, "mock_data": {}, @@ -43,6 +45,160 @@ "status_code": 200 } }, + { + "name": "Create publication for few schemas", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "few_schemas": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACEHOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Create publication for few schemas and few tables", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "few_schemas": true, + "few_tables": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACEHOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Create publication for few tables and where clause", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "few_tables": true, + "with_where": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACEHOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Create publication for few tables and columns", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "few_tables": true, + "with_columns": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACEHOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Create publication for few tables and columns and where clause", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "few_tables": true, + "with_columns": true, + "with_where": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACEHOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Create publication for few schemas and few tables and columns", + "url": "/browser/publication/obj/", + "is_positive_test": false, + "few_schemas": true, + "few_tables": true, + "with_columns": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACEHOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "PLACE_HOLDER" + }, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg3.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, { "name": "Create a publication without name", "url": "/browser/publication/obj/", @@ -56,7 +212,8 @@ "evnt_truncate": false, "pubowner": "postgres", "all_table": true, - "pubtable": "" + "pubtable": "", + "pubschema": "" }, "mocking_required": false, "mock_data": {}, @@ -64,6 +221,30 @@ "status_code": 410 } }, + { + "name": "Exception while adding a publication", + "url": "/browser/publication/obj/", + "is_positive_test": false, + "test_data": { + "name": "PLACEHOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "pubowner": "postgres", + "all_table": true, + "pubtable": "", + "pubschema": "" + }, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg3.connection.Connection.execute_dict", + "return_value": "(True, True)(False, 'Mocked Internal Server Error ')" + }, + "expected_data": { + "status_code": 500 + } + }, { "name": "Error while adding a publication", "url": "/browser/publication/obj/", @@ -77,7 +258,8 @@ "evnt_truncate": false, "pubowner": "postgres", "all_table": true, - "pubtable": "" + "pubtable": "", + "pubschema": "" }, "mocking_required": true, "mock_data": { @@ -87,29 +269,6 @@ "expected_data": { "status_code": 500 } - }, - { - "name": "Exception while adding a publication", - "url": "/browser/publication/obj/", - "is_positive_test": false, - "test_data": { - "name": "PLACEHOLDER", - "evnt_insert": true, - "evnt_update": true, - "evnt_delete": false, - "evnt_truncate": false, - "pubowner": "postgres", - "all_table": true, - "pubtable": "" - }, - "mocking_required": true, - "mock_data": { - "function_name": "pgadmin.utils.driver.psycopg3.connection.Connection.execute_dict", - "return_value": "(True, True)(False, 'Mocked Internal Server Error ')" - }, - "expected_data": { - "status_code": 500 - } } ], "get_publication": [ @@ -275,6 +434,15 @@ "update_name": true, "test_data": { "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "", "id": "PLACE_HOLDER" }, "mocking_required": false, @@ -290,7 +458,16 @@ "owner_publication": true, "test_data": { "id": "PLACE_HOLDER", - "evnt_insert": "PLACEHOLDER" + "name": "PLACE_HOLDER", + "evnt_insert": "PLACEHOLDER", + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "" }, "mocking_required": false, "mock_data": {}, @@ -305,7 +482,16 @@ "owner_publication": true, "test_data": { "id": "PLACE_HOLDER", - "evnt_delete": "PLACEHOLDER" + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": "PLACE_HOLDER", + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "" }, "mocking_required": false, "mock_data": {}, @@ -320,7 +506,16 @@ "mocking_required": true, "test_data": { "name": "PLACE_HOLDER", - "id": "PLACE_HOLDER" + "id": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "" }, "mock_data": { "function_name": "pgadmin.utils.driver.psycopg3.connection.Connection.execute_dict", @@ -337,7 +532,17 @@ "wrong_publication_id": true, "mocking_required": false, "test_data": { - "id": "PLACE_HOLDER" + "id": "PLACE_HOLDER", + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "" }, "mock_data": {}, "expected_data": { @@ -345,6 +550,438 @@ } } ], + "update_publication_add_table": [ + { + "name": "update a publication via adding a table", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "add_table": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via adding a table with where and columns", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "add_table_with_where_columns": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via adding a table with columns", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "add_table_with_columns": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via adding a table with where", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "add_table_with_where": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "update_publication_update_table": [ + { + "name": "update a publication via updating a table columns", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "update_table_with_columns": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via updating a table where clause", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "update_table_with_where": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via updating a table columns and where clause", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "update_table_with_where_columns": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via updating old new tables with columns", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "update_old_new_tables_with_columns": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via updating old new tables with where clause", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "update_old_new_tables_with_where": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via updating old new tables with columns and where clause", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "update_old_new_tables_with_columns_where": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via updating old new tables with where clause and schema", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "update_old_new_tables_with_where_schema": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "PLACE_HOLDER", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "update_publication_drop_table": [ + { + "name": "update a publication via dropping a table", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "drop_tables": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via dropping a table with columns", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "drop_tables_with_columns": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a publication via dropping a table with where clause", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "drop_tables_with_where": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "PLACE_HOLDER", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "update_publication_add_schema": [ + { + "name": "update a publication via adding a schema", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "update_publication_update_schema": [ + { + "name": "update a publication via updating old new schemas", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "PLACE_HOLDER", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], + "update_publication_drop_schema": [ + { + "name": "update a publication via dropping a schema", + "url": "/browser/publication/obj/", + "is_positive_test": true, + "compatible_sversion": true, + "test_data": { + "name": "PLACE_HOLDER", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "evnt_truncate": false, + "publish_via_partition_root": false, + "pubowner": "postgres", + "all_table": false, + "pubtable": "", + "pubschema": "PLACE_HOLDER", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ], "delete_multiple_publication": [ { "name": "Delete multiple publication", diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_create.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_create.py index f4c1c7709..247dc0102 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_create.py +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_create.py @@ -41,6 +41,10 @@ class PublicationsAddTestCase(BaseTestGenerator): "for server version less than 10" ) + if self.server_version < 150000 and \ + hasattr(self, 'compatible_sversion'): + self.skipTest("The version is not compatible for" + " the current test case") db_con = database_utils.connect_database(self, utils.SERVER_GROUP, self.server_id, self.db_id) @@ -48,13 +52,15 @@ class PublicationsAddTestCase(BaseTestGenerator): raise Exception( "Could not connect to database to add a publication.") - if self.is_positive_test and hasattr(self, 'few_tables'): + if hasattr(self, 'few_tables'): self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) self.table_id = tables_utils. \ create_table(self.server, self.db_name, self.schema_name, self.table_name) self.test_data['pubtable'] = publication_utils.get_tables(self) + if self.server_version >= 150000 and hasattr(self, 'few_schemas'): + self.test_data['pubschema'] = publication_utils.get_schemas(self) def runTest(self): """This function will publication.""" @@ -68,6 +74,8 @@ class PublicationsAddTestCase(BaseTestGenerator): if hasattr(self, 'without_name'): del data["name"] response = self.create_publication() + elif hasattr(self, 'with_columns'): + response = self.create_publication() elif hasattr(self, 'error_creating_publication'): with patch(self.mock_data["function_name"], return_value=eval(self.mock_data["return_value"])): diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete.py index 2c17a437a..b7b132675 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete.py +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete.py @@ -54,9 +54,7 @@ class PublicationDeleteTestCase(BaseTestGenerator): str(uuid.uuid4())[1:8]) self.publication_id = \ - publication_utils.create_publication(self.server, - self.db_name, - self.publication_name) + publication_utils.create_publication(self) def delete_publication(self): return self.tester.delete( diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete_multiple.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete_multiple.py index 31e50da1c..b5b4627e4 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete_multiple.py +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete_multiple.py @@ -61,9 +61,8 @@ class PublicationDeleteTestCases(BaseTestGenerator): self.publication_name_1 = "test_publication_delete_%s" % ( str(uuid.uuid4())[1:8]) self.publication_ids = [ - publication_utils.create_publication(self.server, self.db_name, - self.publication_name), - publication_utils.create_publication(self.server, self.db_name, + publication_utils.create_publication(self), + publication_utils.create_publication(self, self.publication_name_1), ] diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_get.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_get.py index e7131c389..a540f31a0 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_get.py +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_get.py @@ -60,9 +60,7 @@ class PublicationGetTestCase(BaseTestGenerator): self.publication_name = "test_publication_get_%s" % ( str(uuid.uuid4())[1:8]) self.publication_id = publication_utils. \ - create_publication(self.server, - self.db_name, - self.publication_name) + create_publication(self) def get_publication(self): return self.tester.get( diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put.py index 590d65499..4d529e794 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put.py +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put.py @@ -41,35 +41,16 @@ class PublicationUpdateTestCase(BaseTestGenerator): "for server version less than 10" ) + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, self.server_id, self.db_id) if not db_con['data']["connected"]: raise Exception( "Could not connect to database to delete publication.") - self.schema_id = schema_info["schema_id"] - self.schema_name = schema_info["schema_name"] - schema_response = schema_utils.verify_schemas(self.server, - self.db_name, - self.schema_name) - if not schema_response: - raise Exception("Could not find the schema to delete publication.") - self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) - self.server_version = schema_info["server_version"] - if self.server_version < 99999: - self.skipTest( - "Logical replication is not supported " - "for server version less than 10" - - ) - self.table_id = tables_utils.create_table(self.server, self.db_name, - self.schema_name, - self.table_name) - self.publication_name = "test_publication_update_%s" % ( + self.test_data['name'] = "test_publication_add_%s" % ( str(uuid.uuid4())[1:8]) - self.publication_id = \ - publication_utils.create_publication(self.server, self.db_name, - self.publication_name) + self.publication_id = publication_utils.create_publication(self) def update_publication(self, data): return self.tester.put( @@ -86,22 +67,19 @@ class PublicationUpdateTestCase(BaseTestGenerator): publication_name = publication_utils. \ verify_publication(self.server, self.db_name, - self.publication_name) + self.test_data['name']) + if not publication_name: + raise Exception("Could not find the publication to update.") + if hasattr(self, "update_name"): self.test_data['name'] = "test_publication_update_%s" % ( str(uuid.uuid4())[1:8]) - else: - self.test_data['name'] = self.publication_name - self.test_data['id'] = self.publication_id - if not publication_name: - raise Exception("Could not find the publication to update.") + self.test_data['id'] = self.publication_id if self.is_positive_test: if hasattr(self, "wrong_publication_id"): self.publication_id = 9999 - if hasattr(self, "plid_none"): - self.publication_id = '' response = self.update_publication(self.test_data) else: with patch(self.mock_data["function_name"], @@ -114,13 +92,6 @@ class PublicationUpdateTestCase(BaseTestGenerator): self.expected_data["status_code"]) def tearDown(self): - connection = utils.get_db_connection(self.server['db'], - self.server['username'], - self.server['db_password'], - self.server['host'], - self.server['port'], - self.server['sslmode']) - publication_utils.delete_publication(self.server, self.db_name, self.test_data['name']) diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_add_schema.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_add_schema.py new file mode 100644 index 000000000..5aadfdc37 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_add_schema.py @@ -0,0 +1,89 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as publication_utils + + +class PublicationUpdateAddSchemaTestCase(BaseTestGenerator): + """This class will update the publication.""" + scenarios = utils.generate_scenarios('update_publication_add_schema', + publication_utils.test_cases) + + def setUp(self): + super().setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + self.server_version = schema_info["server_version"] + if self.server_version < 99999: + self.skipTest( + "Logical replication is not supported " + "for server version less than 10" + + ) + if self.server_version < 150000 and \ + hasattr(self, 'compatible_sversion'): + self.skipTest("The version is not compatible for " + "the current test case") + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to delete publication.") + + self.test_data['name'] = "test_publication_add_%s" % ( + str(uuid.uuid4())[1:8]) + self.publication_id = publication_utils.create_publication(self) + + def update_publication(self, data): + return self.tester.put( + self.url + str(utils.SERVER_GROUP) + '/' + + str(self.server_id) + '/' + str( + self.db_id) + + '/' + str(self.publication_id), + data=json.dumps(data), + follow_redirects=True) + + def runTest(self): + """This function will update the publication.""" + + publication_name = publication_utils. \ + verify_publication(self.server, + self.db_name, + self.test_data['name']) + if not publication_name: + raise Exception("Could not find the publication to update.") + + self.test_data['id'] = self.publication_id + + if self.is_positive_test: + self.test_data['pubtable'] = '' + self.test_data['pubschema'] = \ + publication_utils.get_schemas(self) + response = self.update_publication(self.test_data) + + self.assertEqual(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + publication_utils.delete_publication(self.server, self.db_name, + self.test_data['name']) + + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_add_table.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_add_table.py new file mode 100644 index 000000000..de40f6717 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_add_table.py @@ -0,0 +1,116 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as publication_utils + + +class PublicationUpdateAddTableTestCase(BaseTestGenerator): + """This class will update the publication.""" + scenarios = utils.generate_scenarios('update_publication_add_table', + publication_utils.test_cases) + + def setUp(self): + super().setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + self.server_version = schema_info["server_version"] + self.schema_name = schema_info["schema_name"] + if self.server_version < 99999: + self.skipTest( + "Logical replication is not supported " + "for server version less than 10" + + ) + if self.server_version < 150000 and \ + hasattr(self, 'compatible_sversion'): + self.skipTest("The version is not compatible for " + "the current test case") + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to delete publication.") + + publication_utils.create_table_for_publication(self) + self.test_data['pubtable'] = publication_utils.get_tables(self) + self.test_data['name'] = "test_publication_add_%s" % ( + str(uuid.uuid4())[1:8]) + self.publication_id = publication_utils.create_publication(self) + + def update_publication(self, data): + return self.tester.put( + self.url + str(utils.SERVER_GROUP) + '/' + + str(self.server_id) + '/' + str( + self.db_id) + + '/' + str(self.publication_id), + data=json.dumps(data), + follow_redirects=True) + + def runTest(self): + """This function will update the publication.""" + + publication_name = publication_utils. \ + verify_publication(self.server, + self.db_name, + self.test_data['name']) + if not publication_name: + raise Exception("Could not find the publication to update.") + + self.test_data['id'] = self.publication_id + + if self.is_positive_test: + if hasattr(self, 'add_table'): + publication_utils.create_table_for_publication(self) + if self.server_version >= 150000: + self.test_data['pubtable'] = {'added': [ + {'table_name': self.schema_name + '.' + self.table_name + }]} + else: + self.test_data['pubtable'] = publication_utils.get_tables( + self) + if hasattr(self, 'add_table_with_where'): + publication_utils.create_table_for_publication(self) + self.test_data['pubtable'] = {'added': [ + {'table_name': self.schema_name + '.' + self.table_name, + 'where': 'id=2'}]} + if hasattr(self, 'add_table_with_columns'): + publication_utils.create_table_for_publication(self) + columns = publication_utils.get_all_columns(self) + self.test_data['pubtable'] = {'added': [ + {'table_name': self.schema_name + '.' + self.table_name, + "columns": [columns[0]['value'], columns[1]['value']]}]} + if hasattr(self, 'add_table_with_where_columns'): + publication_utils.create_table_for_publication(self) + columns = publication_utils.get_all_columns(self) + self.test_data['pubtable'] = {'added': [ + {'table_name': self.schema_name + '.' + self.table_name, + "columns": [columns[0]['value'], + columns[1]['value']], 'where': 'id=2'}]} + response = self.update_publication(self.test_data) + + self.assertEqual(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + publication_utils.delete_publication(self.server, self.db_name, + self.test_data['name']) + + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_drop_schema.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_drop_schema.py new file mode 100644 index 000000000..21f192879 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_drop_schema.py @@ -0,0 +1,100 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as publication_utils + + +class PublicationUpdateDropSchemaTestCase(BaseTestGenerator): + """This class will update the publication.""" + scenarios = utils.generate_scenarios('update_publication_drop_schema', + publication_utils.test_cases) + + def setUp(self): + super().setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + self.server_version = schema_info["server_version"] + if self.server_version < 99999: + self.skipTest( + "Logical replication is not supported " + "for server version less than 10" + + ) + if self.server_version < 150000 and \ + hasattr(self, 'compatible_sversion'): + self.skipTest("The version is not compatible for " + "the current test case") + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to delete publication.") + + self.schema_name = "schema_get_%s" % (str(uuid.uuid4())[1:8]) + connection = utils.get_db_connection(self.db_name, + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode']) + self.schema_details = schema_utils.create_schema(connection, + self.schema_name) + self.test_data['pubschema'] = publication_utils.get_schemas(self) + self.test_data['name'] = "test_publication_add_%s" % ( + str(uuid.uuid4())[1:8]) + self.publication_id = publication_utils.create_publication(self) + + def update_publication(self, data): + return self.tester.put( + self.url + str(utils.SERVER_GROUP) + '/' + + str(self.server_id) + '/' + str( + self.db_id) + + '/' + str(self.publication_id), + data=json.dumps(data), + follow_redirects=True) + + def runTest(self): + """This function will update the publication.""" + + publication_name = publication_utils. \ + verify_publication(self.server, + self.db_name, + self.test_data['name']) + if not publication_name: + raise Exception("Could not find the publication to update.") + + self.test_data['id'] = self.publication_id + + if self.is_positive_test: + schemas = publication_utils.get_schemas(self) + self.test_data['pubschema'] = [schemas[0]] + response = self.update_publication(self.test_data) + + self.assertEqual(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + publication_utils.delete_publication(self.server, self.db_name, + self.test_data['name']) + + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_drop_table.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_drop_table.py new file mode 100644 index 000000000..25cf30d0e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_drop_table.py @@ -0,0 +1,121 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as publication_utils + + +class PublicationUpdateDropTableTestCase(BaseTestGenerator): + """This class will update the publication.""" + scenarios = utils.generate_scenarios('update_publication_drop_table', + publication_utils.test_cases) + + def setUp(self): + super().setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + self.server_version = schema_info["server_version"] + self.schema_name = schema_info["schema_name"] + + if self.server_version < 99999: + self.skipTest( + "Logical replication is not supported " + "for server version less than 10" + + ) + if self.server_version < 150000 and \ + hasattr(self, 'compatible_sversion'): + self.skipTest("The version is not compatible for " + "the current test case") + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to delete publication.") + + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + if hasattr(self, 'drop_tables') or \ + hasattr(self, 'drop_tables_with_columns'): + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = \ + tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) + + self.test_data['pubtable'] = publication_utils.get_tables(self) + self.test_data['name'] = "test_publication_add_%s" % ( + str(uuid.uuid4())[1:8]) + self.publication_id = publication_utils.create_publication(self) + + def update_publication(self, data): + return self.tester.put( + self.url + str(utils.SERVER_GROUP) + '/' + + str(self.server_id) + '/' + str( + self.db_id) + + '/' + str(self.publication_id), + data=json.dumps(data), + follow_redirects=True) + + def runTest(self): + """This function will update the publication.""" + + publication_name = publication_utils. \ + verify_publication(self.server, + self.db_name, + self.test_data['name']) + if not publication_name: + raise Exception("Could not find the publication to update.") + + self.test_data['id'] = self.publication_id + + if self.is_positive_test: + if hasattr(self, 'drop_tables'): + if self.server_version >= 150000: + self.test_data['pubtable'] = {'deleted': [ + {'table_name': self.schema_name + '.' + self.table_name + }]} + else: + tables = publication_utils.get_tables(self) + self.test_data['pubtable'] = [tables[0]] + if hasattr(self, 'drop_tables_with_columns'): + columns = publication_utils.get_all_columns(self) + self.test_data['pubtable'] = {'deleted': [ + {'table_name': self.schema_name + '.' + self.table_name, + "columns": [columns[0]['value']]}]} + if hasattr(self, 'drop_tables_with_where'): + self.test_data['pubtable'] = {'deleted': [ + {'table_name': self.schema_name + '.' + self.table_name, + "where": 'id=2'}]} + response = self.update_publication(self.test_data) + + self.assertEqual(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + publication_utils.delete_publication(self.server, self.db_name, + self.test_data['name']) + + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_update_schema.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_update_schema.py new file mode 100644 index 000000000..4b2858d18 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_update_schema.py @@ -0,0 +1,101 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as publication_utils + + +class PublicationUpdateSchemaUpdateTestCase(BaseTestGenerator): + """This class will update the publication.""" + scenarios = utils.generate_scenarios('update_publication_update_schema', + publication_utils.test_cases) + + def setUp(self): + super().setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + self.server_version = schema_info["server_version"] + if self.server_version < 99999: + self.skipTest( + "Logical replication is not supported " + "for server version less than 10" + + ) + if self.server_version < 150000 and \ + hasattr(self, 'compatible_sversion'): + self.skipTest("The version is not compatible for " + "the current test case") + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to delete publication.") + + self.test_data['pubschema'] = publication_utils.get_schemas(self) + self.test_data['name'] = "test_publication_add_%s" % ( + str(uuid.uuid4())[1:8]) + self.publication_id = publication_utils.create_publication(self) + + def update_publication(self, data): + return self.tester.put( + self.url + str(utils.SERVER_GROUP) + '/' + + str(self.server_id) + '/' + str( + self.db_id) + + '/' + str(self.publication_id), + data=json.dumps(data), + follow_redirects=True) + + def runTest(self): + """This function will update the publication.""" + + publication_name = publication_utils. \ + verify_publication(self.server, + self.db_name, + self.test_data['name']) + if not publication_name: + raise Exception("Could not find the publication to update.") + + self.test_data['id'] = self.publication_id + + if self.is_positive_test: + self.schema_name = "schema_get_%s" % (str(uuid.uuid4())[1:8]) + connection = \ + utils.get_db_connection(self.db_name, + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode']) + self.schema_details = \ + schema_utils.create_schema(connection, self.schema_name) + self.test_data['pubschema'] = \ + publication_utils.get_schemas(self) + response = self.update_publication(self.test_data) + + self.assertEqual(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + publication_utils.delete_publication(self.server, self.db_name, + self.test_data['name']) + + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_update_table.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_update_table.py new file mode 100644 index 000000000..45834d55c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put_update_table.py @@ -0,0 +1,184 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as publication_utils + + +class PublicationUpdateTableUpdateTestCase(BaseTestGenerator): + """This class will update the publication.""" + scenarios = utils.generate_scenarios('update_publication_update_table', + publication_utils.test_cases) + + def setUp(self): + super().setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + self.server_version = schema_info["server_version"] + self.schema_name = schema_info["schema_name"] + + if self.server_version < 99999: + self.skipTest( + "Logical replication is not supported " + "for server version less than 10" + + ) + if self.server_version < 150000 and \ + hasattr(self, 'compatible_sversion'): + self.skipTest("The version is not compatible for " + "the current test case") + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to delete publication.") + + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + self.test_data['pubtable'] = publication_utils.get_tables(self) + self.test_data['pubschema'] = publication_utils.get_schemas(self) + self.test_data['name'] = "test_publication_add_%s" % ( + str(uuid.uuid4())[1:8]) + self.publication_id = publication_utils.create_publication(self) + + def update_publication(self, data): + return self.tester.put( + self.url + str(utils.SERVER_GROUP) + '/' + + str(self.server_id) + '/' + str( + self.db_id) + + '/' + str(self.publication_id), + data=json.dumps(data), + follow_redirects=True) + + def runTest(self): + """This function will update the publication.""" + + publication_name = publication_utils. \ + verify_publication(self.server, + self.db_name, + self.test_data['name']) + if not publication_name: + raise Exception("Could not find the publication to update.") + + self.test_data['id'] = self.publication_id + + if self.is_positive_test: + if hasattr(self, 'update_table_with_columns'): + tables = publication_utils.get_tables(self) + columns = publication_utils.get_all_columns(self) + self.test_data['pubtable'] = {'changed': [ + {'table_name': tables[0]['table_name'], + "columns": [columns[0]['value']]}]} + if hasattr(self, 'update_table_with_where'): + tables = publication_utils.get_tables(self) + self.test_data['pubtable'] = {'changed': [ + {'table_name': tables[0]['table_name'], + 'where': 'id=2'}]} + if hasattr(self, 'update_table_with_where_columns'): + tables = publication_utils.get_tables(self) + columns = publication_utils.get_all_columns(self) + self.test_data['pubtable'] = {'changed': [ + {'table_name': tables[0]['table_name'], + "columns": [columns[0]['value'], + columns[1]['value']], 'where': 'id=2'}]} + if hasattr(self, 'update_old_new_tables_with_columns'): + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) + tables = publication_utils.get_tables(self) + columns = publication_utils.get_all_columns(self) + self.test_data['pubtable'] = {'added': [ + {'table_name': self.schema_name + '.' + self.table_name}], + 'changed': [ + {'table_name': tables[0]['table_name'], + "columns": [columns[0]['value'], + columns[1]['value']]}] + } + if hasattr(self, 'update_old_new_tables_with_where'): + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) + tables = publication_utils.get_tables(self) + self.test_data['pubtable'] = {'added': [ + {'table_name': self.schema_name + '.' + self.table_name}], + 'changed': [ + {'table_name': tables[0]['table_name'], 'where': 'id=2'}] + } + if hasattr(self, 'update_old_new_tables_with_columns_where'): + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) + tables = publication_utils.get_tables(self) + columns = publication_utils.get_all_columns(self) + self.test_data['pubtable'] = {'added': [ + {'table_name': self.schema_name + '.' + self.table_name}], + 'changed': [ + {'table_name': tables[0]['table_name'], + "columns": [columns[0]['value'], + columns[1]['value']], 'where': 'id=2'}] + } + if hasattr(self, 'update_old_new_tables_with_where_schema'): + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) + tables = publication_utils.get_tables(self) + self.test_data['pubtable'] = {'added': [ + {'table_name': self.schema_name + '.' + self.table_name}], + 'changed': [ + {'table_name': tables[0]['table_name'], + 'where': 'id=2'}] + } + self.schema_name = "schema_get_%s" % (str(uuid.uuid4())[1:8]) + connection = \ + utils.get_db_connection(self.db_name, + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode']) + self.schema_details = \ + schema_utils.create_schema(connection, self.schema_name) + self.test_data['pubschema'] = \ + publication_utils.get_schemas(self) + response = self.update_publication(self.test_data) + + self.assertEqual(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + publication_utils.delete_publication(self.server, self.db_name, + self.test_data['name']) + + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_sql.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_sql.py index fac40fdd1..7c5aa1f7c 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_sql.py +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_sql.py @@ -59,8 +59,7 @@ class PublicationGetTestCase(BaseTestGenerator): self.publication_name = "test_publication_delete_%s" % ( str(uuid.uuid4())[1:8]) self.publication_id = \ - publication_utils.create_publication(self.server, self.db_name, - self.publication_name) + publication_utils.create_publication(self) def get_sql(self): return self.tester.get( diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/utils.py index 794dcd5e3..254a7ed2f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/utils.py @@ -12,14 +12,25 @@ import sys import os import json import traceback +import uuid from regression.python_test_utils import test_utils as utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) with open(CURRENT_PATH + "/publication_test_data.json") as data_file: test_cases = json.load(data_file) +def create_table_for_publication(self): + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) + + def get_tables(self): tables = self.tester.get( '/browser/publication/get_tables/' + str( @@ -27,7 +38,48 @@ def get_tables(self): self.server_id) + '/' + str(self.db_id) + '/', content_type='html/json') - return [tables.json['data'][0]['value']] + if self.server_version >= 150000: + columns = get_all_columns(self) + if hasattr(self, 'with_columns'): + return [{"table_name": tables.json['data'][0]['value'], + "columns": [columns[0]['value'], + columns[1]['value']] + }] + elif hasattr(self, 'with_where'): + return [{"table_name": tables.json['data'][0]['value'], + "where": 'id=5' + }] + elif hasattr(self, 'with_columns') and hasattr(self, 'with_where'): + return [{"table_name": tables.json['data'][0]['value'], + "columns": [columns[0]['value'], + columns[1]['value']], + "where": 'id=5' + }] + else: + return [{"table_name": i['value']} for i in tables.json['data']] + else: + + return [i['value'] for i in tables.json['data']] + + +def get_all_columns(self): + columns = self.tester.get( + '/browser/publication/get_all_columns/' + str( + utils.SERVER_GROUP) + '/' + str( + self.server_id) + '/' + + str(self.db_id) + '/' + '?tid=' + str(self.table_id), + content_type='html/json') + return columns.json['data'] + + +def get_schemas(self): + schemas = self.tester.get( + '/browser/publication/get_schemas/' + str( + utils.SERVER_GROUP) + '/' + str( + self.server_id) + '/' + + str(self.db_id) + '/', + content_type='html/json') + return [i['value'] for i in schemas.json['data']] def create_publication_api(self): @@ -39,7 +91,7 @@ def create_publication_api(self): content_type='html/json') -def create_publication(server, db_name, publication_name): +def create_publication(self, publication_name=None): """ This function creates a publication under provided table. :param server: server details @@ -52,23 +104,47 @@ def create_publication(server, db_name, publication_name): :rtype: int """ try: - connection = utils.get_db_connection(db_name, - server['username'], - server['db_password'], - server['host'], - server['port'], - server['sslmode']) + connection = utils.get_db_connection(self.db_name, + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode']) old_isolation_level = connection.isolation_level utils.set_isolation_level(connection, 0) pg_cursor = connection.cursor() - query = "CREATE publication %s FOR ALL TABLES" % \ - (publication_name) - pg_cursor.execute(query) + if not hasattr(self, "test_data"): + if publication_name: + query = "CREATE publication %s FOR ALL TABLES" % \ + (publication_name) + else: + query = "CREATE publication %s FOR ALL TABLES" % \ + (self.publication_name) + pg_cursor.execute(query) + else: + self.tester.post( + self.url + str(utils.SERVER_GROUP) + '/' + + str(self.server_id) + '/' + str( + self.db_id) + '/', + data=json.dumps(self.test_data), + content_type='html/json') + utils.set_isolation_level(connection, old_isolation_level) connection.commit() # Get role oid of newly added publication - pg_cursor.execute("select oid from pg_catalog.pg_publication pub " - "where pub.pubname='%s'" % publication_name) + if not hasattr(self, "test_data"): + if publication_name: + pg_cursor.execute( + "select oid from pg_catalog.pg_publication pub " + "where pub.pubname='%s'" % publication_name) + else: + pg_cursor.execute( + "select oid from pg_catalog.pg_publication pub " + "where pub.pubname='%s'" % self.publication_name) + else: + pg_cursor.execute("select oid from pg_catalog.pg_publication pub " + "where pub.pubname='%s'" + % self.test_data['name']) publication = pg_cursor.fetchone() publication_id = '' if publication: diff --git a/web/pgadmin/static/js/components/QueryThresholds.jsx b/web/pgadmin/static/js/components/QueryThresholds.jsx index bbfaebf18..3cb207fa9 100644 --- a/web/pgadmin/static/js/components/QueryThresholds.jsx +++ b/web/pgadmin/static/js/components/QueryThresholds.jsx @@ -76,7 +76,7 @@ export default function QueryThresholds({ value, onChange }) { - {gettext('(in minuts)')} + {gettext('(in minutes)')} diff --git a/web/regression/javascript/schema_ui_files/publication.ui.spec.js b/web/regression/javascript/schema_ui_files/publication.ui.spec.js index 765582c3f..8eea694d3 100644 --- a/web/regression/javascript/schema_ui_files/publication.ui.spec.js +++ b/web/regression/javascript/schema_ui_files/publication.ui.spec.js @@ -16,7 +16,9 @@ describe('PublicationSchema', ()=>{ let mount; let schemaObj = new PublicationSchema( { - publicationTable: ()=>[], + allTables: ()=>[], + allSchemas:()=>[], + getColumns: ()=>[], role: ()=>[], }, { @@ -69,5 +71,11 @@ describe('PublicationSchema', ()=>{ expect(status).toBe(true); }); + it('pubschema disabled', ()=>{ + let disabled = _.find(schemaObj.fields, (f)=>f.id=='pubschema').disabled; + let status = disabled({pubtable: [],all_table: true}); + expect(status).toBe(true); + }); + });