Added support of Aggregate and Operator node in view-only mode. Fixes #3834

This commit is contained in:
Akshay Joshi 2021-10-29 16:14:33 +05:30
parent 7b85253465
commit 39992a817d
30 changed files with 1698 additions and 4 deletions

View File

@ -9,7 +9,7 @@ This release contains a number of bug fixes and new features since the release o
New features
************
| `Issue #3834 <https://redmine.postgresql.org/issues/3834>`_ - Added support of Aggregate and Operator node in view-only mode.
Housekeeping
************

View File

@ -0,0 +1,374 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
""" Implements Aggregate Node """
from functools import wraps
from flask import render_template
from flask_babelex import gettext
import pgadmin.browser.server_groups.servers.databases as database
from config import PG_DEFAULT_DRIVER
from pgadmin.browser.server_groups.servers.databases.schemas.utils \
import SchemaChildModule
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
class AggregateModule(SchemaChildModule):
"""
class AggregateModule(SchemaChildModule)
A module class for Aggregate node derived from SchemaChildModule.
Methods:
-------
* __init__(*args, **kwargs)
- Method is used to initialize the Aggregate and it's base module.
* get_nodes(gid, sid, did, scid, agid)
- 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 = 'aggregate'
_COLLECTION_LABEL = gettext("Aggregates")
def __init__(self, *args, **kwargs):
"""
Method is used to initialize the AggregateModule and it's base module.
Args:
*args:
**kwargs:
"""
super(AggregateModule, self).__init__(*args, **kwargs)
self.min_ver = 90100
self.max_ver = None
def get_nodes(self, gid, sid, did, scid):
"""
Generate the collection node
"""
yield self.generate_browser_collection_node(scid)
@property
def script_load(self):
"""
Load the module script for database, when any of the database node is
initialized.
"""
return database.DatabaseModule.node_type
@property
def node_inode(self):
return False
blueprint = AggregateModule(__name__)
class AggregateView(PGChildNodeView):
"""
This class is responsible for generating routes for Aggregate node
Methods:
-------
* __init__(**kwargs)
- Method is used to initialize the AggregateView and it's base view.
* check_precondition()
- This function will behave as a decorator which will checks
database connection before running view, it will also attaches
manager,conn & template_path properties to self
* list()
- This function is used to list all the Aggregate nodes within that
collection.
* nodes()
- This function will used to create all the child node within that
collection, Here it will create all the Aggregate node.
* properties(gid, sid, did, scid, agid)
- This function will show the properties of the selected Aggregate node
* sql(gid, sid, did, scid):
- This function will generate sql to show it in sql pane for the
selected Aggregate node.
"""
node_type = blueprint.node_type
node_label = "Aggregate"
parent_ids = [
{'type': 'int', 'id': 'gid'},
{'type': 'int', 'id': 'sid'},
{'type': 'int', 'id': 'did'},
{'type': 'int', 'id': 'scid'}
]
ids = [
{'type': 'int', 'id': 'agid'}
]
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'children': [{'get': 'children'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}]
})
def check_precondition(f):
"""
This function will behave as a decorator which will checks
database connection before running view, it will also attaches
manager,conn & template_path properties to self
"""
@wraps(f)
def wrap(*args, **kwargs):
# Here args[0] will hold self & kwargs will hold gid,sid,did
self = args[0]
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
kwargs['sid']
)
self.conn = self.manager.connection(did=kwargs['did'])
self.datlastsysoid = \
self.manager.db_info[kwargs['did']]['datlastsysoid'] \
if self.manager.db_info is not None and \
kwargs['did'] in self.manager.db_info else 0
self.datistemplate = False
if (
self.manager.db_info is not None and
kwargs['did'] in self.manager.db_info and
'datistemplate' in self.manager.db_info[kwargs['did']]
):
self.datistemplate = self.manager.db_info[
kwargs['did']]['datistemplate']
# Set the template path for the SQL scripts
self.template_path = compile_template_path(
'aggregates/sql/',
self.manager.server_type,
self.manager.version
)
return f(*args, **kwargs)
return wrap
@check_precondition
def list(self, gid, sid, did, scid):
"""
This function is used to list all the aggregate nodes within that
collection.
Args:
gid: Server group ID
sid: Server ID
did: Database ID
scid: Schema ID
Returns:
JSON of available aggregate nodes
"""
SQL = render_template("/".join([self.template_path,
self._NODES_SQL]), scid=scid)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
return ajax_response(
response=res['rows'],
status=200
)
@check_precondition
def nodes(self, gid, sid, did, scid):
"""
This function will used to create all the child node within that
collection.
Here it will create all the aggregate node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
Returns:
JSON of available aggregate child nodes
"""
res = []
SQL = render_template("/".join([self.template_path,
self._NODES_SQL]), scid=scid)
status, rset = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=rset)
for row in rset['rows']:
res.append(
self.blueprint.generate_browser_node(
row['oid'],
scid,
row['name'],
icon="icon-aggregate"
))
return make_json_response(
data=res,
status=200
)
@check_precondition
def node(self, gid, sid, did, scid, agid):
"""
This function will fetch properties of the aggregate node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
agid: Aggregate ID
Returns:
JSON of given aggregate node
"""
SQL = render_template("/".join([self.template_path,
self._NODES_SQL]), agid=agid)
status, rset = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=rset)
for row in rset['rows']:
return make_json_response(
data=self.blueprint.generate_browser_node(
row['oid'],
scid,
row['name'],
icon="icon-aggregate"
),
status=200
)
return gone(self.not_found_error_msg())
@check_precondition
def properties(self, gid, sid, did, scid, agid):
"""
This function will show the properties of the selected aggregate node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
scid: Schema ID
agid: Aggregate ID
Returns:
JSON of selected aggregate node
"""
status, res = self._fetch_properties(scid, agid)
if not status:
return res
return ajax_response(
response=res,
status=200
)
def _fetch_properties(self, scid, agid):
"""
This function fetch the properties for the specified object.
:param scid: Schema ID
:param agid: Aggregate ID
"""
SQL = render_template("/".join([self.template_path,
self._PROPERTIES_SQL]),
scid=scid, agid=agid,
datlastsysoid=self.datlastsysoid)
status, res = self.conn.execute_dict(SQL)
if not status:
return False, internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return False, gone(self.not_found_error_msg())
res['rows'][0]['is_sys_obj'] = (
res['rows'][0]['oid'] <= self.datlastsysoid or self.datistemplate)
return True, res['rows'][0]
@check_precondition
def sql(self, gid, sid, did, scid, agid, **kwargs):
"""
This function will generates reverse engineered sql for aggregate
object
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
agid: Aggregate ID
json_resp: True then return json response
"""
SQL = render_template("/".join([self.template_path,
self._PROPERTIES_SQL]),
scid=scid, agid=agid)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(self.not_found_error_msg())
data = res['rows'][0]
SQL = render_template("/".join([self.template_path,
self._CREATE_SQL]),
data=data)
sql_header = "-- Aggregate: {0};\n\n-- ".format(data['name'])
sql_header += render_template("/".join([self.template_path,
self._DELETE_SQL]),
data=data)
SQL = sql_header + '\n' + SQL.strip('\n')
return ajax_response(response=SQL)
AggregateView.register_node_view(blueprint)

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#cadff1;stroke:#6f6f6f;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{fill:#9cbe44;}.cls-3{fill:#888;}</style></defs><rect class="cls-1" x="4.5" y="3.23" width="7" height="9.54" rx="1"/><rect class="cls-2" x="6.02" y="4.84" width="4" height="2"/><rect class="cls-3" x="6.14" y="7.66" width="1.5" height="1.36"/><rect class="cls-3" x="8.52" y="7.66" width="1.5" height="1.36"/><rect class="cls-3" x="6.14" y="10.02" width="1.5" height="1.36"/><rect class="cls-3" x="8.52" y="10.02" width="1.5" height="1.36"/></svg>

After

Width:  |  Height:  |  Size: 635 B

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#cadff1;stroke:#6f6f6f;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{fill:#9cbe44;}.cls-3{fill:#888;}</style></defs><rect class="cls-1" x="2.48" y="1.21" width="7" height="9.54" rx="1"/><rect class="cls-2" x="4" y="2.83" width="4" height="2"/><rect class="cls-3" x="4.12" y="5.64" width="1.5" height="1.36"/><rect class="cls-3" x="6.5" y="5.64" width="1.5" height="1.36"/><rect class="cls-3" x="4.12" y="8" width="1.5" height="1.36"/><rect class="cls-3" x="6.5" y="8" width="1.5" height="1.36"/><rect class="cls-1" x="4.5" y="3.23" width="7" height="9.54" rx="1"/><rect class="cls-2" x="6.02" y="4.84" width="4" height="2"/><rect class="cls-3" x="6.14" y="7.66" width="1.5" height="1.36"/><rect class="cls-3" x="8.52" y="7.66" width="1.5" height="1.36"/><rect class="cls-3" x="6.14" y="10.02" width="1.5" height="1.36"/><rect class="cls-3" x="8.52" y="10.02" width="1.5" height="1.36"/><rect class="cls-1" x="6.52" y="5.25" width="7" height="9.54" rx="1"/><rect class="cls-2" x="8.04" y="6.86" width="4" height="2"/><rect class="cls-3" x="8.16" y="9.67" width="1.5" height="1.36"/><rect class="cls-3" x="10.54" y="9.67" width="1.5" height="1.36"/><rect class="cls-3" x="8.16" y="12.03" width="1.5" height="1.36"/><rect class="cls-3" x="10.54" y="12.03" width="1.5" height="1.36"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,86 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import AggregateSchema from './aggregate.ui';
define('pgadmin.node.aggregate', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection',
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, schemaChild) {
if (!pgBrowser.Nodes['coll-aggregate']) {
pgAdmin.Browser.Nodes['coll-aggregate'] =
pgAdmin.Browser.Collection.extend({
node: 'aggregate',
label: gettext('Aggregates'),
type: 'coll-aggregate',
columns: ['name', 'owner', 'description'],
canDrop: false,
canDropCascade: false,
});
}
if (!pgBrowser.Nodes['aggregate']) {
pgAdmin.Browser.Nodes['aggregate'] = schemaChild.SchemaChildNode.extend({
type: 'aggregate',
sqlAlterHelp: 'sql-alteraggregate.html',
sqlCreateHelp: 'sql-createaggregate.html',
label: gettext('Aggregate'),
collection_type: 'coll-aggregate',
hasSQL: true,
hasDepends: false,
canDrop: false,
canDropCascade: false,
Init: function() {
/* Avoid mulitple registration of menus */
if (this.initialized)
return;
this.initialized = true;
},
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'oid',
// Default values!
initialize: function(attrs, args) {
var isNew = (_.size(attrs) === 0);
if (isNew) {
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
var schemaInfo = args.node_info.schema;
this.set({'owner': userInfo.name}, {silent: true});
this.set({'schema': schemaInfo._label}, {silent: true});
}
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
},
schema: [{
id: 'name', label: gettext('Aggregate'), cell: 'string',
type: 'text', mode: ['properties', 'create', 'edit'],
},{
id: 'owner', label: gettext('Owner'), cell: 'string',
type: 'text', mode: ['properties', 'create', 'edit'],
control: 'node-list-by-name',
node: 'role',
},{
id: 'description', label: gettext('Comment'), cell: 'string',
type: 'multiline', mode: ['properties', 'create', 'edit'],
}
],
}),
getSchema: ()=>{
let schema = new AggregateSchema();
return schema;
}
});
}
return pgBrowser.Nodes['aggregate'];
});

View File

@ -0,0 +1,185 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import gettext from 'sources/gettext';
export default class AggregateSchema extends BaseUISchema {
constructor(fieldOptions = {},initValues) {
super({
name: undefined,
oid: undefined,
owner: undefined,
description: undefined,
schema: null,
input_types: undefined,
state_type: undefined,
state_func: undefined,
state_data_size: 0,
final_type: undefined,
final_func: undefined,
final_func_modify: undefined,
final_extra_param: undefined,
initial_val: undefined,
moving_state_type: undefined,
moving_state_func: undefined,
moving_state_data_size: 0,
moving_final_type: undefined,
moving_final_func: undefined,
moving_final_func_modify: undefined,
moving_final_extra_param: undefined,
moving_initial_val: undefined,
moving_inverse_func: undefined,
combine_func: undefined,
serialization_func: undefined,
deserialization_func: undefined,
sort_oper: undefined,
...initValues
});
this.fieldOptions = fieldOptions;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'name', label: gettext('Name'),
type: 'text', readonly: true,
},
{
id: 'oid', label: gettext('OID'),
type: 'text', mode: ['properties'],
},
{
id: 'owner', label: gettext('Owner'),
type: 'text', readonly: true,
},
{
id: 'schema', label: gettext('Schema'),
mode: ['create', 'edit'],
type: 'text', readonly: true,
},
{
id: 'is_sys_obj', label: gettext('System aggregate?'),
cell: 'boolean', type: 'switch', mode: ['properties'],
},
{
id: 'description', label: gettext('Comment'),
type: 'multiline', mode: ['properties', 'create', 'edit'],
readonly: true,
},
{
id: 'input_types', label: gettext('Input types'),
group: gettext('Options'),
type: 'text', readonly: true,
},
{
id: 'state_type', label: gettext('State type'),
group: gettext('Options'),
type: 'text', readonly: true,
},
{
id: 'state_func', label: gettext('State function'),
group: gettext('Options'),
type: 'text', readonly: true,
},
{
id: 'state_data_size', label: gettext('State data size'),
group: gettext('Options'),
type: 'text', readonly: true,
},
{
id: 'final_type', label: gettext('Final type'),
group: gettext('Options'),
type: 'text', readonly: true,
},
{
id: 'final_func', label: gettext('Final function'),
group: gettext('Options'),
type: 'text', readonly: true,
},
{
id: 'final_func_modify', label: gettext('Final function modify'),
group: gettext('Options'), min_version: 110000,
type: 'text', readonly: true,
},
{
id: 'final_extra_param', label: gettext('Pass extra arguments to final function'),
group: gettext('Options'), type: 'switch', readonly: true,
},
{
id: 'initial_val', label: gettext('Initial condition'),
group: gettext('Options'),
type: 'text', readonly: true,
},
{
id: 'moving_state_type', label: gettext('State type'),
group: gettext('Moving Options'),
type: 'text', readonly: true,
},
{
id: 'moving_state_func', label: gettext('State function'),
group: gettext('Moving Options'),
type: 'text', readonly: true,
},
{
id: 'moving_state_data_size', label: gettext('State data size'),
group: gettext('Moving Options'),
type: 'text', readonly: true,
},
{
id: 'moving_final_func', label: gettext('Final function'),
group: gettext('Moving Options'),
type: 'text', readonly: true,
},
{
id: 'moving_final_func_modify', label: gettext('Final function modify'),
group: gettext('Moving Options'), min_version: 110000,
type: 'text', readonly: true,
},
{
id: 'moving_final_extra_param', label: gettext('Pass extra arguments to final function'),
group: gettext('Moving Options'), type: 'switch', readonly: true,
},
{
id: 'moving_inverse_func', label: gettext('Inverse function'),
group: gettext('Moving Options'),
type: 'text', readonly: true,
},
{
id: 'moving_initial_val', label: gettext('Initial condition'),
group: gettext('Moving Options'),
type: 'text', readonly: true,
},
{
id: 'sort_oper', label: gettext('Sort operator'),
group: gettext('Advanced'),
type: 'text', readonly: true,
},
{
id: 'combine_func', label: gettext('Combine function'),
group: gettext('Advanced'),
type: 'text', readonly: true,
},
{
id: 'serialization_func', label: gettext('Serialization function'),
group: gettext('Advanced'),
type: 'text', readonly: true,
},
{
id: 'deserialization_func', label: gettext('Deserialization function'),
group: gettext('Advanced'),
type: 'text', readonly: true,
},
];
}
}

View File

@ -0,0 +1,24 @@
{% if data %}
CREATE AGGREGATE {{conn|qtIdent(data.schema, data.name)}}({% if data.input_types %}{{data.input_types}}{% endif %}) (
SFUNC = {{data.state_func}},
STYPE = {{data.state_type}} {% if data.state_data_size %},
SSPACE = {{data.state_data_size}}{% endif %}{% if data.final_func %},
FINALFUNC = {{data.final_func}}{% endif %}{% if data.final_extra_param %},
FINALFUNC_EXTRA{% endif %}{% if data.final_func_modify %},
FINALFUNC_MODIFY = {{data.final_func_modify}}{% endif %}{% if data.combine_func %},
COMBINEFUNC = {{data.combine_func}}{% endif %}{% if data.serialization_func %},
SERIALFUNC = {{data.serialization_func}}{% endif %}{% if data.deserialization_func %},
DESERIALFUNC = {{data.deserialization_func}}{% endif %}{% if data.initial_val %},
INITCOND = '{{data.initial_val}}'::text{% endif %}{% if data.moving_state_func %},
MSFUNC = {{data.moving_state_func}}{% endif %}{% if data.moving_inverse_func %},
MINVFUNC = {{data.moving_inverse_func}}{% endif %}{% if data.moving_state_type %},
MSTYPE = {{data.moving_state_type}}{% endif %}{% if data.moving_state_data_size %},
MSSPACE = {{data.moving_state_data_size}}{% endif %}{% if data.moving_final_func %},
MFINALFUNC = {{data.moving_final_func}}{% endif %}{% if data.moving_final_extra_param %},
MFINALFUNC_EXTRA{% endif %}{% if data.moving_final_func_modify %},
MFINALFUNC_MODIFY = {{data.moving_final_func_modify}}{% endif %}{% if data.moving_initial_val %},
MINITCOND = '{{data.moving_initial_val}}'::text{% endif %}{% if data.sort_oper %},
SORTOP = {{data.sort_oper}}{% endif %}
);
{% endif %}

View File

@ -0,0 +1,47 @@
SELECT aggfnoid::oid as oid, proname as name, ns.nspname as schema,
pg_catalog.pg_get_userbyid(proowner) as owner,
COALESCE(pg_catalog.pg_get_function_arguments(aggfnoid::oid), '') as input_types, proacl,
CASE WHEN ag.aggkind = 'n' THEN 'normal'
WHEN ag.aggkind = 'o' THEN 'ordered-set'
WHEN ag.aggkind = 'h' THEN 'hypothetical-set'
ELSE 'unknown' END as kind,
CASE WHEN aggtransfn = '-'::regproc THEN null ELSE aggtransfn END as state_func,
CASE WHEN aggfinalfn = '-'::regproc THEN null ELSE aggfinalfn END as final_func,
CASE WHEN aggcombinefn = '-'::regproc THEN null ELSE aggcombinefn END as combine_func,
CASE WHEN aggserialfn = '-'::regproc THEN null ELSE aggserialfn END as serialization_func,
CASE WHEN aggdeserialfn = '-'::regproc THEN null ELSE aggdeserialfn END as deserialization_func,
CASE WHEN aggmtransfn = '-'::regproc THEN null ELSE aggmtransfn END as moving_state_func,
CASE WHEN aggmfinalfn = '-'::regproc THEN null ELSE aggmfinalfn END as moving_final_func,
CASE WHEN aggminvtransfn = '-'::regproc THEN null ELSE aggminvtransfn END as moving_inverse_func,
CASE WHEN ag.aggfinalmodify = 'r' THEN 'READ_ONLY'
WHEN ag.aggfinalmodify = 's' THEN 'SHAREABLE'
WHEN ag.aggfinalmodify = 'w' THEN 'READ_WRITE'
ELSE 'unknown' END as final_func_modify,
CASE WHEN ag.aggmfinalmodify = 'r' THEN 'READ_ONLY'
WHEN ag.aggmfinalmodify = 's' THEN 'SHAREABLE'
WHEN ag.aggmfinalmodify = 'w' THEN 'READ_WRITE'
ELSE 'unknown' END as moving_final_func_modify,
agginitval as initial_val, aggminitval as moving_initial_val,
op.oprname as sort_oper, aggfinalextra as final_extra_param, aggmfinalextra as moving_final_extra_param,
aggtransspace as state_data_size, aggmtransspace as moving_state_data_size,
CASE WHEN (tt.typlen = -1 AND tt.typelem != 0) THEN
(SELECT at.typname FROM pg_type at WHERE at.oid = tt.typelem) || '[]'
ELSE tt.typname END as state_type,
CASE WHEN (tf.typlen = -1 AND tf.typelem != 0) THEN
(SELECT at.typname FROM pg_catalog.pg_type at WHERE at.oid = tf.typelem) || '[]'
ELSE tf.typname END as final_type,
CASE WHEN (tm.typlen = -1 AND tm.typelem != 0) THEN
(SELECT at.typname FROM pg_type at WHERE at.oid = tm.typelem) || '[]'
ELSE tm.typname END as moving_state_type,
description
FROM pg_catalog.pg_aggregate ag
LEFT OUTER JOIN pg_catalog.pg_proc pr ON pr.oid = ag.aggfnoid
LEFT OUTER JOIN pg_catalog.pg_namespace ns ON ns.oid=pr.pronamespace
LEFT OUTER JOIN pg_catalog.pg_type tt on tt.oid=aggtranstype
LEFT OUTER JOIN pg_catalog.pg_type tf on tf.oid=prorettype
LEFT OUTER JOIN pg_catalog.pg_type tm on tm.oid=aggmtranstype
LEFT OUTER JOIN pg_catalog.pg_operator op on op.oid = ag.aggsortop
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=aggfnoid::oid AND des.classoid='pg_aggregate'::regclass)
WHERE pronamespace = {{scid}}::oid
{% if agid %} AND ag.aggfnoid = {{agid}}::oid {% endif %}
ORDER BY name;

View File

@ -0,0 +1,24 @@
{% if data %}
CREATE OR REPLACE AGGREGATE {{conn|qtIdent(data.schema, data.name)}}({% if data.input_types %}{{data.input_types}}{% endif %}) (
SFUNC = {{data.state_func}},
STYPE = {{data.state_type}} {% if data.state_data_size %},
SSPACE = {{data.state_data_size}}{% endif %}{% if data.final_func %},
FINALFUNC = {{data.final_func}}{% endif %}{% if data.final_extra_param %},
FINALFUNC_EXTRA{% endif %}{% if data.final_func_modify %},
FINALFUNC_MODIFY = {{data.final_func_modify}}{% endif %}{% if data.combine_func %},
COMBINEFUNC = {{data.combine_func}}{% endif %}{% if data.serialization_func %},
SERIALFUNC = {{data.serialization_func}}{% endif %}{% if data.deserialization_func %},
DESERIALFUNC = {{data.deserialization_func}}{% endif %}{% if data.initial_val %},
INITCOND = '{{data.initial_val}}'::text{% endif %}{% if data.moving_state_func %},
MSFUNC = {{data.moving_state_func}}{% endif %}{% if data.moving_inverse_func %},
MINVFUNC = {{data.moving_inverse_func}}{% endif %}{% if data.moving_state_type %},
MSTYPE = {{data.moving_state_type}}{% endif %}{% if data.moving_state_data_size %},
MSSPACE = {{data.moving_state_data_size}}{% endif %}{% if data.moving_final_func %},
MFINALFUNC = {{data.moving_final_func}}{% endif %}{% if data.moving_final_extra_param %},
MFINALFUNC_EXTRA{% endif %}{% if data.moving_final_func_modify %},
MFINALFUNC_MODIFY = {{data.moving_final_func_modify}}{% endif %}{% if data.moving_initial_val %},
MINITCOND = '{{data.moving_initial_val}}'::text{% endif %}{% if data.sort_oper %},
SORTOP = {{data.sort_oper}}{% endif %}
);
{% endif %}

View File

@ -0,0 +1,22 @@
{% if data %}
CREATE AGGREGATE {{conn|qtIdent(data.schema, data.name)}}({% if data.input_types %}{{data.input_types}}{% endif %}) (
SFUNC = {{data.state_func}},
STYPE = {{data.state_type}} {% if data.state_data_size %},
SSPACE = {{data.state_data_size}}{% endif %}{% if data.final_func %},
FINALFUNC = {{data.final_func}}{% endif %}{% if data.final_extra_param %},
FINALFUNC_EXTRA{% endif %}{% if data.combine_func %},
COMBINEFUNC = {{data.combine_func}}{% endif %}{% if data.serialization_func %},
SERIALFUNC = {{data.serialization_func}}{% endif %}{% if data.deserialization_func %},
DESERIALFUNC = {{data.deserialization_func}}{% endif %}{% if data.initial_val %},
INITCOND = '{{data.initial_val}}'::text{% endif %}{% if data.moving_state_func %},
MSFUNC = {{data.moving_state_func}}{% endif %}{% if data.moving_inverse_func %},
MINVFUNC = {{data.moving_inverse_func}}{% endif %}{% if data.moving_state_type %},
MSTYPE = {{data.moving_state_type}}{% endif %}{% if data.moving_state_data_size %},
MSSPACE = {{data.moving_state_data_size}}{% endif %}{% if data.moving_final_func %},
MFINALFUNC = {{data.moving_final_func}}{% endif %}{% if data.moving_final_extra_param %},
MFINALFUNC_EXTRA{% endif %}{% if data.moving_initial_val %},
MINITCOND = '{{data.moving_initial_val}}'::text{% endif %}{% if data.sort_oper %},
SORTOP = {{data.sort_oper}}{% endif %}
);
{% endif %}

View File

@ -0,0 +1,3 @@
{% if data %}
DROP AGGREGATE IF EXISTS {{conn|qtIdent(data.schema, data.name)}}({% if data.input_types %}{{data.input_types}}{% endif %}){% if cascade %} CASCADE{% endif %};
{% endif %}

View File

@ -0,0 +1,14 @@
SELECT aggfnoid::oid as oid,
proname || '(' || COALESCE(pg_catalog.pg_get_function_arguments(aggfnoid::oid), '') || ')' AS name,
pg_catalog.pg_get_userbyid(proowner) AS owner
FROM pg_aggregate ag
LEFT OUTER JOIN pg_catalog.pg_proc pr ON pr.oid = ag.aggfnoid
LEFT OUTER JOIN pg_catalog.pg_type tt on tt.oid=aggtranstype
LEFT OUTER JOIN pg_catalog.pg_type tf on tf.oid=prorettype
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=aggfnoid::oid AND des.classoid='pg_aggregate'::regclass)
{% if scid %}
WHERE pronamespace = {{scid}}::oid
{% elif agid %}
WHERE ag.aggfnoid = {{agid}}::oid
{% endif %}
ORDER BY name;

View File

@ -0,0 +1,39 @@
SELECT aggfnoid::oid as oid, proname as name, ns.nspname as schema,
pg_catalog.pg_get_userbyid(proowner) as owner,
COALESCE(pg_catalog.pg_get_function_arguments(aggfnoid::oid), '') as input_types, proacl,
CASE WHEN ag.aggkind = 'n' THEN 'normal'
WHEN ag.aggkind = 'o' THEN 'ordered-set'
WHEN ag.aggkind = 'h' THEN 'hypothetical-set'
ELSE 'unknown' END as kind,
CASE WHEN aggtransfn = '-'::regproc THEN null ELSE aggtransfn END as state_func,
CASE WHEN aggfinalfn = '-'::regproc THEN null ELSE aggfinalfn END as final_func,
CASE WHEN aggcombinefn = '-'::regproc THEN null ELSE aggcombinefn END as combine_func,
CASE WHEN aggserialfn = '-'::regproc THEN null ELSE aggserialfn END as serialization_func,
CASE WHEN aggdeserialfn = '-'::regproc THEN null ELSE aggdeserialfn END as deserialization_func,
CASE WHEN aggmtransfn = '-'::regproc THEN null ELSE aggmtransfn END as moving_state_func,
CASE WHEN aggmfinalfn = '-'::regproc THEN null ELSE aggmfinalfn END as moving_final_func,
CASE WHEN aggminvtransfn = '-'::regproc THEN null ELSE aggminvtransfn END as moving_inverse_func,
agginitval as initial_val, aggminitval as moving_initial_val,
op.oprname as sort_oper, aggfinalextra as final_extra_param, aggmfinalextra as moving_final_extra_param,
aggtransspace as state_data_size, aggmtransspace as moving_state_data_size,
CASE WHEN (tt.typlen = -1 AND tt.typelem != 0) THEN
(SELECT at.typname FROM pg_type at WHERE at.oid = tt.typelem) || '[]'
ELSE tt.typname END as state_type,
CASE WHEN (tf.typlen = -1 AND tf.typelem != 0) THEN
(SELECT at.typname FROM pg_catalog.pg_type at WHERE at.oid = tf.typelem) || '[]'
ELSE tf.typname END as final_type,
CASE WHEN (tm.typlen = -1 AND tm.typelem != 0) THEN
(SELECT at.typname FROM pg_type at WHERE at.oid = tm.typelem) || '[]'
ELSE tm.typname END as moving_state_type,
description
FROM pg_catalog.pg_aggregate ag
LEFT OUTER JOIN pg_catalog.pg_proc pr ON pr.oid = ag.aggfnoid
LEFT OUTER JOIN pg_catalog.pg_namespace ns ON ns.oid=pr.pronamespace
LEFT OUTER JOIN pg_catalog.pg_type tt on tt.oid=aggtranstype
LEFT OUTER JOIN pg_catalog.pg_type tf on tf.oid=prorettype
LEFT OUTER JOIN pg_catalog.pg_type tm on tm.oid=aggmtranstype
LEFT OUTER JOIN pg_catalog.pg_operator op on op.oid = ag.aggsortop
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=aggfnoid::oid AND des.classoid='pg_aggregate'::regclass)
WHERE pronamespace = {{scid}}::oid
{% if agid %} AND ag.aggfnoid = {{agid}}::oid {% endif %}
ORDER BY name;

View File

@ -0,0 +1,377 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
""" Implements Operator Node """
from functools import wraps
from flask import render_template
from flask_babelex import gettext
import pgadmin.browser.server_groups.servers.databases as database
from config import PG_DEFAULT_DRIVER
from pgadmin.browser.server_groups.servers.databases.schemas.utils \
import SchemaChildModule
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
class OperatorModule(SchemaChildModule):
"""
class OperatorModule(SchemaChildModule)
A module class for Operator node derived from SchemaChildModule.
Methods:
-------
* __init__(*args, **kwargs)
- Method is used to initialize the Operator and it's base module.
* get_nodes(gid, sid, did, scid, opid)
- 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 = 'operator'
_COLLECTION_LABEL = gettext("Operators")
def __init__(self, *args, **kwargs):
"""
Method is used to initialize the OperatorModule and it's base module.
Args:
*args:
**kwargs:
"""
super(OperatorModule, self).__init__(*args, **kwargs)
self.min_ver = 90100
self.max_ver = None
def get_nodes(self, gid, sid, did, scid):
"""
Generate the collection node
"""
yield self.generate_browser_collection_node(scid)
@property
def script_load(self):
"""
Load the module script for database, when any of the database node is
initialized.
"""
return database.DatabaseModule.node_type
@property
def node_inode(self):
return False
blueprint = OperatorModule(__name__)
class OperatorView(PGChildNodeView):
"""
This class is responsible for generating routes for Operator node
Methods:
-------
* __init__(**kwargs)
- Method is used to initialize the OperatorView and it's base view.
* check_precondition()
- This function will behave as a decorator which will checks
database connection before running view, it will also attaches
manager,conn & template_path properties to self
* list()
- This function is used to list all the Operator nodes within that
collection.
* nodes()
- This function will used to create all the child node within that
collection, Here it will create all the Operator node.
* properties(gid, sid, did, scid, opid)
- This function will show the properties of the selected Operator node
* sql(gid, sid, did, scid):
- This function will generate sql to show it in sql pane for the
selected Operator node.
"""
node_type = blueprint.node_type
node_label = "Operator"
parent_ids = [
{'type': 'int', 'id': 'gid'},
{'type': 'int', 'id': 'sid'},
{'type': 'int', 'id': 'did'},
{'type': 'int', 'id': 'scid'}
]
ids = [
{'type': 'int', 'id': 'opid'}
]
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'children': [{'get': 'children'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}]
})
def check_precondition(f):
"""
This function will behave as a decorator which will checks
database connection before running view, it will also attaches
manager,conn & template_path properties to self
"""
@wraps(f)
def wrap(*args, **kwargs):
# Here args[0] will hold self & kwargs will hold gid,sid,did
self = args[0]
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
kwargs['sid']
)
self.conn = self.manager.connection(did=kwargs['did'])
self.datlastsysoid = \
self.manager.db_info[kwargs['did']]['datlastsysoid'] \
if self.manager.db_info is not None and \
kwargs['did'] in self.manager.db_info else 0
self.datistemplate = False
if (
self.manager.db_info is not None and
kwargs['did'] in self.manager.db_info and
'datistemplate' in self.manager.db_info[kwargs['did']]
):
self.datistemplate = self.manager.db_info[
kwargs['did']]['datistemplate']
# Set the template path for the SQL scripts
self.template_path = compile_template_path(
'operators/sql/',
self.manager.server_type,
self.manager.version
)
return f(*args, **kwargs)
return wrap
@check_precondition
def list(self, gid, sid, did, scid):
"""
This function is used to list all the operator nodes within that
collection.
Args:
gid: Server group ID
sid: Server ID
did: Database ID
scid: Schema ID
Returns:
JSON of available operator nodes
"""
SQL = render_template("/".join([self.template_path,
self._NODES_SQL]), scid=scid)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
return ajax_response(
response=res['rows'],
status=200
)
@check_precondition
def nodes(self, gid, sid, did, scid):
"""
This function will used to create all the child node within that
collection.
Here it will create all the operator node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
Returns:
JSON of available operator child nodes
"""
res = []
SQL = render_template("/".join([self.template_path,
self._NODES_SQL]), scid=scid)
status, rset = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=rset)
for row in rset['rows']:
res.append(
self.blueprint.generate_browser_node(
row['oid'],
scid,
row['name'],
icon="icon-operator"
))
return make_json_response(
data=res,
status=200
)
@check_precondition
def node(self, gid, sid, did, scid, opid):
"""
This function will fetch properties of the operator node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
opid: Operator ID
Returns:
JSON of given operator node
"""
SQL = render_template("/".join([self.template_path,
self._NODES_SQL]), opid=opid)
status, rset = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=rset)
for row in rset['rows']:
return make_json_response(
data=self.blueprint.generate_browser_node(
row['oid'],
scid,
row['name'],
icon="icon-operator"
),
status=200
)
return gone(self.not_found_error_msg())
@check_precondition
def properties(self, gid, sid, did, scid, opid):
"""
This function will show the properties of the selected operator node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
scid: Schema ID
opid: Operator ID
Returns:
JSON of selected operator node
"""
status, res = self._fetch_properties(scid, opid)
if not status:
return res
return ajax_response(
response=res,
status=200
)
def _fetch_properties(self, scid, opid):
"""
This function fetch the properties for the specified object.
:param scid: Schema ID
:param opid: Operator ID
"""
SQL = render_template("/".join([self.template_path,
self._PROPERTIES_SQL]),
scid=scid, opid=opid,
datlastsysoid=self.datlastsysoid)
status, res = self.conn.execute_dict(SQL)
if not status:
return False, internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return False, gone(self.not_found_error_msg())
res['rows'][0]['is_sys_obj'] = (
res['rows'][0]['oid'] <= self.datlastsysoid or self.datistemplate)
return True, res['rows'][0]
@check_precondition
def sql(self, gid, sid, did, scid, opid):
"""
This function will generates reverse engineered sql for operator
object
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
opid: Operator ID
json_resp: True then return json response
"""
SQL = render_template("/".join([self.template_path,
self._PROPERTIES_SQL]),
scid=scid, opid=opid)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(self.not_found_error_msg())
data = res['rows'][0]
SQL = render_template("/".join([self.template_path,
self._CREATE_SQL]),
data=data)
sql_header = "-- Operator: {0};\n\n-- ".format(data['name'])
sql_header += render_template("/".join([self.template_path,
self._DELETE_SQL]),
name=data['name'],
oprnamespace=data['schema'],
lefttype=data['lefttype'],
righttype=data['righttype'],
)
SQL = sql_header + '\n' + SQL.strip('\n')
return ajax_response(response=SQL)
OperatorView.register_node_view(blueprint)

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d4e3f3;}.cls-2{fill:#6f6f6f;}</style></defs><polygon class="cls-1" points="2.15 7.15 2.15 4.65 4.65 4.65 4.65 2.15 7.15 2.15 7.15 7.15 2.15 7.15"/><path class="cls-2" d="M6.65,2.65v4h-4V5.15h2.5V2.65h1.5m1-1H4.15v2.5H1.65v3.5h6v-6Z"/><polygon class="cls-1" points="4.15 9.15 4.15 6.65 6.65 6.65 6.65 4.15 9.15 4.15 9.15 9.15 4.15 9.15"/><path class="cls-2" d="M8.65,4.65v4h-4V7.15h2.5V4.65h1.5m1-1H6.15v2.5H3.65v3.5h6v-6Z"/><polygon class="cls-1" points="8.85 13.85 8.85 11.35 6.35 11.35 6.35 8.85 8.85 8.85 8.85 6.35 11.35 6.35 11.35 8.85 13.85 8.85 13.85 11.35 11.35 11.35 11.35 13.85 8.85 13.85"/><path class="cls-2" d="M10.85,6.85v2.5h2.5v1.5h-2.5v2.5H9.35v-2.5H6.85V9.35h2.5V6.85h1.5m1-1H8.35v2.5H5.85v3.5h2.5v2.5h3.5v-2.5h2.5V8.35h-2.5V5.85Z"/></svg>

After

Width:  |  Height:  |  Size: 875 B

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d4e3f3;stroke:#6f6f6f;stroke-miterlimit:10;}</style></defs><polygon class="cls-1" points="13 6.85 9.15 6.85 9.15 3 6.85 3 6.85 6.85 3 6.85 3 9.15 6.85 9.15 6.85 13 9.15 13 9.15 9.15 13 9.15 13 6.85"/></svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@ -0,0 +1,86 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import OperatorSchema from './operator.ui';
define('pgadmin.node.operator', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/child', 'pgadmin.browser.collection',
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, schemaChild) {
if (!pgBrowser.Nodes['coll-operator']) {
pgAdmin.Browser.Nodes['coll-operator'] =
pgAdmin.Browser.Collection.extend({
node: 'operator',
label: gettext('Operators'),
type: 'coll-operator',
columns: ['name', 'owner', 'description'],
canDrop: false,
canDropCascade: false,
});
}
if (!pgBrowser.Nodes['operator']) {
pgAdmin.Browser.Nodes['operator'] = schemaChild.SchemaChildNode.extend({
type: 'operator',
sqlAlterHelp: 'sql-alteroperator.html',
sqlCreateHelp: 'sql-createoperator.html',
label: gettext('Operator'),
collection_type: 'coll-operator',
hasSQL: true,
hasDepends: false,
canDrop: false,
canDropCascade: false,
Init: function() {
/* Avoid mulitple registration of menus */
if (this.initialized)
return;
this.initialized = true;
},
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'oid',
// Default values!
initialize: function(attrs, args) {
var isNew = (_.size(attrs) === 0);
if (isNew) {
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
var schemaInfo = args.node_info.schema;
this.set({'owner': userInfo.name}, {silent: true});
this.set({'schema': schemaInfo._label}, {silent: true});
}
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
},
schema: [{
id: 'name', label: gettext('Operator'), cell: 'string',
type: 'text', mode: ['properties', 'create', 'edit'],
},{
id: 'owner', label: gettext('Owner'), cell: 'string',
type: 'text', mode: ['properties', 'create', 'edit'],
control: 'node-list-by-name',
node: 'role',
},{
id: 'description', label: gettext('Comment'), cell: 'string',
type: 'multiline', mode: ['properties', 'create', 'edit'],
}
],
}),
getSchema: ()=>{
let schema = new OperatorSchema();
return schema;
}
});
}
return pgBrowser.Nodes['operator'];
});

View File

@ -0,0 +1,125 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import gettext from 'sources/gettext';
export default class OperatorSchema extends BaseUISchema {
constructor(fieldOptions = {},initValues) {
super({
name: undefined,
oid: undefined,
owner: undefined,
description: undefined,
schema: null,
lefttype: undefined,
righttype: undefined,
operproc:undefined,
joinproc: undefined,
restrproc: undefined,
commutator: undefined,
negator:undefined,
support_hash: false,
support_merge: false,
...initValues
});
this.fieldOptions = fieldOptions;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'name', label: gettext('Name'), type: 'tel',
readonly: true,
},
{
id: 'oid', label: gettext('OID'),
type: 'text', mode: ['properties']
},
{
id: 'owner', label: gettext('Owner'),
type: 'text', readonly: true,
},
{
id: 'schema', label: gettext('Schema'),
mode: ['create', 'edit'],
type: 'text', readonly: true,
},
{
id: 'is_sys_obj', label: gettext('System operator?'),
cell: 'boolean', type: 'switch', mode: ['properties'],
},
{
id: 'description', label: gettext('Comment'),
type: 'multiline', mode: ['properties', 'create', 'edit'],
readonly: true,
},
{
id: 'lefttype', label: gettext('Left type'),
group: gettext('Definition'),
type: 'text', readonly: true,
},
{
id: 'righttype', label: gettext('Right type'),
group: gettext('Definition'),
type: 'text', readonly: true,
},
{
id: 'resulttype', label: gettext('Result type'),
group: gettext('Definition'),
type: 'text', mode: ['properties'],
},
{
id: 'oprkind', label: gettext('Kind'),
group: gettext('Definition'),
type: 'text', mode: ['properties'],
},
{
id: 'operproc', label: gettext('Operator function'),
group: gettext('Definition'),
type: 'text', readonly: true,
},
{
id: 'restrproc', label: gettext('Restrict function'),
group: gettext('Implementation'),
type: 'text', readonly: true,
},
{
id: 'joinproc', label: gettext('Join function'),
group: gettext('Implementation'),
type: 'text', readonly: true,
},
{
id: 'commutator', label: gettext('Commutator'),
group: gettext('Implementation'),
type: 'text', readonly: true,
},
{
id: 'negator', label: gettext('Negator'),
group: gettext('Implementation'),
type: 'text', readonly: true,
},
{
id: 'support_hash', label: gettext('Supports hash'),
group: gettext('Implementation'),
cell: 'boolean', type: 'switch', readonly: true,
},
{
id: 'support_merge', label: gettext('Supports merge'),
group: gettext('Implementation'),
cell: 'boolean', type: 'switch', readonly: true,
}
];
}
}

View File

@ -0,0 +1,13 @@
{% if data %}
CREATE OPERATOR {{data.schema}}.{{data.name}} (
FUNCTION = {{data.operproc}}{% if data.lefttype %},
LEFTARG = {{data.lefttype}}{% endif %}{% if data.righttype %},
RIGHTARG = {{data.righttype}}{% endif %}{% if data.commutator %},
COMMUTATOR = {{data.commutator}}{% endif %}{% if data.negator %},
NEGATOR = {{data.negator}}{% endif %}{% if data.restrproc %},
RESTRICT = {{data.restrproc}}{% endif %}{% if data.joinproc %},
JOIN = {{data.joinproc}}{% endif %}{% if data.support_hash %},
HASHES{% endif %}{% if data.support_merge %}, MERGES{% endif %}
);
{% endif %}

View File

@ -0,0 +1,13 @@
{% if data %}
CREATE OPERATOR {{data.schema}}.{{data.name}} (
PROCEDURE = {{data.operproc}}{% if data.lefttype %},
LEFTARG = {{data.lefttype}}{% endif %}{% if data.righttype %},
RIGHTARG = {{data.righttype}}{% endif %}{% if data.commutator %},
COMMUTATOR = {{data.commutator}}{% endif %}{% if data.negator %},
NEGATOR = {{data.negator}}{% endif %}{% if data.restrproc %},
RESTRICT = {{data.restrproc}}{% endif %}{% if data.joinproc %},
JOIN = {{data.joinproc}}{% endif %}{% if data.support_hash %},
HASHES{% endif %}{% if data.support_merge %}, MERGES{% endif %}
);
{% endif %}

View File

@ -0,0 +1,13 @@
{% if lefttype %}
{% set ltype = lefttype %}
{% else %}
{% set ltype = none %}
{% endif %}
{% if righttype %}
{% set rtype = righttype %}
{% else %}
{% set rtype = none %}
{% endif %}
{% if name %}
DROP OPERATOR IF EXISTS {{conn|qtIdent(oprnamespace)}}.{{name}} ({{ltype}} , {{rtype}}){% if cascade %} CASCADE{% endif %};
{% endif %}

View File

@ -0,0 +1,23 @@
SELECT op.oid, pg_catalog.pg_get_userbyid(op.oprowner) as owner,
CASE WHEN lt.typname IS NOT NULL AND rt.typname IS NOT NULL THEN
op.oprname || ' (' || pg_catalog.format_type(lt.oid, NULL) || ', ' || pg_catalog.format_type(rt.oid, NULL) || ')'
WHEN lt.typname IS NULL AND rt.typname IS NOT NULL THEN
op.oprname || ' (' || pg_catalog.format_type(rt.oid, NULL) || ')'
WHEN lt.typname IS NOT NULL AND rt.typname IS NULL THEN
op.oprname || ' (' || pg_catalog.format_type(lt.oid, NULL) || ')'
ELSE op.oprname || '()'
END as name,
lt.typname as lefttype, rt.typname as righttype
FROM pg_catalog.pg_operator op
LEFT OUTER JOIN pg_catalog.pg_type lt ON lt.oid=op.oprleft
LEFT OUTER JOIN pg_catalog.pg_type rt ON rt.oid=op.oprright
JOIN pg_catalog.pg_type et on et.oid=op.oprresult
LEFT OUTER JOIN pg_catalog.pg_operator co ON co.oid=op.oprcom
LEFT OUTER JOIN pg_catalog.pg_operator ne ON ne.oid=op.oprnegate
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=op.oid AND des.classoid='pg_operator'::regclass)
{% if scid %}
WHERE op.oprnamespace = {{scid}}::oid
{% elif opid %}
WHERE op.oid = {{opid}}::oid
{% endif %}
ORDER BY op.oprname;

View File

@ -0,0 +1,25 @@
SELECT op.oid, op.oprname as name, ns.nspname as schema,
pg_catalog.pg_get_userbyid(op.oprowner) as owner,
op.oprcanhash as support_hash, op.oprcanmerge as support_merge,
ns.nspname as schema,
CASE WHEN op.oprkind = 'b' THEN 'infix'
WHEN op.oprkind = 'l' THEN 'prefix'
WHEN op.oprkind = 'r' THEN 'postfix'
ELSE 'unknown' END as oprkind, et.typname as resulttype,
pg_catalog.format_type(lt.oid, NULL) as lefttype,
pg_catalog.format_type(rt.oid, NULL) as righttype,
co.oprname as commutator, op.oprcode as operproc,
ne.oprname as negator, description,
CASE WHEN op.oprrest = '-'::regproc THEN null ELSE op.oprrest END as restrproc,
CASE WHEN op.oprjoin = '-'::regproc THEN null ELSE op.oprjoin END as joinproc
FROM pg_catalog.pg_operator op
LEFT OUTER JOIN pg_catalog.pg_namespace ns ON ns.oid=op.oprnamespace
LEFT OUTER JOIN pg_catalog.pg_type lt ON lt.oid=op.oprleft
LEFT OUTER JOIN pg_catalog.pg_type rt ON rt.oid=op.oprright
JOIN pg_catalog.pg_type et on et.oid=op.oprresult
LEFT OUTER JOIN pg_catalog.pg_operator co ON co.oid=op.oprcom
LEFT OUTER JOIN pg_catalog.pg_operator ne ON ne.oid=op.oprnegate
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=op.oid AND des.classoid='pg_operator'::regclass)
WHERE op.oprnamespace = {{scid}}::oid
{% if opid %} AND op.oid = {{opid}}::oid {% endif %}
ORDER BY op.oprname;

View File

@ -182,7 +182,8 @@ class DataTypeReader:
'label': row['typname'], 'value': row['typname'],
'typval': typeval, 'precision': precision,
'length': length, 'min_val': min_val, 'max_val': max_val,
'is_collatable': row['is_collatable']
'is_collatable': row['is_collatable'],
'oid': row['oid']
})
except Exception as e:

View File

@ -44,6 +44,8 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='int'/>;
case 'numeric':
return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='numeric'/>;
case 'tel':
return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='tel'/>;
case 'text':
return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props}/>;
case 'multiline':

View File

@ -320,7 +320,7 @@ export const InputText = forwardRef(({
let changeVal = e.target.value;
/* For type number, we set type as tel with number regex to get validity.*/
if(['numeric', 'int'].indexOf(type) > -1) {
if(['numeric', 'int', 'tel'].indexOf(type) > -1) {
if(!e.target.validity.valid && changeVal !== '' && changeVal !== '-') {
return;
}
@ -347,7 +347,7 @@ export const InputText = forwardRef(({
id: cid,
maxLength: maxlength,
'aria-describedby': helpid,
...(type ? {pattern: patterns[type]} : {})
...(type ? {pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type]} : {})
}}
readOnly={Boolean(readonly)}
disabled={Boolean(disabled)}

View File

@ -0,0 +1,95 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import '../helper/enzyme.helper';
import { createMount } from '@material-ui/core/test-utils';
import pgAdmin from 'sources/pgadmin';
import {messages} from '../fake_messages';
import SchemaView from '../../../pgadmin/static/js/SchemaView';
import AggregateSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/aggregates/static/js/aggregate.ui';
describe('AggregateSchema', ()=>{
let mount;
let schemaObj = new AggregateSchema();
let getInitData = ()=>Promise.resolve({});
/* Use createMount so that material ui components gets the required context */
/* https://material-ui.com/guides/testing/#api */
beforeAll(()=>{
mount = createMount();
});
afterAll(() => {
mount.cleanUp();
});
beforeEach(()=>{
jasmineEnzyme();
/* messages used by validators */
pgAdmin.Browser = pgAdmin.Browser || {};
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
});
it('create', ()=>{
mount(<SchemaView
formType='dialog'
schema={schemaObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
});
it('edit', ()=>{
mount(<SchemaView
formType='dialog'
schema={schemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
});
it('properties', ()=>{
mount(<SchemaView
formType='tab'
schema={schemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'properties',
}}
onHelp={()=>{}}
onEdit={()=>{}}
/>);
});
});

View File

@ -0,0 +1,95 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import '../helper/enzyme.helper';
import { createMount } from '@material-ui/core/test-utils';
import pgAdmin from 'sources/pgadmin';
import {messages} from '../fake_messages';
import SchemaView from '../../../pgadmin/static/js/SchemaView';
import OperatorSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/operators/static/js/operator.ui';
describe('OperatorSchema', ()=>{
let mount;
let schemaObj = new OperatorSchema();
let getInitData = ()=>Promise.resolve({});
/* Use createMount so that material ui components gets the required context */
/* https://material-ui.com/guides/testing/#api */
beforeAll(()=>{
mount = createMount();
});
afterAll(() => {
mount.cleanUp();
});
beforeEach(()=>{
jasmineEnzyme();
/* messages used by validators */
pgAdmin.Browser = pgAdmin.Browser || {};
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
});
it('create', ()=>{
mount(<SchemaView
formType='dialog'
schema={schemaObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
});
it('edit', ()=>{
mount(<SchemaView
formType='dialog'
schema={schemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
});
it('properties', ()=>{
mount(<SchemaView
formType='tab'
schema={schemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'properties',
}}
onHelp={()=>{}}
onEdit={()=>{}}
/>);
});
});

View File

@ -506,6 +506,8 @@ module.exports = [{
'pure|pgadmin.node.table',
'pure|pgadmin.node.partition',
'pure|pgadmin.node.compound_trigger',
'pure|pgadmin.node.aggregate',
'pure|pgadmin.node.operator',
],
},
},

View File

@ -226,6 +226,7 @@ var webpackShimConfig = {
'pgadmin.node.publication': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/publications/static/js/publication'),
'pgadmin.node.subscription': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription'),
'pgadmin.node.catalog': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/static/js/catalog'),
'pgadmin.node.aggregate': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/aggregates/static/js/aggregate'),
'pgadmin.node.catalog_object': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/catalog_objects/static/js/catalog_object'),
'pgadmin.node.catalog_object_column': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/catalog_objects/columns/static/js/catalog_object_column'),
'pgadmin.node.check_constraint': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/js/check_constraint'),
@ -255,6 +256,7 @@ var webpackShimConfig = {
'pgadmin.node.index': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index'),
'pgadmin.node.language': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/languages/static/js/language'),
'pgadmin.node.mview': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview'),
'pgadmin.node.operator': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/operators/static/js/operator'),
'pgadmin.node.package': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package'),
'pgadmin.node.partition': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/partitions/static/js/partition'),
'pgadmin.node.pga_job': path.join(__dirname, './pgadmin/browser/server_groups/servers/pgagent/static/js/pga_job'),