From d4cdaa5b6491a37ad603cd32150599d9b117c5a3 Mon Sep 17 00:00:00 2001 From: yogeshmahajan-1903 Date: Mon, 4 Sep 2023 18:10:11 +0530 Subject: [PATCH] Implement column module and its features for foreign table.#640, #6373, #6674. --- docs/en_US/images/foreign_table_columns.png | Bin 65389 -> 90274 bytes .../schemas/foreign_tables/__init__.py | 123 +++++++-- .../foreign_tables/children/__init__.py | 5 +- .../foreign_table_columns/__init__.py | 97 +++++++ .../static/img/coll-foreign_table_column.svg | 1 + .../static/img/foreign_table_column.svg | 1 + .../static/js/foreign_table_column.js | 70 ++++++ ...amp_with_default_value_using_function.msql | 8 + ...tamp_with_default_value_using_function.sql | 9 + ...erated_always_column_option_variables.msql | 5 + ...nerated_always_column_option_variables.sql | 13 + ...amp_with_default_value_using_function.msql | 5 + ...tamp_with_default_value_using_function.sql | 9 + ...erated_always_column_option_variables.msql | 9 + ...nerated_always_column_option_variables.sql | 13 + .../tests/12_plus/test.json | 204 +++++++++++++++ .../foreign_table_columns/tests/__init__.py | 16 ++ .../tests/column_test_data.json | 154 ++++++++++++ .../tests/default/alter_column_numeric.msql | 8 + .../tests/default/alter_column_numeric.sql | 9 + .../alter_column_text_with_default_value.msql | 2 + .../alter_column_text_with_default_value.sql | 9 + .../tests/default/create_column_numeric.msql | 5 + .../tests/default/create_column_numeric.sql | 9 + ...create_column_text_with_default_value.msql | 5 + .../create_column_text_with_default_value.sql | 9 + .../tests/default/test.json | 129 ++++++++++ .../tests/test_ft_column_add.py | 111 ++++++++ .../tests/test_ft_column_delete.py | 132 ++++++++++ .../tests/test_ft_column_get.py | 125 +++++++++ .../foreign_table_columns/tests/utils.py | 203 +++++++++++++++ .../foreign_tables/static/js/foreign_table.js | 54 +++- .../static/js/foreign_table.ui.js | 238 ++++++++++++++---- .../sql/default/create.sql | 39 +++ .../sql/default/delete.sql | 1 + .../sql/default/update.sql | 110 ++++++++ .../foreign_tables/sql/default/create.sql | 6 +- .../sql/default/edit_mode_types_multi.sql | 13 + .../sql/default/enable_disable_trigger.sql | 3 + .../sql/default/foreign_table_schema_diff.sql | 2 +- .../sql/default/get_columns.sql | 11 +- .../sql/default/get_enabled_triggers.sql | 1 + .../sql/default/get_table_columns.sql | 2 +- .../foreign_tables/sql/default/node.sql | 4 +- .../foreign_tables/sql/default/update.sql | 23 +- .../tests/foreign_tables_test_data.json | 20 +- .../tests/pg/default/test_foreign_table.json | 30 +-- .../ppas/default/test_foreign_table.json | 24 +- .../schemas/tables/columns/__init__.py | 38 ++- .../databases/schemas/tables/columns/utils.py | 38 +++ .../columns/sql/12_plus/properties.sql | 2 +- .../columns/sql/default/properties.sql | 2 +- .../schema_ui_files/foreign_table.ui.spec.js | 14 -- web/regression/re_sql/tests/test_resql.py | 2 + web/webpack.config.js | 1 + web/webpack.shim.js | 1 + 56 files changed, 2023 insertions(+), 154 deletions(-) create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/img/coll-foreign_table_column.svg create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/img/foreign_table_column.svg create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/js/foreign_table_column.js create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_timestamp_with_default_value_using_function.msql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_timestamp_with_default_value_using_function.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_with_integer_generated_always_column_option_variables.msql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_with_integer_generated_always_column_option_variables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_timestamp_with_default_value_using_function.msql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_timestamp_with_default_value_using_function.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_with_integer_generated_always_column_option_variables.msql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_with_integer_generated_always_column_option_variables.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/test.json create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/__init__.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/column_test_data.json create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_numeric.msql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_numeric.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_text_with_default_value.msql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_text_with_default_value.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_numeric.msql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_numeric.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_text_with_default_value.msql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_text_with_default_value.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/test.json create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_add.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_delete.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_get.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/utils.py create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/create.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/delete.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/update.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/edit_mode_types_multi.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/enable_disable_trigger.sql create mode 100644 web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_enabled_triggers.sql diff --git a/docs/en_US/images/foreign_table_columns.png b/docs/en_US/images/foreign_table_columns.png index 0a3d6381e6191e64e6832b852e2ccc7f8bfecdb3..5ca083f5a0ac5fe451c82a40c37049a66df66f9d 100644 GIT binary patch literal 90274 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�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 Ou*pa$idTsm1^r*A#ky<& diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/__init__.py index 081b8c6ab41..0f96e51bc4b 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/__init__.py @@ -29,10 +29,11 @@ from pgadmin.browser.utils import PGChildNodeView from pgadmin.utils.ajax import make_json_response, internal_server_error, \ make_response as ajax_response, gone -from pgadmin.utils.compile_template_name import compile_template_path from pgadmin.utils.driver import get_driver from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare +from pgadmin.browser.server_groups.servers.databases.schemas.tables.\ + columns import utils as column_utils class ForeignTableModule(SchemaChildModule): @@ -88,6 +89,10 @@ def register(self, app, options): from pgadmin.browser.server_groups.servers.databases.schemas.tables.\ constraints import blueprint as module self.submodules.append(module) + from pgadmin.browser.server_groups.servers.databases.schemas. \ + foreign_tables.foreign_table_columns import \ + foreign_table_column_blueprint as module + self.submodules.append(module) super().register(app, options) @@ -216,6 +221,7 @@ class ForeignTableView(PGChildNodeView) 'get_foreign_servers': [{'get': 'get_foreign_servers'}, {'get': 'get_foreign_servers'}], 'get_tables': [{'get': 'get_tables'}, {'get': 'get_tables'}], + 'set_trigger': [{'put': 'enable_disable_triggers'}], 'get_columns': [{'get': 'get_columns'}, {'get': 'get_columns'}], 'select_sql': [{'get': 'select_sql'}], 'insert_sql': [{'get': 'insert_sql'}], @@ -451,7 +457,9 @@ def nodes(self, gid, sid, did, scid): scid, row['name'], icon="icon-foreign_table", - description=row['description'] + description=row['description'], + tigger_count=row['triggercount'], + has_enable_triggers=row['has_enable_triggers'], )) return make_json_response( @@ -1160,15 +1168,14 @@ def _format_columns(self, columns): """ cols = [] for c in columns: - if len(c) > 0: - if '[]' in c['datatype']: - c['datatype'] = c['datatype'].replace('[]', '') + if len(c) > 0 and 'cltype' in c: + if '[]' in c['cltype']: + c['cltype'] = c['cltype'].replace('[]', '') c['isArrayType'] = True else: c['isArrayType'] = False cols.append(c) - - return cols + return cols if cols else columns def _fetch_properties(self, gid, sid, did, scid, foid, inherits=False, ): """ @@ -1238,18 +1245,44 @@ def _fetch_properties(self, gid, sid, did, scid, foid, inherits=False, ): if not status: return False, internal_server_error(errormsg=cols) - self._get_datatype_precision(cols) + # Fetch length and precision data + for col in cols['rows']: + column_utils.fetch_length_precision(col) + + self._get_edit_types(cols['rows']) if cols and 'rows' in cols: data['columns'] = cols['rows'] # Get Inherited table names from their OID is_error, errmsg = self._get_inherited_table_name(data, inherits) + if is_error: return False, internal_server_error(errormsg=errmsg) return True, data + def _get_edit_types(self, cols): + edit_types = {} + for col in cols: + edit_types[col['atttypid']] = [] + + if len(cols) > 0: + SQL = render_template("/".join([self.template_path, + 'edit_mode_types_multi.sql']), + type_ids=",".join(map(lambda x: str(x), + edit_types.keys()))) + status, res = self.conn.execute_2darray(SQL) + for row in res['rows']: + edit_types[row['main_oid']] = sorted(row['edit_types']) + + for column in cols: + edit_type_list = edit_types[column['atttypid']] + edit_type_list.append(column['fulltype']) + column['edit_types'] = sorted(edit_type_list) + column['cltype'] = \ + DataTypeReader.parse_type_name(column['cltype']) + def _get_datatype_precision(self, cols): """ The Length and the precision of the Datatype should be separated. @@ -1262,11 +1295,11 @@ def _get_datatype_precision(self, cols): substr = self.extract_type_length_precision(c) typlen = substr.split(",") if len(typlen) > 1: - c['typlen'] = self.convert_typlen_to_int(typlen) - c['precision'] = self.convert_precision_to_int(typlen) + c['attlen'] = self.convert_typlen_to_int(typlen) + c['attprecision'] = self.convert_precision_to_int(typlen) else: - c['typlen'] = self.convert_typlen_to_int(typlen) - c['precision'] = None + c['attlen'] = self.convert_typlen_to_int(typlen) + c['attprecision'] = None # Get formatted Column Options if 'attfdwoptions' in c and c['attfdwoptions'] != '': @@ -1393,7 +1426,7 @@ def select_sql(self, gid, sid, did, scid, foid): columns = [] for c in data['columns']: - columns.append(self.qtIdent(self.conn, c['attname'])) + columns.append(self.qtIdent(self.conn, c['name'])) if len(columns) > 0: columns = ", ".join(columns) @@ -1434,7 +1467,7 @@ def insert_sql(self, gid, sid, did, scid, foid): # Now we have all list of columns which we need if 'columns' in data: for c in data['columns']: - columns.append(self.qtIdent(self.conn, c['attname'])) + columns.append(self.qtIdent(self.conn, c['name'])) values.append('?') if len(columns) > 0: @@ -1475,7 +1508,7 @@ def update_sql(self, gid, sid, did, scid, foid): # Now we have all list of columns which we need if 'columns' in data: for c in data['columns']: - columns.append(self.qtIdent(self.conn, c['attname'])) + columns.append(self.qtIdent(self.conn, c['name'])) if len(columns) > 0: if len(columns) == 1: @@ -1676,6 +1709,66 @@ def modify_data_for_schema_diff(self, data, old_data): data['ftoptions'] = tmp_ftoptions + @check_precondition + def enable_disable_triggers(self, gid, sid, did, scid, foid): + """ + This function will enable/disable trigger(s) on the table object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + # Below will decide if it's simple drop or drop with cascade call + data = request.form if request.form else json.loads( + request.data + ) + # Convert str 'true' to boolean type + is_enable_trigger = data['is_enable_trigger'] + + try: + if foid is not None: + status, data = self._fetch_properties( + gid, sid, did, scid, foid, inherits=True) + if not status: + return data + elif not data: + return gone(self.not_found_error_msg()) + + SQL = render_template( + "/".join([self.template_path, 'enable_disable_trigger.sql']), + data=data, is_enable_trigger=is_enable_trigger + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + SQL = render_template( + "/".join([self.template_path, 'get_enabled_triggers.sql']), + tid=foid + ) + + status, trigger_res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Trigger(s) have been disabled") + if is_enable_trigger == 'D' + else gettext("Trigger(s) have been enabled"), + data={ + 'id': foid, + 'scid': scid, + 'has_enable_triggers': trigger_res + } + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + SchemaDiffRegistry(blueprint.node_type, ForeignTableView) ForeignTableView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/children/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/children/__init__.py index d165c500b70..fdc59156324 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/children/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/children/__init__.py @@ -5,9 +5,10 @@ Do not remove these imports as they will be automatically imported by the view module as its children """ -from pgadmin.browser.server_groups.servers.databases.schemas.tables.columns \ - import blueprint as columns_module from pgadmin.browser.server_groups.servers.databases.schemas.tables.triggers \ import blueprint as triggers_modules from pgadmin.browser.server_groups.servers.databases.schemas.tables.\ constraints import blueprint as constraints_modules +from pgadmin.browser.server_groups.servers.databases.schemas.\ + foreign_tables.foreign_table_columns import foreign_table_column_blueprint\ + as foreign_tables_columns_modules diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/__init__.py new file mode 100644 index 00000000000..588c0a3c082 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/__init__.py @@ -0,0 +1,97 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Column Node for foreign table """ +import pgadmin.browser.server_groups.servers.databases as database +from flask_babel import gettext +from pgadmin.browser.collection import CollectionNodeModule + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.columns \ + import ColumnsView + + +class ForeignTableColumnsModule(CollectionNodeModule): + """ + class ColumnsModule(CollectionNodeModule) + + A module class for Column node derived from CollectionNodeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the Column and it's base module. + + * get_nodes(gid, sid, did, scid, tid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for schema, when any of the server node is + initialized. + """ + + _NODE_TYPE = 'foreign_table_column' + _COLLECTION_LABEL = gettext("Columns") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the ColumnModule and it's base module. + + Args: + *args: + **kwargs: + """ + self.min_ver = None + self.max_ver = None + super().__init__(*args, **kwargs) + + def get_nodes(self, gid, sid, did, scid, foid, **kwargs): + """ + Generate the collection node + """ + if self.has_nodes( + sid, did, scid=scid, tid=foid, + base_template_path=ForeignTableColumnsView.BASE_TEMPLATE_PATH): + yield self.generate_browser_collection_node(foid) + + @property + def script_load(self): + """ + Load the module script for server, when any of the server-group node is + initialized. + """ + return database.DatabaseModule.node_type + + @property + def node_inode(self): + """ + Load the module node as a leaf node + """ + return False + + @property + def module_use_template_javascript(self): + """ + Returns whether Jinja2 template is used for generating the javascript + module. + """ + return False + + +foreign_table_column_blueprint = ForeignTableColumnsModule(__name__) + + +class ForeignTableColumnsView(ColumnsView): + node_type = foreign_table_column_blueprint.node_type + node_label = "Column" + + +ForeignTableColumnsView.register_node_view(foreign_table_column_blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/img/coll-foreign_table_column.svg b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/img/coll-foreign_table_column.svg new file mode 100644 index 00000000000..7688f535569 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/img/coll-foreign_table_column.svg @@ -0,0 +1 @@ +coll-column \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/img/foreign_table_column.svg b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/img/foreign_table_column.svg new file mode 100644 index 00000000000..5afb75a9962 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/img/foreign_table_column.svg @@ -0,0 +1 @@ +column diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/js/foreign_table_column.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/js/foreign_table_column.js new file mode 100644 index 00000000000..d160fc7dee0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/js/foreign_table_column.js @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2023, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import { getNodeColumnSchema } from '../../../static/js/foreign_table.ui'; + +define('pgadmin.node.foreign_table_column', [ + 'sources/gettext', 'sources/url_for', 'pgadmin.browser', + 'pgadmin.node.schema.dir/schema_child_tree_node', 'pgadmin.browser.collection', +], function( + gettext, url_for, pgBrowser +) { + + if (!pgBrowser.Nodes['coll-foreign_table_column']) { + pgBrowser.Nodes['coll-foreign_table_column'] = + pgBrowser.Collection.extend({ + node: 'foreign_table_column', + label: gettext('Columns'), + type: 'coll-foreign_table_column', + columns: ['name', 'cltype', 'description'] + }); + } + + if (!pgBrowser.Nodes['foreign_table_column']) { + pgBrowser.Nodes['foreign_table_column'] = pgBrowser.Node.extend({ + // Foreign table is added in parent_type to support triggers on + // foreign table where we need column information. + parent_type: ['foreign_table'], + collection_type: ['coll-foreign_table'], + type: 'foreign_table_column', + label: gettext('Column'), + hasSQL: true, + sqlAlterHelp: 'sql-altertable.html', + sqlCreateHelp: 'sql-altertable.html', + dialogHelp: url_for('help.static', {'filename': 'column_dialog.html'}), + canDrop: true, + canDropCascade: false, + hasDepends: true, + hasStatistics: true, + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + pgBrowser.add_menus([{ + name: 'create_column_on_coll', node: 'coll-foreign_table_column', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: gettext('Column...'), + data: {action: 'create'}, enable: 'canCreate', + },{ + name: 'create_column_onTable', node: 'foreign_table', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: gettext('Column...'), + data: {action: 'create'}, enable: 'canCreate', + }, + ]); + }, + getSchema: function(treeNodeInfo, itemNodeData) { + return getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser); + }, + }); + } + + return pgBrowser.Nodes['foreign_table_column']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_timestamp_with_default_value_using_function.msql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_timestamp_with_default_value_using_function.msql new file mode 100644 index 00000000000..0f55e375c9b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_timestamp_with_default_value_using_function.msql @@ -0,0 +1,8 @@ +ALTER FOREIGN TABLE public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "col_3_$%{}[]()&*^!@""'`\/#" TYPE time(10) with time zone ; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "col_3_$%{}[]()&*^!@""'`\/#" DROP NOT NULL; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_3_$%{}[]()&*^!@""'`\/#" + IS 'test comment modification'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_timestamp_with_default_value_using_function.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_timestamp_with_default_value_using_function.sql new file mode 100644 index 00000000000..0849eb175ce --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_timestamp_with_default_value_using_function.sql @@ -0,0 +1,9 @@ +-- Column: public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_3_$%{}[]()&*^!@""'`\/#" + +-- ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" DROP COLUMN IF EXISTS "col_3_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_3_$%{}[]()&*^!@""'`\/#" time(6) with time zone DEFAULT now(); + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_3_$%{}[]()&*^!@""'`\/#" + IS 'test comment modification'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_with_integer_generated_always_column_option_variables.msql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_with_integer_generated_always_column_option_variables.msql new file mode 100644 index 00000000000..e098ac4e275 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_with_integer_generated_always_column_option_variables.msql @@ -0,0 +1,5 @@ +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "col_4_$%{}[]()&*^!@""'`\/#" OPTIONS (SET column_name 'test_options_update'); +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "col_4_$%{}[]()&*^!@""'`\/#" + SET (n_distinct=111); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_with_integer_generated_always_column_option_variables.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_with_integer_generated_always_column_option_variables.sql new file mode 100644 index 00000000000..0be30f1e9d5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/alter_column_with_integer_generated_always_column_option_variables.sql @@ -0,0 +1,13 @@ +-- Column: public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_4_$%{}[]()&*^!@""'`\/#" + +-- ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" DROP COLUMN IF EXISTS "col_4_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_4_$%{}[]()&*^!@""'`\/#" bigint OPTIONS (column_name 'test_options_update') NOT NULL GENERATED ALWAYS AS ((1000 + 1)) STORED; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_4_$%{}[]()&*^!@""'`\/#" + IS 'test comment'; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "col_4_$%{}[]()&*^!@""'`\/#" + SET (n_distinct=111); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_timestamp_with_default_value_using_function.msql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_timestamp_with_default_value_using_function.msql new file mode 100644 index 00000000000..2580ab433a0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_timestamp_with_default_value_using_function.msql @@ -0,0 +1,5 @@ +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_3_$%{}[]()&*^!@""'`\/#" time with time zone NOT NULL DEFAULT now(); + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_3_$%{}[]()&*^!@""'`\/#" + IS 'test comment'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_timestamp_with_default_value_using_function.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_timestamp_with_default_value_using_function.sql new file mode 100644 index 00000000000..5896d67d2a0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_timestamp_with_default_value_using_function.sql @@ -0,0 +1,9 @@ +-- Column: public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_3_$%{}[]()&*^!@""'`\/#" + +-- ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" DROP COLUMN IF EXISTS "col_3_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_3_$%{}[]()&*^!@""'`\/#" time with time zone NOT NULL DEFAULT now(); + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_3_$%{}[]()&*^!@""'`\/#" + IS 'test comment'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_with_integer_generated_always_column_option_variables.msql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_with_integer_generated_always_column_option_variables.msql new file mode 100644 index 00000000000..0dd1febd841 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_with_integer_generated_always_column_option_variables.msql @@ -0,0 +1,9 @@ +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_4_$%{}[]()&*^!@""'`\/#" bigint OPTIONS (column_name 'test_options') NOT NULL GENERATED ALWAYS AS (1000+1) STORED; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_4_$%{}[]()&*^!@""'`\/#" + IS 'test comment'; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "col_4_$%{}[]()&*^!@""'`\/#" + SET (n_distinct=1); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_with_integer_generated_always_column_option_variables.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_with_integer_generated_always_column_option_variables.sql new file mode 100644 index 00000000000..274029a8564 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/create_column_with_integer_generated_always_column_option_variables.sql @@ -0,0 +1,13 @@ +-- Column: public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_4_$%{}[]()&*^!@""'`\/#" + +-- ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" DROP COLUMN IF EXISTS "col_4_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_4_$%{}[]()&*^!@""'`\/#" bigint OPTIONS (column_name 'test_options') NOT NULL GENERATED ALWAYS AS ((1000 + 1)) STORED; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_4_$%{}[]()&*^!@""'`\/#" + IS 'test comment'; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "col_4_$%{}[]()&*^!@""'`\/#" + SET (n_distinct=1); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/test.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/test.json new file mode 100644 index 00000000000..a6704bd3d77 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/12_plus/test.json @@ -0,0 +1,204 @@ +{ + "scenarios": [ + { + "type": "create", + "name": "Create FDW for foreign table", + "endpoint": "NODE-foreign_data_wrapper.obj", + "sql_endpoint": "NODE-foreign_data_wrapper.sql_id", + "data": { + "name": "test_fdw_for_foreign_table_columns", + "fdwacl": [], + "fdwoptions": [] + }, + "store_object_id": "True" + }, + { + "type": "create", + "name": "Create foreign server for foreign table", + "endpoint": "NODE-foreign_server.obj", + "sql_endpoint": "NODE-foreign_server.sql_id", + "data": { + "name":"test_fs_for_foreign_table_column" + }, + "store_object_id": "True" + }, + { + "type": "create", + "name": "Create Foreign Table for testing column node.", + "endpoint": "NODE-foreign_table.obj", + "sql_endpoint": "NODE-foreign_table.sql_id", + "data": { + "name": "foreign_table_2_$%{}[]()&*^!@\"'`\\/#", + "columns": [], + "schema": "public", + "basensp": "public", + "ftsrvname": "test_fs_for_foreign_table_column", + "owner": "postgres", + "ftoptions": [], + "inherits": [], + "relacl": [], + "seclabels": [] + }, + "store_object_id": "True" + }, + { + "type": "create", + "name": "Create FT Column (Integer/Numeric type)", + "endpoint": "NODE-foreign_table_column.obj", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql", + "data": { + "name": "col_1_$%{}[]()&*^!@\"'`\\/#", + "description": "Comment for create", + "cltype": "numeric", + "attlen":"10", + "attprecision":"5", + "coloptions":[], + "colconstype":"n", + "attnotnull": true, + "attoptions": [], + "seclabels": [], + "defval": "1" + }, + "expected_sql_file": "create_column_numeric.sql", + "expected_msql_file": "create_column_numeric.msql" + }, + { + "type": "alter", + "name": "Alter FT Column (Integer/Numeric type)", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql_id", + "data": { + "attnum": 1, + "name": "new_col_1_$%{}[]()&*^!@\"'`\\/#", + "description": "Comment for alter", + "cltype": "real" + }, + "expected_sql_file": "alter_column_numeric.sql", + "expected_msql_file": "alter_column_numeric.msql" + }, + { + "type": "delete", + "name": "Drop FT Column (Integer/Numeric type)", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "data": { + "name": "new_col_1_$%{}[]()&*^!@\"'`\\/#" + } + }, + { + "type": "create", + "name": "Create FT Column with text & default value", + "endpoint": "NODE-foreign_table_column.obj", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql", + "data": { + "name": "col_2_$%{}[]()&*^!@\"'`\\/#", + "cltype": "text", + "description": "test comment", + "attnotnull": true, + "colconstype": "n", + "defval": "'xyz'" + }, + "expected_sql_file": "create_column_text_with_default_value.sql", + "expected_msql_file": "create_column_text_with_default_value.msql" + }, + { + "type": "alter", + "name": "Alter FT Column with text & update default value", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql_id", + "data": { + "attnum": 2, + "defval": "'changed default value'" + }, + "expected_sql_file": "alter_column_text_with_default_value.sql", + "expected_msql_file": "alter_column_text_with_default_value.msql" + }, + { + "type": "delete", + "name": "Drop FT column with text & update default value", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "data": { + "name": "col_2_$%{}[]()&*^!@\"'`\\/#" + } + }, + { + "type": "create", + "name": "Create FT Column with time with time zone & default value using function", + "endpoint": "NODE-foreign_table_column.obj", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql", + "data": { + "name": "col_3_$%{}[]()&*^!@\"'`\\/#", + "cltype": "time with time zone", + "attacl": [], + "description": "test comment", + "attnotnull": true, + "colconstype": "n", + "defval": "now()" + }, + "expected_sql_file": "create_column_timestamp_with_default_value_using_function.sql", + "expected_msql_file": "create_column_timestamp_with_default_value_using_function.msql" + }, + { + "type": "alter", + "name": "Alter FT Column with time with time zone & update length", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql_id", + "data": { + "attlen": "10", + "attnotnull": false, + "attnum":3, + "description": "test comment modification" + }, + "expected_sql_file": "alter_column_timestamp_with_default_value_using_function.sql", + "expected_msql_file": "alter_column_timestamp_with_default_value_using_function.msql" + }, + { + "type": "delete", + "name": "Drop FT Column with time with time zone", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "data": { + "name": "col_3_$%{}[]()&*^!@\"'`\\/" + } + }, + { + "type": "create", + "name": "Create FT Column with integer & generated always with expression, column option, variables", + "endpoint": "NODE-foreign_table_column.obj", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql", + "data": {"name":"col_4_$%{}[]()&*^!@\"'`\\/#","description":"test comment","cltype":"bigint","attnotnull":true,"coloptions":[{"option":"column_name","value":"test_options"}],"colconstype":"g","is_tlength":false,"is_precision":false,"genexpr":"1000+1","attoptions":[{"name":"n_distinct","value":"1"}]}, + "expected_sql_file": "create_column_with_integer_generated_always_column_option_variables.sql", + "expected_msql_file": "create_column_with_integer_generated_always_column_option_variables.msql" + }, + { + "type": "alter", + "name": "Alter FT Column with integer & generated always with expression, column option, variables", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql_id", + "data":{ + "attoptions":{ + "changed":[{"name":"n_distinct","value":"111"}]}, + "coloptions":{"changed":[{"option":"column_name","value":"test_options_update"}]}, + "attnum":4}, + "expected_sql_file": "alter_column_with_integer_generated_always_column_option_variables.sql", + "expected_msql_file": "alter_column_with_integer_generated_always_column_option_variables.msql" + }, + { + "type": "delete", + "name": "Drop FT Column with time with time zone", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_columnn.sql_id", + "data": { + "name":"col_4_$%{}[]()&*^!@\"'`\\/#" + } + } + ]} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/__init__.py new file mode 100644 index 00000000000..66810d7e5b6 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/__init__.py @@ -0,0 +1,16 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from pgadmin.utils.route import BaseTestGenerator + + +class ForeignTableGeneratorTestCase(BaseTestGenerator): + + def runTest(self): + return [] diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/column_test_data.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/column_test_data.json new file mode 100644 index 00000000000..0975db204c0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/column_test_data.json @@ -0,0 +1,154 @@ +{ + "column_create": [ + { + "name": "Create: Add FT column with valid data", + "is_positive_test": true, + "inventory_data": {}, + "test_data": { + "name": "test_ft_column_add_", + "description": "Comment for create", + "cltype": "numeric", + "attlen":"10", + "attprecision":"5", + "coloptions":[], + "colconstype":"n", + "attnotnull": true, + "attoptions": [], + "seclabels": [], + "defval": "1" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": {} + } + }, + { + "name": "Create: Add FT column with valid data while server down", + "is_positive_test": false, + "inventory_data": {}, + "test_data": { + "name": "test_column_add_", + "cltype": "\"char\"", + "attacl": [], + "is_primary_key": false, + "attnotnull": false, + "attlen": null, + "attprecision": null, + "attoptions": [], + "seclabels": [], + "description": { + "comment": "jsoncomment" + } + }, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg3.connection.Connection.execute_scalar", + "return_value": "[(False, 'Mocked Internal Server Error'),(True,True)]" + }, + "expected_data": { + "status_code": 500, + "error_msg": "Mocked Internal Server Error", + "test_result_data": {} + } + } + ], + "column_delete": [ + { + "name": "Delete: Existing FT column", + "is_positive_test": true, + "inventory_data": {}, + "test_data": {}, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": {} + }, + "is_list": false + }, + { + "name": "Delete: Multiple existing FT column", + "is_positive_test": true, + "inventory_data": {}, + "test_data": {}, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": {} + }, + "is_list": true + }, + { + "name": "Delete: Non-existing FT column", + "is_positive_test": false, + "inventory_data": {}, + "test_data": { + "column_id": 9999999 + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": "Error: Object not found.", + "test_result_data": {} + }, + "is_list": false + }, + { + "name": "Delete: Existing FT column while server down", + "is_positive_test": false, + "inventory_data": {}, + "test_data": {}, + "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, + "error_msg": "Mocked Internal Server Error", + "test_result_data": {} + }, + "is_list": false + } + ], + "column_get": [ + { + "name": "Get details: For existing FT column", + "is_positive_test": true, + "inventory_data": {}, + "test_data": {}, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": {} + }, + "is_list": false + }, + { + "name": "Get details: For existing FT column while server down", + "is_positive_test": false, + "inventory_data": {}, + "test_data": {}, + "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, + "error_msg": "Mocked Internal Server Error", + "test_result_data": {} + }, + "is_list": false + } + ] +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_numeric.msql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_numeric.msql new file mode 100644 index 00000000000..7d7ab9cc711 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_numeric.msql @@ -0,0 +1,8 @@ +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + RENAME "col_1_$%{}[]()&*^!@""'`\/#" TO "new_col_1_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "new_col_1_$%{}[]()&*^!@""'`\/#" TYPE real; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."new_col_1_$%{}[]()&*^!@""'`\/#" + IS 'Comment for alter'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_numeric.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_numeric.sql new file mode 100644 index 00000000000..01c19c7d44c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_numeric.sql @@ -0,0 +1,9 @@ +-- Column: public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."new_col_1_$%{}[]()&*^!@""'`\/#" + +-- ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" DROP COLUMN IF EXISTS "new_col_1_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "new_col_1_$%{}[]()&*^!@""'`\/#" real NOT NULL DEFAULT 1; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."new_col_1_$%{}[]()&*^!@""'`\/#" + IS 'Comment for alter'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_text_with_default_value.msql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_text_with_default_value.msql new file mode 100644 index 00000000000..53e4d714ea7 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_text_with_default_value.msql @@ -0,0 +1,2 @@ +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ALTER COLUMN "col_2_$%{}[]()&*^!@""'`\/#" SET DEFAULT 'changed default value'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_text_with_default_value.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_text_with_default_value.sql new file mode 100644 index 00000000000..c31a66a8611 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/alter_column_text_with_default_value.sql @@ -0,0 +1,9 @@ +-- Column: public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_2_$%{}[]()&*^!@""'`\/#" + +-- ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" DROP COLUMN IF EXISTS "col_2_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_2_$%{}[]()&*^!@""'`\/#" text COLLATE pg_catalog."default" NOT NULL DEFAULT 'changed default value'::text; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_2_$%{}[]()&*^!@""'`\/#" + IS 'test comment'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_numeric.msql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_numeric.msql new file mode 100644 index 00000000000..dac34e0d839 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_numeric.msql @@ -0,0 +1,5 @@ +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_1_$%{}[]()&*^!@""'`\/#" numeric(10, 5) NOT NULL DEFAULT 1; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_1_$%{}[]()&*^!@""'`\/#" + IS 'Comment for create'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_numeric.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_numeric.sql new file mode 100644 index 00000000000..56b13664f2f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_numeric.sql @@ -0,0 +1,9 @@ +-- Column: public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_1_$%{}[]()&*^!@""'`\/#" + +-- ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" DROP COLUMN IF EXISTS "col_1_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_1_$%{}[]()&*^!@""'`\/#" numeric(10,5) NOT NULL DEFAULT 1; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_1_$%{}[]()&*^!@""'`\/#" + IS 'Comment for create'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_text_with_default_value.msql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_text_with_default_value.msql new file mode 100644 index 00000000000..38accf535d8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_text_with_default_value.msql @@ -0,0 +1,5 @@ +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_2_$%{}[]()&*^!@""'`\/#" text NOT NULL DEFAULT 'xyz'; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_2_$%{}[]()&*^!@""'`\/#" + IS 'test comment'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_text_with_default_value.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_text_with_default_value.sql new file mode 100644 index 00000000000..190bc36fff5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/create_column_text_with_default_value.sql @@ -0,0 +1,9 @@ +-- Column: public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_2_$%{}[]()&*^!@""'`\/#" + +-- ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" DROP COLUMN IF EXISTS "col_2_$%{}[]()&*^!@""'`\/#"; + +ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#" + ADD COLUMN "col_2_$%{}[]()&*^!@""'`\/#" text COLLATE pg_catalog."default" NOT NULL DEFAULT 'xyz'::text; + +COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."col_2_$%{}[]()&*^!@""'`\/#" + IS 'test comment'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/test.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/test.json new file mode 100644 index 00000000000..90714e9aba0 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/default/test.json @@ -0,0 +1,129 @@ +{ + "scenarios": [ + { + "type": "create", + "name": "Create FDW for foreign table", + "endpoint": "NODE-foreign_data_wrapper.obj", + "sql_endpoint": "NODE-foreign_data_wrapper.sql_id", + "data": { + "name": "test_fdw_for_foreign_table_columns", + "fdwacl": [], + "fdwoptions": [] + }, + "store_object_id": "True" + }, + { + "type": "create", + "name": "Create foreign server for foreign table", + "endpoint": "NODE-foreign_server.obj", + "sql_endpoint": "NODE-foreign_server.sql_id", + "data": { + "name":"test_fs_for_foreign_table_column" + }, + "store_object_id": "True" + }, + { + "type": "create", + "name": "Create Foreign Table for testing column node.", + "endpoint": "NODE-foreign_table.obj", + "sql_endpoint": "NODE-foreign_table.sql_id", + "data": { + "name": "foreign_table_2_$%{}[]()&*^!@\"'`\\/#", + "columns": [], + "schema": "public", + "basensp": "public", + "ftsrvname": "test_fs_for_foreign_table_column", + "owner": "postgres", + "ftoptions": [], + "inherits": [], + "relacl": [], + "seclabels": [] + }, + "store_object_id": "True" + }, + { + "type": "create", + "name": "Create FT Column (Integer/Numeric type)", + "endpoint": "NODE-foreign_table_column.obj", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql", + "data": { + "name": "col_1_$%{}[]()&*^!@\"'`\\/#", + "description": "Comment for create", + "cltype": "numeric", + "attlen":"10", + "attprecision":"5", + "coloptions":[], + "colconstype":"n", + "attnotnull": true, + "attoptions": [], + "seclabels": [], + "defval": "1" + }, + "expected_sql_file": "create_column_numeric.sql", + "expected_msql_file": "create_column_numeric.msql" + }, + { + "type": "alter", + "name": "Alter FT Column (Integer/Numeric type)", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql_id", + "data": { + "attnum": 1, + "name": "new_col_1_$%{}[]()&*^!@\"'`\\/#", + "description": "Comment for alter", + "cltype": "real" + }, + "expected_sql_file": "alter_column_numeric.sql", + "expected_msql_file": "alter_column_numeric.msql" + }, + { + "type": "delete", + "name": "Drop FT Column (Integer/Numeric type)", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "data": { + "name": "new_col_1_$%{}[]()&*^!@\"'`\\/#" + } + }, + { + "type": "create", + "name": "Create FT Column with text & default value", + "endpoint": "NODE-foreign_table_column.obj", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql", + "data": { + "name": "col_2_$%{}[]()&*^!@\"'`\\/#", + "cltype": "text", + "description": "test comment", + "attnotnull": true, + "colconstype": "n", + "defval": "'xyz'" + }, + "expected_sql_file": "create_column_text_with_default_value.sql", + "expected_msql_file": "create_column_text_with_default_value.msql" + }, + { + "type": "alter", + "name": "Alter FT Column with text & update default value", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "msql_endpoint": "NODE-foreign_table_column.msql_id", + "data": { + "attnum": 2, + "defval": "'changed default value'" + }, + "expected_sql_file": "alter_column_text_with_default_value.sql", + "expected_msql_file": "alter_column_text_with_default_value.msql" + }, + { + "type": "delete", + "name": "Drop FT column with text & update default value", + "endpoint": "NODE-foreign_table_column.obj_id", + "sql_endpoint": "NODE-foreign_table_column.sql_id", + "data": { + "name": "col_2_$%{}[]()&*^!@\"'`\\/#" + } + } + ]} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_add.py new file mode 100644 index 00000000000..ca2c2f988e3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_add.py @@ -0,0 +1,111 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid +from unittest.mock import patch + +from pgadmin.browser.server_groups.servers.databases.schemas.foreign_tables.\ + tests import utils as ft_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 pgadmin.browser.server_groups.servers.databases.schemas.foreign_tables.\ + foreign_table_columns. tests import utils as columns_utils +from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \ + foreign_servers.tests import utils as fsrv_utils +from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \ + tests import utils as fdw_utils + + +class ColumnAddTestCase(BaseTestGenerator): + """This class will add new column under table node.""" + url = '/browser/foreign_table_column/obj/' + + # Generates scenarios + scenarios = utils.generate_scenarios("column_create", + columns_utils.test_cases) + + def setUp(self): + super().setUp() + # Load test data + self.data = self.test_data + + # Get parent schema info + self.db_name = parent_node_dict["database"][-1]["db_name"] + self.schema_info = parent_node_dict['schema'][-1] + self.server_id = self.schema_info['server_id'] + self.db_id = self.schema_info['db_id'] + + # Create db connection + 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 add a table.") + + # Create schema + self.schema_name = self.schema_info['schema_name'] + self.schema_id = self.schema_info['schema_id'] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + + # Create FDW, server & table + self.fdw_name = "fdw_%s" % (str(uuid.uuid4())[1:8]) + self.fsrv_name = "fsrv_%s" % (str(uuid.uuid4())[1:8]) + self.ft_name = "ft_%s" % (str(uuid.uuid4())[1:8]) + + self.fdw_id = fdw_utils.create_fdw(self.server, self.db_name, + self.fdw_name) + self.fsrv_id = fsrv_utils.create_fsrv(self.server, self.db_name, + self.fsrv_name, self.fdw_name) + self.ft_id = ft_utils.create_foreign_table(self.server, self.db_name, + self.schema_name, + self.fsrv_name, + self.ft_name) + + def runTest(self): + """This function will add column under table node.""" + if "name" in self.data: + self.data["name"] = self.data["name"] + (str(uuid.uuid4())[1:8]) + + if self.is_positive_test: + response = columns_utils.api_create(self) + + # Assert response + utils.assert_status_code(self, response) + + # Verify in backend + self.assertIsNotNone(columns_utils.verify_column + (self.server, self.db_name, + self.data["name"]), + "Column not found") + else: + if self.mocking_required: + with patch(self.mock_data["function_name"], + side_effect=eval(self.mock_data["return_value"])): + response = columns_utils.api_create(self) + else: + if 'table_id' in self.data: + self.table_id = self.data['table_id'] + + response = columns_utils.api_create(self) + + # Assert response + utils.assert_status_code(self, response) + utils.assert_error_message(self, response) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_delete.py new file mode 100644 index 00000000000..538ad1c7220 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_delete.py @@ -0,0 +1,132 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid +from unittest.mock import patch + +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 pgadmin.browser.server_groups.servers.databases.schemas.foreign_tables.\ + foreign_table_columns. tests import utils as columns_utils +from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \ + foreign_servers.tests import utils as fsrv_utils +from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \ + tests import utils as fdw_utils +from pgadmin.browser.server_groups.servers.databases.schemas.foreign_tables.\ + tests import utils as ft_utils + + +class ColumnDeleteTestCase(BaseTestGenerator): + """This class will delete column under table node.""" + url = '/browser/foreign_table_column/obj/' + + # Generates scenarios + scenarios = utils.generate_scenarios("column_delete", + columns_utils.test_cases) + + def setUp(self): + super().setUp() + # Load test data + self.data = self.test_data + + # Get parent schema info + self.db_name = parent_node_dict["database"][-1]["db_name"] + self.schema_info = parent_node_dict['schema'][-1] + self.server_id = self.schema_info['server_id'] + self.db_id = self.schema_info['db_id'] + + # Create db connection + 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 add a table.") + + # Create schema + self.schema_name = self.schema_info['schema_name'] + self.schema_id = self.schema_info['schema_id'] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + + # Create FDW, server & table + self.fdw_name = "fdw_%s" % (str(uuid.uuid4())[1:8]) + self.fsrv_name = "fsrv_%s" % (str(uuid.uuid4())[1:8]) + self.ft_name = "ft_%s" % (str(uuid.uuid4())[1:8]) + + self.fdw_id = fdw_utils.create_fdw(self.server, self.db_name, + self.fdw_name) + self.fsrv_id = fsrv_utils.create_fsrv(self.server, self.db_name, + self.fsrv_name, self.fdw_name) + self.ft_id = ft_utils.create_foreign_table(self.server, self.db_name, + self.schema_name, + self.fsrv_name, + self.ft_name) + + # Create column + self.column_name = "test_column_delete_%s" % (str(uuid.uuid4())[1:8]) + self.column_id = columns_utils.create_column(self.server, + self.db_name, + self.schema_name, + self.ft_name, + self.column_name) + + self.column_name_1 = "test_column_delete_%s" % (str(uuid.uuid4())[1:8]) + + self.column_id_1 = columns_utils.create_column(self.server, + self.db_name, + self.schema_name, + self.ft_name, + self.column_name_1) + # Verify column creation + col_response = columns_utils.verify_column(self.server, self.db_name, + self.column_name) + if not col_response: + raise Exception("Could not find the column to drop.") + + col_response = columns_utils.verify_column(self.server, self.db_name, + self.column_name_1) + if not col_response: + raise Exception("Could not find the column to drop.") + + def runTest(self): + """This function will drop column under table node.""" + + if self.is_positive_test: + if self.is_list: + self.data["ids"] = [self.column_id, self.column_id_1] + response = columns_utils.api_delete(self, "") + else: + response = columns_utils.api_delete(self) + + # Assert response + utils.assert_status_code(self, response) + else: + if self.mocking_required: + with patch(self.mock_data["function_name"], + side_effect=eval(self.mock_data["return_value"])): + response = columns_utils.api_delete(self) + else: + if 'column_id' in self.data: + self.column_id = self.data['column_id'] + response = columns_utils.api_delete(self) + + # Assert response + utils.assert_status_code(self, response) + utils.assert_error_message(self, response) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_get.py new file mode 100644 index 00000000000..0a0c341a516 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/test_ft_column_get.py @@ -0,0 +1,125 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid +from unittest.mock import patch + +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 columns_utils +from pgadmin.browser.server_groups.servers.databases.schemas.foreign_tables.\ + foreign_table_columns. tests import utils as columns_utils +from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \ + foreign_servers.tests import utils as fsrv_utils +from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \ + tests import utils as fdw_utils +from pgadmin.browser.server_groups.servers.databases.schemas.foreign_tables.\ + tests import utils as ft_utils + + +class ColumnGetTestCase(BaseTestGenerator): + """This class will get column under table node.""" + url = '/browser/foreign_table_column/obj/' + + # Generates scenarios + scenarios = utils.generate_scenarios("column_get", + columns_utils.test_cases) + + def setUp(self): + super().setUp() + # Load test data + self.data = self.test_data + + # Get parent schema info + self.db_name = parent_node_dict["database"][-1]["db_name"] + self.schema_info = parent_node_dict['schema'][-1] + self.server_id = self.schema_info['server_id'] + self.db_id = self.schema_info['db_id'] + + # Create db connection + 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 add a table.") + + # Create schema + self.schema_name = self.schema_info['schema_name'] + self.schema_id = self.schema_info['schema_id'] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a table.") + + # Create FDW, server & table + self.fdw_name = "fdw_%s" % (str(uuid.uuid4())[1:8]) + self.fsrv_name = "fsrv_%s" % (str(uuid.uuid4())[1:8]) + self.ft_name = "ft_%s" % (str(uuid.uuid4())[1:8]) + + self.fdw_id = fdw_utils.create_fdw(self.server, self.db_name, + self.fdw_name) + self.fsrv_id = fsrv_utils.create_fsrv(self.server, self.db_name, + self.fsrv_name, self.fdw_name) + self.ft_id = ft_utils.create_foreign_table(self.server, self.db_name, + self.schema_name, + self.fsrv_name, + self.ft_name) + + # Create column + self.column_name = "test_column_delete_%s" % (str(uuid.uuid4())[1:8]) + self.column_id = columns_utils.create_column(self.server, + self.db_name, + self.schema_name, + self.ft_name, + self.column_name) + if self.is_list: + # Create column + self.column_name_1 = "test_column_delete_%s" % \ + (str(uuid.uuid4())[1:8]) + self.column_id_1 = columns_utils.create_column(self.server, + self.db_name, + self.schema_name, + self.ft_name, + self.column_name_1) + + def runTest(self): + """This function will fetch the column under table node.""" + if self.is_positive_test: + if self.is_list: + response = columns_utils.api_get(self, "") + else: + response = columns_utils.api_get(self) + + # Assert response + utils.assert_status_code(self, response) + else: + if self.mocking_required: + with patch(self.mock_data["function_name"], + side_effect=eval(self.mock_data["return_value"])): + if self.is_list: + response = columns_utils.api_get(self, "") + else: + response = columns_utils.api_get(self) + else: + if 'column_id' in self.data: + self.column_id = self.data['column_id'] + response = columns_utils.api_get(self) + + # Assert response + utils.assert_status_code(self, response) + utils.assert_error_message(self, response) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/utils.py new file mode 100644 index 00000000000..95ed8b5fc3f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/tests/utils.py @@ -0,0 +1,203 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2023, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +import sys +import traceback +import os +import json +from urllib.parse import urlencode +from regression.python_test_utils import test_utils as utils + +# Load test data from json file. +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +with open(CURRENT_PATH + "/column_test_data.json") as data_file: + test_cases = json.load(data_file) + + +# api method calls +def api_create(self): + return self.tester.post("{0}{1}/{2}/{3}/{4}/{5}/". + format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.ft_id), + data=json.dumps(self.data), + content_type='html/json' + ) + + +def api_delete(self, column_id=None): + if column_id is None: + column_id = self.column_id + return self.tester.delete("{0}{1}/{2}/{3}/{4}/{5}/{6}". + format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.ft_id, column_id), + data=json.dumps(self.data), + follow_redirects=True + ) + + +def api_get(self, column_id=None): + if column_id is None: + column_id = self.column_id + return self.tester.get("{0}{1}/{2}/{3}/{4}/{5}/{6}". + format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, self.schema_id, + self.ft_id, column_id), + data=json.dumps(self.data), + follow_redirects=True + ) + + +def api_get_msql(self, url_encode_data): + return self.tester.get("{0}{1}/{2}/{3}/{4}/{5}/{6}?{7}". + format(self.url, utils.SERVER_GROUP, self.server_id, + self.db_id, + self.schema_id, self.ft_id, + self.column_id, + urlencode(url_encode_data)), + follow_redirects=True + ) + + +def api_put(self): + return self.tester.put("{0}{1}/{2}/{3}/{4}/{5}/{6}". + format(self.url, utils.SERVER_GROUP, self.server_id, + self.db_id, self.schema_id, self.ft_id, + self.column_id), + data=json.dumps(self.data), + follow_redirects=True) + + +def create_column(server, db_name, schema_name, table_name, col_name, + col_data_type='char'): + """ + This function creates a column under provided table. + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param schema_name: schema name + :type schema_name: str + :param table_name: table name + :type table_name: str + :param col_name: column name + :type col_name: str + :param col_data_type: column data type + :type col_data_type: str + :return ft_id: table id + :rtype: int + """ + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + old_isolation_level = connection.isolation_level + utils.set_isolation_level(connection, 0) + pg_cursor = connection.cursor() + query = "ALTER FOREIGN TABLE %s.%s ADD COLUMN %s %s" % \ + (schema_name, table_name, col_name, col_data_type) + pg_cursor.execute(query) + utils.set_isolation_level(connection, old_isolation_level) + connection.commit() + # Get column position of newly added column + pg_cursor.execute("select attnum from pg_attribute where" + " attname='%s'" % col_name) + col = pg_cursor.fetchone() + col_pos = '' + if col: + col_pos = col[0] + connection.close() + return col_pos + except Exception: + traceback.print_exc(file=sys.stderr) + raise + + +def create_identity_column(server, db_name, schema_name, table_name, + col_name, col_data_type='bigint'): + """ + This function creates a column under provided table. + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param schema_name: schema name + :type schema_name: str + :param table_name: table name + :type table_name: str + :param col_name: column name + :type col_name: str + :param col_data_type: column data type + :type col_data_type: str + :return table_id: table id + :rtype: int + """ + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + old_isolation_level = connection.isolation_level + utils.set_isolation_level(connection, 0) + pg_cursor = connection.cursor() + query = "ALTER TABLE %s.%s ADD COLUMN %s %s " \ + "GENERATED ALWAYS AS IDENTITY" % \ + (schema_name, table_name, col_name, col_data_type) + pg_cursor.execute(query) + utils.set_isolation_level(connection, old_isolation_level) + connection.commit() + # Get column position of newly added column + pg_cursor.execute("select attnum from pg_attribute where" + " attname='%s'" % col_name) + col = pg_cursor.fetchone() + col_pos = '' + if col: + col_pos = col[0] + connection.close() + return col_pos + except Exception: + traceback.print_exc(file=sys.stderr) + raise + + +def verify_column(server, db_name, col_name): + """ + This function verifies table exist in database or not. + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param col_name: column name + :type col_name: str + :return table: table record from database + :rtype: tuple + """ + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute("select * from pg_attribute where attname='%s'" % + col_name) + col = pg_cursor.fetchone() + connection.close() + return col + except Exception: + traceback.print_exc(file=sys.stderr) + raise diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.js index 5e9eab8736a..667d90cb696 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.js @@ -11,15 +11,16 @@ import { getNodeVariableSchema } from '../../../../../static/js/variable.ui'; import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui'; import ForeignTableSchema from './foreign_table.ui'; import _ from 'lodash'; +import Notify from '../../../../../../../../static/js/helpers/Notifier'; /* Create and Register Foreign Table Collection and Node. */ -define('pgadmin.node.foreign_table', [ +define('pgadmin.node.foreign_table', ['pgadmin.tables.js/enable_disable_triggers', 'sources/gettext', 'sources/url_for', 'pgadmin.browser', 'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/schema_child_tree_node', 'pgadmin.browser.collection','pgadmin.node.column', 'pgadmin.node.constraints' ], function( - gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode + tableFunctions, gettext, url_for, pgBrowser, schemaChild, schemaChildTreeNode, ) { if (!pgBrowser.Nodes['coll-foreign_table']) { @@ -69,9 +70,54 @@ define('pgadmin.node.foreign_table', [ applies: ['object', 'context'], callback: 'show_obj_properties', category: 'create', priority: 4, label: gettext('Foreign Table...'), data: {action: 'create', check: false}, enable: 'canCreate', - }, + },{ + // To enable/disable all triggers for the table + name: 'enable_all_triggers', node: 'foreign_table', module: this, + applies: ['object', 'context'], callback: 'enable_triggers_on_table', + category: gettext('Trigger(s)'), priority: 4, label: gettext('Enable All'), + enable : 'canCreate_with_trigger_enable', + data: { + data_disabled: gettext('The selected tree node does not support this option.'), + action: 'create' + }, + },{ + name: 'disable_all_triggers', node: 'foreign_table', module: this, + applies: ['object', 'context'], callback: 'disable_triggers_on_table', + category: gettext('Trigger(s)'), priority: 4, label: gettext('Disable All'), + enable : 'canCreate_with_trigger_disable', + data: { + data_disabled: gettext('The selected tree node does not support this option.'), + action: 'create' + }} ]); - + }, + callbacks: { + /* Enable trigger(s) on table */ + enable_triggers_on_table: function(args) { + tableFunctions.enableTriggers( + pgBrowser.tree, + Notify, + this.generate_url.bind(this), + args + ); + }, + /* Disable trigger(s) on table */ + disable_triggers_on_table: function(args) { + tableFunctions.disableTriggers( + pgBrowser.tree, + Notify, + this.generate_url.bind(this), + args + ); + }, + }, + // Check to whether table has disable trigger(s) + canCreate_with_trigger_enable: function(itemData) { + return itemData.tigger_count > 0 && (itemData.has_enable_triggers == 0 || itemData.has_enable_triggers < itemData.tigger_count); + }, + // Check to whether table has enable trigger(s) + canCreate_with_trigger_disable: function(itemData) { + return itemData.tigger_count > 0 && itemData.has_enable_triggers > 0; }, getSchema: function(treeNodeInfo, itemNodeData) { return new ForeignTableSchema( diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.ui.js index 4cfda05e27d..9d342eac2da 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table.ui.js @@ -12,6 +12,7 @@ import SecLabelSchema from '../../../../../static/js/sec_label.ui'; import BaseUISchema from 'sources/SchemaView/base_schema.ui'; import OptionsSchema from '../../../../../static/js/options.ui'; import { isEmptyString } from 'sources/validators'; +import VariableSchema from 'top/browser/server_groups/servers/static/js/variable.ui'; import _ from 'lodash'; import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui'; @@ -167,7 +168,7 @@ export default class ForeignTableSchema extends BaseUISchema { id: 'columns', label: gettext('Columns'), cell: 'text', type: 'collection', group: gettext('Columns'), mode: ['edit', 'create'], schema: this.columnsObj, - canAdd: true, canDelete: true, canEdit: true, columns: ['attname', 'datatype', 'inheritedfrom'], + canAdd: true, canDelete: true, canEdit: true, columns: ['name', 'cltype', 'attprecision', 'attlen', 'inheritedfrom'], // For each row edit/delete button enable/disable canEditRow: this.canEditDeleteRowColumns, canDeleteRow: this.canEditDeleteRowColumns, @@ -256,27 +257,28 @@ export function getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser) { export class ColumnSchema extends BaseUISchema { constructor(initValues, getPrivilegeRoleSchema, nodeInfo, datatypeOptions, collspcnameOptions) { super({ - attname: undefined, - datatype: undefined, - typlen: undefined, - precision: undefined, - typdefault: undefined, + name: undefined, + description: undefined, + atttypid: undefined, + cltype: undefined, + edit_types: undefined, + attlen: undefined, + attprecision: undefined, + defval: undefined, attnotnull: undefined, - collname: undefined, + collspcname: undefined, + attstattarget:undefined, attnum: undefined, inheritedfrom: undefined, inheritedid: undefined, - attstattarget: undefined, coloptions: [], + colconstype: 'n', }); this.getPrivilegeRoleSchema = getPrivilegeRoleSchema; this.nodeInfo = nodeInfo; - this.datatypeOptions = datatypeOptions; + this.cltypeOptions = datatypeOptions; this.collspcnameOptions = collspcnameOptions; - - this.datatypes = []; - } get idAttribute() { @@ -287,43 +289,93 @@ export class ColumnSchema extends BaseUISchema { return (_.isUndefined(state.inheritedid) || _.isNull(state.inheritedid) || _.isUndefined(state.inheritedfrom) || _.isNull(state.inheritedfrom)) ? true : false; } + // Check whether the column is a generated column + isTypeGenerated(state) { + let colconstype = state.colconstype; + return (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'g'); + } + get baseFields() { let obj = this; return [ { - id: 'attname', label: gettext('Name'), cell: 'text', + id: 'name', label: gettext('Name'), cell: 'text', type: 'text', editable: obj.editable_check_for_column, noEmpty: true, minWidth: 115, + disabled: (state)=>{ + return state.is_inherited; + }, + }, + { + id: 'description', label: gettext('Comment'), cell: 'text', + type: 'multiline', mode: ['properties', 'create', 'edit'], }, { - id: 'datatype', label: gettext('Data type'), minWidth: 150, - group: gettext('Definition'), noEmpty: true, + id: 'cltype', + label: gettext('Data type'), + minWidth: 150, + group: gettext('Definition'), + noEmpty: true, editable: obj.editable_check_for_column, - options: obj.datatypeOptions, + disabled: (state)=>{ + return state.is_inherited; + }, + options: obj.cltypeOptions, optionsLoaded: (options)=>{ - obj.datatypes = options; obj.type_options = options; }, - cell: 'select', + cell: (row)=>{ + return { + cell: 'select', + options: this.cltypeOptions, + controlProps: { + allowClear: false, + filter: (options)=>{ + let result = options; + let edit_types = row?.edit_types || []; + if(!obj.isNew(row) && !this.inErd) { + result = _.filter(options, (o)=>edit_types.indexOf(o.value) > -1); + } + return result; + }, + } + }; + }, + type: (state)=>{ + return { + type: 'select', + options: this.cltypeOptions, + controlProps: { + allowClear: false, + filter: (options)=>{ + let result = options; + let edit_types = state?.edit_types || []; + if(!obj.isNew(state) && !this.inErd) { + result = _.filter(options, (o)=>edit_types.indexOf(o.value) > -1); + } + return result; + }, + } + }; + }, controlProps: { allowClear: false, - }, - type: 'select' + } }, { id: 'inheritedfrom', label: gettext('Inherited From'), cell: 'label', - type: 'label', readonly: true, editable: false, mode: ['properties', 'edit'], + type: 'label', readonly: true, editable: false, mode: ['create','properties', 'edit'], }, { id: 'attnum', label: gettext('Position'), cell: 'text', type: 'text', disabled: obj.inCatalog(), mode: ['properties'], }, { - id: 'typlen', label: gettext('Length'), cell: 'int', - deps: ['datatype'], type: 'int', group: gettext('Definition'), width: 120, minWidth: 120, + id: 'attlen', label: gettext('Length'), cell: 'int', + deps: ['cltype'], type: 'int', group: gettext('Definition'), minWidth: 60, disabled: (state) => { - let val = state.typlen; + let val = state.attlen; // We will store type from selected from combobox if(!(_.isUndefined(state.inheritedid) || _.isNull(state.inheritedid) @@ -331,12 +383,12 @@ export class ColumnSchema extends BaseUISchema { || _.isNull(state.inheritedfrom))) { if (!_.isUndefined(val)) { - state.typlen = undefined; + state.attlen = undefined; } return true; } - let of_type = state.datatype, + let of_type = state.cltype, has_length = false; if(obj.type_options) { state.is_tlength = false; @@ -358,34 +410,34 @@ export class ColumnSchema extends BaseUISchema { }); if (!has_length && !_.isUndefined(val)) { - state.typlen = undefined; + state.attlen = undefined; } return !(state.is_tlength); } if (!has_length && !_.isUndefined(val)) { - state.typlen = undefined; + state.attlen = undefined; } return true; }, }, { - id: 'precision', label: gettext('Precision'), cell: 'int', minWidth: 60, - deps: ['datatype'], type: 'int', group: gettext('Definition'), + id: 'attprecision', label: gettext('Scale'), cell: 'int', minWidth: 60, + deps: ['cltype'], type: 'int', group: gettext('Definition'), disabled: (state) => { - let val = state.precision; + let val = state.attprecision; if(!(_.isUndefined(state.inheritedid) || _.isNull(state.inheritedid) || _.isUndefined(state.inheritedfrom) || _.isNull(state.inheritedfrom))) { if (!_.isUndefined(val)) { - state.precision = undefined; + state.attprecision = undefined; } return true; } - let of_type = state.datatype, + let of_type = state.cltype, has_precision = false; if(obj.type_options) { @@ -406,34 +458,16 @@ export class ColumnSchema extends BaseUISchema { } }); if (!has_precision && !_.isUndefined(val)) { - state.precision = undefined; + state.attprecision = undefined; } return !(state.is_precision); } if (!has_precision && !_.isUndefined(val)) { - state.precision = undefined; + state.attprecision = undefined; } return true; }, }, - { - id: 'typdefault', label: gettext('Default'), cell: 'text', - type: 'text', group: gettext('Definition'), - controlProps: {placeholder: gettext('Enter an expression or a value.')}, - editable: (state) => { - if(!(_.isUndefined(state.inheritedid) - || _.isNull(state.inheritedid) - || _.isUndefined(state.inheritedfrom) - || _.isNull(state.inheritedfrom))) { return false; } - - return obj.nodeInfo.server.version >= 90300; - }, - }, - { - id: 'attnotnull', label: gettext('Not NULL?'), cell: 'switch', - type: 'switch', minWidth: 80, - group: gettext('Definition'), editable: obj.editable_check_for_column, - }, { id: 'attstattarget', label: gettext('Statistics'), cell: 'text', type: 'text', disabled: (state) => { @@ -451,9 +485,103 @@ export class ColumnSchema extends BaseUISchema { group: gettext('Definition'), }, { - id: 'collname', label: gettext('Collation'), cell: 'select', + id: 'attstorage', label: gettext('Storage'), group: gettext('Definition'), + type: 'select', mode: ['properties', 'edit'], + cell: 'select', readonly: obj.inSchemaWithColumnCheck, + controlProps: { placeholder: gettext('Select storage'), + allowClear: false, + }, + options: [ + {label: 'PLAIN', value: 'p'}, + {label: 'MAIN', value: 'm'}, + {label: 'EXTERNAL', value: 'e'}, + {label: 'EXTENDED', value: 'x'}, + ], + }, + { + id: 'defval', + label: gettext('Default'), + cell: 'text', + type: 'text', + group: gettext('Constraints'), + editable: (state) => { + if(!(_.isUndefined(state.inheritedid) + || _.isNull(state.inheritedid) + || _.isUndefined(state.inheritedfrom) + || _.isNull(state.inheritedfrom))) { return false; } + + return obj.nodeInfo.server.version >= 90300; + }, + }, + { + id: 'attnotnull', + label: gettext('Not NULL?'), + cell: 'switch', + type: 'switch', + minWidth: 80, + group: gettext('Constraints'), + editable: obj.editable_check_for_column, + }, + { + id: 'colconstype', + label: gettext('Type'), + cell: 'text', + group: gettext('Constraints'), + type: (state)=>{ + let options = [ + { + 'label': gettext('NONE'), + 'value': 'n'}, + ]; // You can't change the existing column to Generated column. + if (this.isNew(state)) { + options.push({ + 'label': gettext('GENERATED'), + 'value': 'g', + }); + } else { + options.push({ + 'label': gettext('GENERATED'), + 'value': 'g', + 'disabled': true, + }); + } + return { + type: 'toggle', + options: options, + }; + }, + disabled: function(state) { + return (!this.isNew(state) && state.colconstype == 'g'); + }, + min_version: 120000, + }, + { + id: 'genexpr', + label: gettext('Expression'), + type: 'text', + mode: ['properties', 'create', 'edit'], + group: gettext('Constraints'), + min_version: 120000, + deps: ['colconstype'], + visible: this.isTypeGenerated, + readonly: function(state) { + return !this.isNew(state); + }, + }, + { + id: 'attoptions', label: gettext('Variables'), type: 'collection', + group: gettext('Variables'), + schema: new VariableSchema([ + {label: 'n_distinct', value: 'n_distinct', vartype: 'string'}, + {label: 'n_distinct_inherited', value: 'n_distinct_inherited', vartype: 'string'} + ], null, null, ['name', 'value']), + uniqueCol : ['name'], mode: ['edit', 'create'], + canAdd: true, canEdit: false, canDelete: true, + }, + { + id: 'collspcname', label: gettext('Collation'), cell: 'select', type: 'select', group: gettext('Definition'), - deps: ['datatype'], options: obj.collspcnameOptions, + deps: ['cltype'], options: obj.collspcnameOptions, disabled: (state)=>{ if (!(_.isUndefined(obj.isNew)) && !obj.isNew(state)) { return false; } diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/create.sql new file mode 100644 index 00000000000..20e67798dcc --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/create.sql @@ -0,0 +1,39 @@ +{% import 'macros/variable.macros' as VARIABLE %} +{% import 'types/macros/get_full_type_sql_format.macros' as GET_TYPE %} +{### Add column ###} +{% if data.name and data.cltype %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ADD COLUMN {{conn|qtIdent(data.name)}} {% if is_sql %}{{data.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, data.cltype, data.attlen, data.attprecision, data.hasSqrBracket) }}{% endif %} +{### Add coloptions to column ###} +{% if data.coloptions %}{% for o in data.coloptions %}{% if o.option is defined and o.value is defined %}{% if loop.first %} + OPTIONS ({% endif %}{% if not loop.first %}, {% endif %}{{o.option}} {{o.value|qtLiteral(conn)}}{% if loop.last %}){% endif %}{% endif %}{% endfor %}{% endif %}{% if data.collspcname %} + COLLATE {{data.collspcname}}{% endif %}{% if data.attnotnull %} + NOT NULL{% endif %}{% if data.defval is defined and data.defval is not none and data.defval != '' and data.colconstype != 'g' %} + DEFAULT {{data.defval}}{% endif %}{% if data.colconstype == 'g' and data.genexpr and data.genexpr != '' %} + GENERATED ALWAYS AS ({{data.genexpr}}) STORED{% endif %}{% endif %}; + +{### Add comments ###} +{% if data and data.description and data.description != None %} +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.table, data.name)}} + IS {{data.description|qtLiteral(conn)}}; +{% endif %} + +{### Add variables to column ###} +{% if data.attoptions %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, data.attoptions) }} +{% endif %} + +{### Alter column statistics value ###} +{% if data.attstattarget is defined and data.attstattarget > -1 %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtTypeIdent(data.name)}} SET STATISTICS {{data.attstattarget}}; +{% endif %} + +{### Alter column storage value ###} +{% if data.attstorage is defined and data.attstorage != data.defaultstorage %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtTypeIdent(data.name)}} SET STORAGE {%if data.attstorage == 'p' %} +PLAIN{% elif data.attstorage == 'm'%}MAIN{% elif data.attstorage == 'e'%} +EXTERNAL{% elif data.attstorage == 'x'%}EXTENDED{% endif %}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/delete.sql new file mode 100644 index 00000000000..9c986ce01f8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/delete.sql @@ -0,0 +1 @@ +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} DROP COLUMN IF EXISTS {{conn|qtIdent(data.name)}}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/update.sql new file mode 100644 index 00000000000..d33199c7a92 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_table_columns/sql/default/update.sql @@ -0,0 +1,110 @@ +{% import 'macros/variable.macros' as VARIABLE %} +{% import 'types/macros/get_full_type_sql_format.macros' as GET_TYPE %} +{### Rename column name ###} +{% if data.name and data.name != o_data.name %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + RENAME {{conn|qtIdent(o_data.name)}} TO {{conn|qtIdent(data.name)}}; + +{% endif %} +{### Add/Update column options ###} +{% if 'coloptions' in data and data.coloptions != None and data.coloptions|length > 0 %} +{% set coloptions = data.coloptions %} +{% if data.name %} +{% if 'added' in coloptions and coloptions.added|length > 0 %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} OPTIONS (ADD {% for opt in coloptions.added %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(opt.option) }} {{ opt.value|qtLiteral(conn) }}{% endfor %}); +{% endif %} +{% if 'changed' in coloptions and coloptions.changed|length > 0 %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} OPTIONS (SET {% for opt in coloptions.changed %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(opt.option) }} {{ opt.value|qtLiteral(conn) }}{% endfor %}); +{% endif %} +{% if 'deleted' in coloptions and coloptions.deleted|length > 0 %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {{conn|qtIdent(data.name)}} OPTIONS (DROP {% for opt in coloptions.deleted %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(opt.option) }}{% endfor %}); +{% endif %} +{% endif %} +{% endif %} +{### Alter column type and collation ###} +{% if (data.cltype and data.cltype != o_data.cltype) or (data.attlen is defined and data.attlen != o_data.attlen) or (data.attprecision is defined and data.attprecision != o_data.attprecision) or (data.collspcname and data.collspcname != o_data.collspcname) or data.col_type_conversion is defined %} +{% if data.col_type_conversion is defined and data.col_type_conversion == False %} +-- WARNING: +-- The SQL statement below would normally be used to alter the cltype for the {{o_data.name}} column, however, +-- the current datatype cannot be cast to the target cltype so this conversion cannot be made automatically. +{% endif %} +{% if data.col_type_conversion is defined and data.col_type_conversion == False %} -- {% endif %}ALTER FOREIGN TABLE {{conn|qtIdent(data.schema, data.table)}} +{% if data.col_type_conversion is defined and data.col_type_conversion == False %} -- {% endif %} ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} TYPE {{ GET_TYPE.UPDATE_TYPE_SQL(conn, data, o_data) }}{% if data.collspcname and data.collspcname != o_data.collspcname %} + COLLATE {{data.collspcname}}{% elif o_data.collspcname %} COLLATE {{o_data.collspcname}}{% endif %}; + +{% endif %} +{### Alter column default value ###} +{% if is_view_only and data.defval is defined and data.defval is not none and data.defval != '' and data.defval != o_data.defval %} +ALTER VIEW {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} SET DEFAULT {{data.defval}}; +{% elif data.defval is defined and data.defval is not none and data.defval != '' and data.defval != o_data.defval %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} SET DEFAULT {{data.defval}}; + +{% endif %} +{### Drop column default value ###} +{% if data.defval is defined and (data.defval == '' or data.defval is none) and data.defval != o_data.defval %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} DROP DEFAULT; + +{% endif %} +{### Alter column not null value ###} +{% if 'attnotnull' in data and data.attnotnull != o_data.attnotnull %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} {% if data.attnotnull %}SET{% else %}DROP{% endif %} NOT NULL; + +{% endif %} +{### Alter column statistics value ###} +{% if data.attstattarget is defined and data.attstattarget != o_data.attstattarget %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} SET STATISTICS {{data.attstattarget}}; + +{% endif %} +{### Alter column storage value ###} +{% if data.attstorage is defined and data.attstorage != o_data.attstorage %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} + ALTER COLUMN {% if data.name %}{{conn|qtTypeIdent(data.name)}}{% else %}{{conn|qtTypeIdent(o_data.name)}}{% endif %} SET STORAGE {%if data.attstorage == 'p' %} +PLAIN{% elif data.attstorage == 'm'%}MAIN{% elif data.attstorage == 'e'%} +EXTERNAL{% elif data.attstorage == 'x'%}EXTENDED{% endif %}; + +{% endif %} +{% if data.description is defined and data.description != None %} +{% if data.name %} +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.table, data.name)}} +{% else %} +COMMENT ON COLUMN {{conn|qtIdent(data.schema, data.table, o_data.name)}} +{% endif %} + IS {{data.description|qtLiteral(conn)}}; + +{% endif %} +{### Update column variables ###} +{% if 'attoptions' in data and data.attoptions != None and data.attoptions|length > 0 %} +{% set variables = data.attoptions %} +{% if 'deleted' in variables and variables.deleted|length > 0 %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} +{% if data.name %} + {{ VARIABLE.UNSET(conn, 'COLUMN', data.name, variables.deleted) }} +{% else %} + {{ VARIABLE.UNSET(conn, 'COLUMN', o_data.name, variables.deleted) }} +{% endif %} +{% endif %} +{% if 'added' in variables and variables.added|length > 0 %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} +{% if data.name %} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, variables.added) }} +{% else %} + {{ VARIABLE.SET(conn, 'COLUMN', o_data.name, variables.added) }} +{% endif %} +{% endif %} +{% if 'changed' in variables and variables.changed|length > 0 %} +ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} +{% if data.name %} + {{ VARIABLE.SET(conn, 'COLUMN', data.name, variables.changed) }} +{% else %} + {{ VARIABLE.SET(conn, 'COLUMN', o_data.name, variables.changed) }} +{% endif %} +{% endif %} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/create.sql index bebc5ec7df7..5d5271c2ee6 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/create.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/create.sql @@ -7,12 +7,12 @@ CREATE FOREIGN TABLE{% if add_not_exists_clause %} IF NOT EXISTS{% endif %} {{ c {% for c in data.columns %} {% if (not c.inheritedfrom or c.inheritedfrom =='' or c.inheritedfrom == None or c.inheritedfrom == 'None' ) %} {% if is_columns.append('1') %}{% endif %} - {{conn|qtIdent(c.attname)}} {% if is_sql %}{{ c.fulltype }}{% else %}{{c.datatype }}{% if c.typlen %}({{c.typlen}}{% if c.precision %}, {{c.precision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %}{% endif %}{% if c.coloptions %} + {{conn|qtIdent(c.name)}} {% if is_sql %}{{ c.fulltype }}{% else %}{{c.cltype }}{% if c.attlen %}({{c.attlen}}{% if c.attprecision %}, {{c.attprecision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %}{% endif %}{% if c.coloptions %} {% for o in c.coloptions %}{% if o.option is defined and o.value is defined %} {% if loop.first %} OPTIONS ({% endif %}{% if not loop.first %}, {% endif %}{{o.option}} {{o.value|qtLiteral(conn)}}{% if loop.last %}){% endif %}{% endif %} {% endfor %}{% endif %} {% if c.attnotnull %} NOT NULL{% else %} NULL{% endif %} -{% if c.typdefault is defined and c.typdefault is not none %} DEFAULT {{c.typdefault}}{% endif %} +{% if c.defval is defined and c.defval is not none %} DEFAULT {{c.defval}}{% endif %} {% if c.collname %} COLLATE {{c.collname}}{% endif %} {% if not loop.last %}, {% endif %} @@ -51,7 +51,7 @@ COMMENT ON FOREIGN TABLE {{ conn|qtIdent(data.basensp, data.name) }} {% for c in data.columns %} {% if c.description %} -COMMENT ON COLUMN {{conn|qtIdent(data.basensp, data.name, c.attname)}} +COMMENT ON COLUMN {{conn|qtIdent(data.basensp, data.name, c.name)}} IS {{c.description|qtLiteral(conn)}}; {% endif %} {% endfor %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/edit_mode_types_multi.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/edit_mode_types_multi.sql new file mode 100644 index 00000000000..9cbd793f099 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/edit_mode_types_multi.sql @@ -0,0 +1,13 @@ +SELECT t.main_oid, pg_catalog.ARRAY_AGG(t.typname) as edit_types +FROM +(SELECT pc.castsource AS main_oid, pg_catalog.format_type(tt.oid,NULL) AS typname +FROM pg_catalog.pg_type tt + JOIN pg_catalog.pg_cast pc ON tt.oid=pc.casttarget + WHERE pc.castsource IN ({{type_ids}}) + AND pc.castcontext IN ('i', 'a') +UNION +SELECT tt.typbasetype AS main_oid, pg_catalog.format_type(tt.oid,NULL) AS typname +FROM pg_catalog.pg_type tt +WHERE tt.typbasetype IN ({{type_ids}}) +) t +GROUP BY t.main_oid; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/enable_disable_trigger.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/enable_disable_trigger.sql new file mode 100644 index 00000000000..ad3dcafa5cd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/enable_disable_trigger.sql @@ -0,0 +1,3 @@ +{% set enable_map = {'O':'ENABLE', 'D':'DISABLE'} %} +ALTER FOREIGN TABLE {{ conn|qtIdent(data.basensp, data.name) }} + {{ enable_map[is_enable_trigger] }} TRIGGER ALL; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/foreign_table_schema_diff.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/foreign_table_schema_diff.sql index 4ba5fcf4663..fa6353cc8b0 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/foreign_table_schema_diff.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/foreign_table_schema_diff.sql @@ -16,7 +16,7 @@ CREATE FOREIGN TABLE {{ conn|qtIdent(o_data.basensp, o_data.name) }}( {% for c in columns %} {% if (not c.inheritedfrom or c.inheritedfrom =='' or c.inheritedfrom == None or c.inheritedfrom == 'None' ) %} {% if is_columns.append('1') %}{% endif %} - {{conn|qtIdent(c.attname)}} {% if is_sql %}{{ c.fulltype }}{% else %}{{c.datatype }}{% if c.typlen %}({{c.typlen}}{% if c.precision %}, {{c.precision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %}{% endif %}{% if c.coloptions %} + {{conn|qtIdent(c.name)}} {% if is_sql %}{{ c.fulltype }}{% else %}{{c.cltype }}{% if c.attlen %}({{c.attlen}}{% if c.attprecision %}, {{c.attprecision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %}{% endif %}{% if c.coloptions %} {% for o in c.coloptions %}{% if o.option is defined and o.value is defined %} {% if loop.first %} OPTIONS ({% endif %}{% if not loop.first %}, {% endif %}{{o.option}} {{o.value|qtLiteral(conn)}}{% if loop.last %}){% endif %}{% endif %} {% endfor %}{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_columns.sql index 3bdb38ae321..4eaf2217e4c 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_columns.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_columns.sql @@ -1,6 +1,6 @@ WITH INH_TABLES AS (SELECT - distinct on (at.attname) attname, ph.inhparent AS inheritedid, ph.inhseqno, + at.attname AS name, ph.inhparent AS inheritedid, ph.inhseqno, pg_catalog.concat(nmsp_parent.nspname, '.',parent.relname ) AS inheritedfrom FROM pg_catalog.pg_attribute at @@ -13,9 +13,12 @@ WITH INH_TABLES AS GROUP BY at.attname, ph.inhparent, ph.inhseqno, inheritedfrom ORDER BY at.attname, ph.inhparent, ph.inhseqno, inheritedfrom ) -SELECT INH.inheritedfrom, INH.inheritedid, att.attoptions, attfdwoptions, - att.attname, att.attndims, att.atttypmod, pg_catalog.format_type(t.oid,NULL) AS datatype, +SELECT INH.inheritedfrom, INH.inheritedid, att.attoptions, att.atttypid, attfdwoptions, + att.attname as name, att.attndims, att.atttypmod, pg_catalog.format_type(t.oid,NULL) AS cltype, att.attnotnull, att.attstattarget, att.attnum, pg_catalog.format_type(t.oid, att.atttypmod) AS fulltype, + CASE WHEN t.typelem > 0 THEN t.typelem ELSE t.oid END as elemoid, + (SELECT nspname FROM pg_catalog.pg_namespace WHERE oid = t.typnamespace) as typnspname, + pg_catalog.format_type(t.oid,NULL) AS typname, CASE WHEN length(cn.nspname::text) > 0 AND length(cl.collname::text) > 0 THEN pg_catalog.concat(cn.nspname, '."', cl.collname,'"') ELSE '' END AS collname, @@ -25,7 +28,7 @@ SELECT INH.inheritedfrom, INH.inheritedid, att.attoptions, attfdwoptions, FROM pg_catalog.pg_attribute att LEFT JOIN - INH_TABLES as INH ON att.attname = INH.attname + INH_TABLES as INH ON att.attname = INH.name JOIN pg_catalog.pg_type t ON t.oid=atttypid JOIN diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_enabled_triggers.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_enabled_triggers.sql new file mode 100644 index 00000000000..c24eda653d7 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_enabled_triggers.sql @@ -0,0 +1 @@ +SELECT count(*) FROM pg_catalog.pg_trigger WHERE tgrelid={{tid}} AND tgisinternal = FALSE AND tgenabled = 'O' diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_table_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_table_columns.sql index 9510cccf471..80626220f18 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_table_columns.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/get_table_columns.sql @@ -1,6 +1,6 @@ {% if attrelid %} SELECT - a.attname, pg_catalog.format_type(a.atttypid, NULL) AS datatype, + a.attname as name, pg_catalog.format_type(a.atttypid, NULL) AS cltype, pg_catalog.quote_ident(n.nspname)||'.'||quote_ident(c.relname) as inheritedfrom, c.oid as inheritedid FROM diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/node.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/node.sql index ed65039a868..d1095dfaf53 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/node.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/node.sql @@ -1,6 +1,8 @@ SELECT c.oid, c.relname AS name, pg_catalog.pg_get_userbyid(relowner) AS owner, - ftoptions, nspname as basensp, description + ftoptions, nspname as basensp, description, + (SELECT count(*) FROM pg_catalog.pg_trigger WHERE tgrelid=c.oid AND tgisinternal = FALSE) AS triggercount, + (SELECT count(*) FROM pg_catalog.pg_trigger WHERE tgrelid=c.oid AND tgisinternal = FALSE AND tgenabled = 'O') AS has_enable_triggers FROM pg_catalog.pg_class c JOIN diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/update.sql index 78817bcbd4e..0ebb4ed4d95 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/update.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/templates/foreign_tables/sql/default/update.sql @@ -17,41 +17,42 @@ ALTER FOREIGN TABLE IF EXISTS {{ conn|qtIdent(o_data.basensp, name) }} {% for c in data.columns.deleted %} {% if (not c.inheritedfrom or c.inheritedfrom =='' or c.inheritedfrom == None or c.inheritedfrom == 'None' ) %} ALTER FOREIGN TABLE IF EXISTS {{ conn|qtIdent(o_data.basensp, name) }} - DROP COLUMN {{conn|qtIdent(c.attname)}}; + DROP COLUMN {{conn|qtIdent(c.name)}}; {% endif %} {% endfor -%} {% for c in data.columns.added %} {% if (not c.inheritedfrom or c.inheritedfrom =='' or c.inheritedfrom == None or c.inheritedfrom == 'None' ) %} ALTER FOREIGN TABLE IF EXISTS {{ conn|qtIdent(o_data.basensp, name) }} - ADD COLUMN {{conn|qtIdent(c.attname)}} {{ c.datatype }}{% if c.typlen %}({{c.typlen}}{% if c.precision %}, {{c.precision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %} + ADD COLUMN {{conn|qtIdent(c.name)}} {{ c.cltype }}{% if c.attlen %}({{c.attlen}}{% if c.attprecision %}, {{c.attprecision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %} {% if c.coloptions %} {% for o in c.coloptions %}{% if o.option is defined and o.value is defined %} {% if loop.first %} OPTIONS ({% endif %}{% if not loop.first %}, {% endif %}{{o.option}} {{o.value|qtLiteral(conn)}}{% if loop.last %}){% endif %}{% endif %} {% endfor %}{% endif %} {% if c.attnotnull %} NOT NULL{% else %} NULL{% endif %} -{% if c.typdefault is defined and c.typdefault is not none %} DEFAULT {{c.typdefault}}{% endif %} +{% if c.defval is defined and c.defval is not none %} DEFAULT {{c.defval}}{% endif %} {% if c.collname %} COLLATE {{c.collname}}{% endif %}; {% endif %} {% endfor -%} {% for c in data.columns.changed %} -{% set col_name = o_data['columns'][c.attnum]['attname'] %} -{% if c.attname != o_data['columns'][c.attnum]['attname'] %} -{% set col_name = c.attname %} +{% set col_name = o_data['columns'][c.attnum]['name'] %} +{% if c.name %}{% if c.name != o_data['columns'][c.attnum]['name'] %} +{% set col_name = c.name %} ALTER FOREIGN TABLE IF EXISTS {{ conn|qtIdent(o_data.basensp, name) }} - RENAME COLUMN {{conn|qtIdent(o_data['columns'][c.attnum]['attname'])}} TO {{conn|qtIdent(c.attname)}}; + RENAME COLUMN {{conn|qtIdent(o_data['columns'][c.attnum]['name'])}} TO {{conn|qtIdent(c.name)}}; +{% endif %} {% endif %} {% if c.attnotnull != o_data['columns'][c.attnum]['attnotnull'] %} ALTER FOREIGN TABLE IF EXISTS {{ conn|qtIdent(o_data.basensp, name) }} ALTER COLUMN {{conn|qtIdent(col_name)}}{% if c.attnotnull %} SET{% else %} DROP{% endif %} NOT NULL; {% endif %} -{% if c.datatype != o_data['columns'][c.attnum]['datatype'] or c.typlen != o_data['columns'][c.attnum]['typlen'] or -c.precision != o_data['columns'][c.attnum]['precision'] %} +{% if c.cltype != o_data['columns'][c.attnum]['cltype'] or c.attlen != o_data['columns'][c.attnum]['attlen'] or +c.attprecision != o_data['columns'][c.attnum]['attprecision'] %} ALTER FOREIGN TABLE IF EXISTS {{ conn|qtIdent(o_data.basensp, name) }} - ALTER COLUMN {{conn|qtIdent(col_name)}} TYPE {{ c.datatype }}{% if c.typlen %}({{c.typlen}}{% if c.precision %}, {{c.precision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %}; + ALTER COLUMN {{conn|qtIdent(col_name)}} TYPE {{ c.cltype }}{% if c.attlen %}({{c.attlen}}{% if c.attprecision %}, {{c.attprecision}}{% endif %}){% endif %}{% if c.isArrayType %}[]{% endif %}; {% endif %} {% if c.typdefault is defined and c.typdefault != o_data['columns'][c.attnum]['typdefault'] %} @@ -83,7 +84,7 @@ ALTER FOREIGN TABLE IF EXISTS {{ conn|qtIdent(o_data.basensp, name) }} ALTER COLUMN {{conn|qtIdent(col_name)}} OPTIONS (SET {% endif %}{% if not loop.first %}, {% endif %}{{o.option}} {{o.value|qtLiteral(conn)}}{% if loop.last %});{% endif %} {% endif %} {% endfor %} -{% endif -%} +{% endif %} {% endfor %} {% endif %} {% if data.inherits and data.inherits|length > 0%} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/foreign_tables_test_data.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/foreign_tables_test_data.json index e4833564a8a..727a5de608e 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/foreign_tables_test_data.json +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/foreign_tables_test_data.json @@ -9,8 +9,8 @@ "basensp": "schema_name", "columns": [ { - "attname": "ename", - "datatype": "text", + "name": "ename", + "cltype": "text", "coloptions": [] } ], @@ -41,8 +41,8 @@ "basensp": "schema_name", "columns": [ { - "attname": "ename", - "datatype": "text", + "name": "ename", + "cltype": "text", "coloptions": [] } ], @@ -72,8 +72,8 @@ "basensp": "schema_name", "columns": [ { - "attname": "ename", - "datatype": "text", + "name": "ename", + "cltype": "text", "coloptions": [] } ], @@ -255,8 +255,8 @@ "columns": { "added": [ { - "attname": "col2", - "datatype": "character varying[]", + "name": "col2", + "cltype": "character varying[]", "coloptions": [] } ], @@ -268,8 +268,8 @@ "value": "OptionValue" } ], - "attname": "emp", - "datatype": "\"char\"", + "name": "emp", + "cltype": "\"char\"", "typdefault": null, "attnotnull": false, "collname": "pg_catalog.\"default\"", diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/pg/default/test_foreign_table.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/pg/default/test_foreign_table.json index 58428d0a523..43883e68148 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/pg/default/test_foreign_table.json +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/pg/default/test_foreign_table.json @@ -11,7 +11,8 @@ "fdwoptions": [] }, "store_object_id": "True" - }, { + }, + { "type": "create", "name": "Create foreign server for foreign table", "endpoint": "NODE-foreign_server.obj", @@ -20,7 +21,8 @@ "name":"test_fs_for_foreign_table" }, "store_object_id": "True" - }, { + }, + { "type": "create", "name": "Create Foreign Table with all options", "endpoint": "NODE-foreign_table.obj", @@ -34,12 +36,12 @@ "description":"Test Comment", "ftsrvname":"test_fs_for_foreign_table", "columns":[{ - "attname":"col1", - "datatype":"bigint", + "name":"col1", + "cltype":"bigint", "coloptions":[] },{ - "attname":"col2", - "datatype":"text", + "name":"col2", + "cltype":"text", "coloptions":[] }], "constraints":[{ @@ -107,12 +109,12 @@ "description":"Test Comment", "columns": { "added": [{ - "attname":"col1", - "datatype":"bigint", + "name":"col1", + "cltype":"bigint", "coloptions":[] },{ - "attname":"col2", - "datatype":"text", + "name":"col2", + "cltype":"text", "coloptions":[] }] } @@ -182,17 +184,17 @@ }, "columns": { "changed": [{ - "attname": "col1", + "name": "col1", "attnum": 1, "attoptions": null, "collname": "", "coloptions": [], - "datatype": "integer", + "cltype": "integer", "fulltype": "bigint" }], "deleted": [{ - "attname":"col2", - "datatype":"text" + "name":"col2", + "cltype":"text" }] } }, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/ppas/default/test_foreign_table.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/ppas/default/test_foreign_table.json index 19889d9bc01..addec340af0 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/ppas/default/test_foreign_table.json +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/tests/ppas/default/test_foreign_table.json @@ -34,12 +34,12 @@ "description":"Test Comment", "ftsrvname":"test_fs_for_foreign_table", "columns":[{ - "attname":"col1", - "datatype":"bigint", + "name":"col1", + "cltype":"bigint", "coloptions":[] },{ - "attname":"col2", - "datatype":"text", + "name":"col2", + "cltype":"text", "coloptions":[] }], "constraints":[{ @@ -107,12 +107,12 @@ "description":"Test Comment", "columns": { "added": [{ - "attname":"col1", - "datatype":"bigint", + "name":"col1", + "cltype":"bigint", "coloptions":[] },{ - "attname":"col2", - "datatype":"text", + "name":"col2", + "cltype":"text", "coloptions":[] }] } @@ -182,17 +182,17 @@ }, "columns": { "changed": [{ - "attname": "col1", + "name": "col1", "attnum": 1, "attoptions": null, "collname": "", "coloptions": [], - "datatype": "integer", + "cltype": "integer", "fulltype": "bigint" }], "deleted": [{ - "attname":"col2", - "datatype":"text" + "name":"col2", + "cltype":"text" }] } }, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/__init__.py index 4eaabced6c3..8b3f6c4adf4 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/__init__.py @@ -25,6 +25,7 @@ make_response as ajax_response, gone from pgadmin.browser.server_groups.servers.databases.schemas.tables.\ columns import utils as column_utils +from pgadmin.utils.compile_template_name import compile_template_path from pgadmin.utils.driver import get_driver from config import PG_DEFAULT_DRIVER from pgadmin.utils.ajax import ColParamsJSONDecoder @@ -214,6 +215,9 @@ def wrap(*args, **kwargs): self.template_path = self.BASE_TEMPLATE_PATH.format( self.manager.version) + self.foreign_table_column_template_path = compile_template_path( + 'foreign_table_columns/sql', self.manager.version) + # Allowed ACL for column 'Select/Update/Insert/References' self.acl = ['a', 'r', 'w', 'x'] @@ -420,7 +424,11 @@ def create(self, gid, sid, did, scid, tid): column_utils.type_formatter(data['cltype']) data = column_utils.convert_length_precision_to_string(data) - SQL = render_template("/".join([self.template_path, + # Check if node is foreign_table_column + template_path = self.foreign_table_column_template_path \ + if self.node_type == 'foreign_table_column' else self.template_path + + SQL = render_template("/".join([template_path, self._CREATE_SQL]), data=data, conn=self.conn) status, res = self.conn.execute_scalar(SQL) @@ -493,7 +501,12 @@ def delete(self, gid, sid, did, scid, tid, clid=None): data['schema'] = self.schema data['table'] = self.table - SQL = render_template("/".join([self.template_path, + # Check if node is foreign_table_column + template_path = self.foreign_table_column_template_path \ + if self.node_type == 'foreign_table_column' \ + else self.template_path + + SQL = render_template("/".join([template_path, self._DELETE_SQL]), data=data, conn=self.conn) status, res = self.conn.execute_scalar(SQL) @@ -649,9 +662,15 @@ def _get_sql_for_create(self, data, is_sql): if 'attacl' in data: data['attacl'] = parse_priv_to_db(data['attacl'], self.acl) + + # Check if node is foreign_table_column + template_path = self.foreign_table_column_template_path \ + if self.node_type == 'foreign_table_column' \ + else self.template_path + # If the request for new object which do not have did sql = render_template( - "/".join([self.template_path, self._CREATE_SQL]), + "/".join([template_path, self._CREATE_SQL]), data=data, conn=self.conn, is_sql=is_sql ) @@ -687,6 +706,11 @@ def get_sql(self, scid, tid, clid, data, is_sql=False): data = column_utils.convert_length_precision_to_string(data) if clid is not None: + # Check if node is foreign_table_column + template_path = self.foreign_table_column_template_path \ + if self.node_type == 'foreign_table_column' \ + else self.template_path + sql = render_template( "/".join([self.template_path, self._PROPERTIES_SQL]), tid=tid, clid=clid, @@ -714,7 +738,7 @@ def get_sql(self, scid, tid, clid, data, is_sql=False): self._parse_acl_to_db_parsing(data, old_data) sql = render_template( - "/".join([self.template_path, self._UPDATE_SQL]), + "/".join([template_path, self._UPDATE_SQL]), data=data, o_data=old_data, conn=self.conn, is_view_only=is_view_only ) @@ -778,8 +802,12 @@ def sql(self, gid, sid, did, scid, tid, clid): self.conn, data['schema'], data['table'], data['name']) ) + # Join delete sql + template_path = self.foreign_table_column_template_path \ + if self.node_type == 'foreign_table_column' \ + else self.template_path sql_header += render_template( - "/".join([self.template_path, self._DELETE_SQL]), + "/".join([template_path, self._DELETE_SQL]), data=data, conn=self.conn ) SQL = sql_header + '\n\n' + SQL diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py index a495ac6f719..4e717338448 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py @@ -155,6 +155,10 @@ def column_formatter(conn, tid, clid, data, edit_types_list=None, data['seclabels'] = seclabels + # Get formatted Column Options + if 'attfdwoptions' in data and data['attfdwoptions'] != '': + data['coloptions'] = _parse_options_for_column(data['attfdwoptions']) + # We need to parse & convert ACL coming from database to json format SQL = render_template("/".join([template_path, 'acl.sql']), tid=tid, clid=clid) @@ -192,6 +196,40 @@ def column_formatter(conn, tid, clid, data, edit_types_list=None, return data +def _parse_options_for_column(db_variables): + """ + Function to format the output for variables. + + Args: + db_variables: Variable object + + Expected Object Format: + ['option1=value1', ..] + where: + user_name and database are optional + Returns: + Variable Object in below format: + { + 'variables': [ + {'name': 'var_name', 'value': 'var_value', + 'user_name': 'user_name', 'database': 'database_name'}, + ...] + } + where: + user_name and database are optional + """ + variables_lst = [] + + if db_variables is not None: + for row in db_variables: + # The value may contain equals in string, split on + # first equals only + var_name, var_value = row.split("=", 1) + var_dict = {'option': var_name, 'value': var_value} + variables_lst.append(var_dict) + return variables_lst + + @get_template_path def get_formatted_columns(conn, tid, data, other_columns, table_or_type, template_path=None): diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/properties.sql index 51834498617..085053b6501 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/12_plus/properties.sql @@ -1,5 +1,5 @@ SELECT DISTINCT ON (att.attnum) att.attname as name, att.atttypid, att.attlen, att.attnum, att.attndims, - att.atttypmod, att.attacl, att.attnotnull, att.attoptions, att.attstattarget, + att.atttypmod, att.attacl, att.attnotnull, att.attoptions, att.attfdwoptions, att.attstattarget, att.attstorage, att.attidentity, pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS defval, pg_catalog.format_type(ty.oid,NULL) AS typname, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql index 5ac870ab7ce..1d12ef0c4f0 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/columns/sql/default/properties.sql @@ -1,5 +1,5 @@ SELECT att.attname as name, att.atttypid, att.attlen, att.attnum, att.attndims, - att.atttypmod, att.attacl, att.attnotnull, att.attoptions, att.attstattarget, + att.atttypmod, att.attacl, att.attnotnull, att.attoptions, att.attfdwoptions, att.attstattarget, att.attstorage, att.attidentity, pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS defval, pg_catalog.format_type(ty.oid,NULL) AS typname, diff --git a/web/regression/javascript/schema_ui_files/foreign_table.ui.spec.js b/web/regression/javascript/schema_ui_files/foreign_table.ui.spec.js index 9246125ad49..69a82972005 100644 --- a/web/regression/javascript/schema_ui_files/foreign_table.ui.spec.js +++ b/web/regression/javascript/schema_ui_files/foreign_table.ui.spec.js @@ -228,20 +228,6 @@ describe('ForeignTableColumnSchema', ()=>{ mount(getEditView(schemaObj, getInitData)); }); - it('column editable', ()=>{ - let state = {}; - let editable = _.find(schemaObj.fields, (f)=>f.id=='attname').editable; - let status = editable(state); - expect(status).toBe(true); - }); - - it('typdefault editable', ()=>{ - let state = {}; - let editable = _.find(schemaObj.fields, (f)=>f.id=='typdefault').editable; - let status = editable(state); - expect(status).toBe(true); - }); - it('typdefault_edit', ()=>{ let defaultSchemaObj = new ForeignTableSchema( ()=>new MockSchema(), diff --git a/web/regression/re_sql/tests/test_resql.py b/web/regression/re_sql/tests/test_resql.py index fdfcaf33776..3c3fd9d42c6 100644 --- a/web/regression/re_sql/tests/test_resql.py +++ b/web/regression/re_sql/tests/test_resql.py @@ -710,6 +710,8 @@ def store_object_ids(self, object_id, object_data, endpoint): self.parent_ids['fsid'] = object_id elif endpoint.__contains__("NODE-role.obj"): object_name = object_data['rolname'] + elif endpoint.__contains__("NODE-foreign_table"): + self.parent_ids['tid'] = object_id # Store object id with object name self.all_object_ids[object_name] = object_id diff --git a/web/webpack.config.js b/web/webpack.config.js index b73c4e30108..67d96e14ab8 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -480,6 +480,7 @@ module.exports = [{ 'pure|pgadmin.node.user_mapping', 'pure|pgadmin.node.schema', 'pure|pgadmin.node.catalog', + 'pure|pgadmin.node.foreign_table_column', 'pure|pgadmin.node.catalog_object', 'pure|pgadmin.node.collation', 'pure|pgadmin.node.domain', diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 37e69e4e730..12a4df75806 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -115,6 +115,7 @@ let webpackShimConfig = { 'pgadmin.node.foreign_key': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key'), 'pgadmin.node.foreign_server': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/static/js/foreign_server'), 'pgadmin.node.foreign_table': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/static/js/foreign_table'), + 'pgadmin.node.foreign_table_column': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/foreign_tables/foreign_table_columns/static/js/foreign_table_column'), 'pgadmin.node.fts_configuration': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration'), 'pgadmin.node.fts_dictionary': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/fts_dictionaries/static/js/fts_dictionary'), 'pgadmin.node.fts_parser': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/fts_parsers/static/js/fts_parser'),