Implement column node and its features for foreign table. #640, #6373, #6674

This commit is contained in:
Yogesh Mahajan 2023-09-26 15:03:03 +05:30 committed by GitHub
parent 5736e173ea
commit 3cd547f879
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 2037 additions and 159 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -10,11 +10,10 @@
"""Implements the Foreign Table Module."""
import sys
import traceback
from functools import wraps
import json
from flask import render_template, make_response, request, jsonify, \
from flask import render_template, request, jsonify, \
current_app
from flask_babel import gettext
@ -29,10 +28,11 @@ from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
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 +88,10 @@ class ForeignTableModule(SchemaChildModule):
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 +220,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
'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 +456,9 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
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 +1167,14 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
"""
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 +1244,44 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
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 +1294,11 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
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 +1425,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
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 +1466,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
# 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 +1507,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
# 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 +1708,66 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
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)

View File

@ -5,9 +5,10 @@ foreign tables.
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

View File

@ -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)

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#e1fff9;}.cls-2{fill:none;stroke:#aecec8;stroke-linejoin:round;stroke-width:0.75px;}.cls-3{fill:#16a085;}</style></defs><title>coll-column</title><g id="_2" data-name="2"><rect class="cls-1" x="4.15" y="1.95" width="5.5" height="9.9" rx="1" ry="1"/><line class="cls-2" x1="4.7" y1="6.9" x2="9.1" y2="6.9"/><line class="cls-2" x1="4.7" y1="9.1" x2="9.1" y2="9.1"/><path class="cls-3" d="M4.9,5.18l4,0v5.55a.35.35,0,0,1-.35.35H5.25a.35.35,0,0,1-.35-.35V5.18m-.75-.75v6.32a1.1,1.1,0,0,0,1.1,1.1h3.3a1.1,1.1,0,0,0,1.1-1.1V4.46l-5.5,0Z"/><path class="cls-3" d="M8.55,2.7A.35.35,0,0,1,8.9,3V4.5h-4V3a.35.35,0,0,1,.35-.35h3.3m0-.75H5.25A1.1,1.1,0,0,0,4.15,3v2.2h5.5V3A1.1,1.1,0,0,0,8.55,2Z"/><rect class="cls-1" x="6.35" y="4.15" width="5.5" height="9.9" rx="1" ry="1"/><line class="cls-2" x1="6.9" y1="9.1" x2="11.3" y2="9.1"/><line class="cls-2" x1="6.9" y1="11.3" x2="11.3" y2="11.3"/><path class="cls-3" d="M7.1,7.38l4,0V13a.35.35,0,0,1-.35.35H7.45A.35.35,0,0,1,7.1,13V7.38m-.75-.75V13A1.1,1.1,0,0,0,7.45,14h3.3a1.1,1.1,0,0,0,1.1-1.1V6.66l-5.5,0Z"/><path class="cls-3" d="M10.75,4.9a.35.35,0,0,1,.35.35V6.7h-4V5.25a.35.35,0,0,1,.35-.35h3.3m0-.75H7.45a1.1,1.1,0,0,0-1.1,1.1v2.2h5.5V5.25a1.1,1.1,0,0,0-1.1-1.1Z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20"><defs><style>.cls-1{fill:#e1fff9;}.cls-2{fill:none;stroke:#aecec8;stroke-linejoin:round;stroke-width:0.75px;}.cls-3{fill:#16a085;}</style></defs><title>column</title><g id="_2" data-name="2"><rect class="cls-1" x="5.3" y="2.15" width="5.4" height="11.7" rx="1" ry="1"/><line class="cls-2" x1="5.75" y1="8" x2="10.25" y2="8"/><line class="cls-2" x1="5.75" y1="10.7" x2="10.25" y2="10.7"/><path class="cls-3" d="M6,5.83l3.9,0V13a.15.15,0,0,1-.15.15H6.2A.15.15,0,0,1,6,13V5.83M5.3,5.08V13a.9.9,0,0,0,.9.9H9.8a.9.9,0,0,0,.9-.9V5.1l-5.4,0Z"/><path class="cls-3" d="M9.8,2.9A.15.15,0,0,1,10,3V5H6V3A.15.15,0,0,1,6.2,2.9H9.8m0-.75H6.2a.9.9,0,0,0-.9.9v2.7h5.4V3a.9.9,0,0,0-.9-.9Z"/></g></svg>

After

Width:  |  Height:  |  Size: 768 B

View File

@ -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 multiple 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'];
});

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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);

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,213 @@
{
"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",
"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",
"coloptions":{"added":[{"option":"column_name","value":"test"}]}
},
"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_$%{}[]()&*^!@\"'`\\/#"
}
}
]}

View File

@ -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 []

View File

@ -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
}
]
}

View File

@ -0,0 +1,10 @@
ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#"
RENAME "col_1_$%{}[]()&*^!@""'`\/#" TO "new_col_1_$%{}[]()&*^!@""'`\/#";
ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#"
ALTER COLUMN "new_col_1_$%{}[]()&*^!@""'`\/#" OPTIONS (ADD column_name 'test');
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';

View File

@ -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 OPTIONS (column_name 'test') NOT NULL DEFAULT 1;
COMMENT ON COLUMN public."foreign_table_2_$%{}[]()&*^!@""'`\/#"."new_col_1_$%{}[]()&*^!@""'`\/#"
IS 'Comment for alter';

View File

@ -0,0 +1,2 @@
ALTER FOREIGN TABLE IF EXISTS public."foreign_table_2_$%{}[]()&*^!@""'`\/#"
ALTER COLUMN "col_2_$%{}[]()&*^!@""'`\/#" SET DEFAULT 'changed default value';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -0,0 +1,130 @@
{
"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",
"coloptions":{"added":[{"option":"column_name","value":"test"}]}
},
"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_$%{}[]()&*^!@\"'`\\/#"
}
}
]}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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(

View File

@ -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: 'datatype', label: gettext('Data type'), minWidth: 150,
group: gettext('Definition'), noEmpty: true,
id: 'description', label: gettext('Comment'), cell: 'text',
type: 'multiline', mode: ['properties', 'create', 'edit'],
},
{
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; }

View File

@ -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 %}

View File

@ -0,0 +1 @@
ALTER FOREIGN TABLE IF EXISTS {{conn|qtIdent(data.schema, data.table)}} DROP COLUMN IF EXISTS {{conn|qtIdent(data.name)}};

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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;

View File

@ -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;

View File

@ -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 %}

View File

@ -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

View File

@ -0,0 +1 @@
SELECT count(*) FROM pg_catalog.pg_trigger WHERE tgrelid={{tid}} AND tgisinternal = FALSE AND tgenabled = 'O'

View File

@ -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

View File

@ -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

View File

@ -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%}

View File

@ -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\"",

View File

@ -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"
}]
}
},

View File

@ -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"
}]
}
},

View File

@ -25,6 +25,7 @@ from pgadmin.utils.ajax import make_json_response, internal_server_error, \
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 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
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 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
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 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
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 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
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 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
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 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
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 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
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

View File

@ -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)
@ -186,12 +190,44 @@ def column_formatter(conn, tid, clid, data, edit_types_list=None,
# We will need present type in edit mode
edit_types_list.append(data['typname'])
data['edit_types'] = sorted(edit_types_list)
data['cltype'] = DataTypeReader.parse_type_name(data['cltype'])
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):

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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(),

View File

@ -710,6 +710,8 @@ class ReverseEngineeredSQLTestCases(BaseTestGenerator):
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

View File

@ -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',

View File

@ -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'),