Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/en_US/images/server_advanced.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/en_US/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ notes for it.
:maxdepth: 1


release_notes_9_3
release_notes_9_2
release_notes_9_1
release_notes_9_0
Expand Down
34 changes: 34 additions & 0 deletions docs/en_US/release_notes_9_3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
***********
Version 9.3
***********

Release date: 2025-04-30

This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.2.

Supported Database Servers
**************************
**PostgreSQL**: 13, 14, 15, 16 and 17

**EDB Advanced Server**: 13, 14, 15, 16 and 17

Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 17.2


New features
************

| `Issue #2767 <https://github.com/pgadmin-org/pgadmin4/issues/2767>`_ - Added ability to use SQL in the "DB Restriction" field.
| `Issue #8629 <https://github.com/pgadmin-org/pgadmin4/issues/8629>`_ - Added support for font ligatures.

Housekeeping
************


Bug fixes
*********

| `Issue #8443 <https://github.com/pgadmin-org/pgadmin4/issues/8443>`_ - Fixed an issue where the debugger hangs when stepping into nested function/procedure.
| `Issue #8556 <https://github.com/pgadmin-org/pgadmin4/issues/8556>`_ - Ensure that graph data is updated even when the Dashboard tab is inactive.
18 changes: 13 additions & 5 deletions docs/en_US/server_dialog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,19 @@ Click the *Advanced* tab to continue.

Use the fields in the *Advanced* tab to configure a connection:

* Use the *DB restriction* field to provide a SQL restriction that will be used
against the pg_database table to limit the databases that you see. For
example, you might enter: *live_db test_db* so that only live_db and test_db
are shown in the pgAdmin browser. Separate entries with a comma or tab as you
type.
* Specify the type of the database restriction that will be used to filter
out the databases for restriction in the *DB restriction type* field:

* Select the *Databases* option to specify the name of the databases
that will be used against the pg_database table to limit the databases
that you see. This is the default.
* Select the *SQL* option to provide a SQL restriction that will be used
against the pg_database table to limit the databases that you see.

* Use the *DB restriction* field to provide a SQL restriction OR Database names
that will be used against the pg_database table to limit the databases that you see.
For example, you might enter: *live_db test_db* so that only live_db and test_db
are shown in the pgAdmin object explorer.
* Use the *Password exec command* field to specify a shell command to be executed
to retrieve a password to be used for SQL authentication. The ``stdout`` of the
command will be used as the SQL password. This may be useful when the password
Expand Down
36 changes: 36 additions & 0 deletions web/migrations/versions/1f0eddc8fc79_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################

"""

Revision ID: 1f0eddc8fc79
Revises: e982c040d9b5
Create Date: 2025-03-26 15:58:24.131719

"""
from alembic import op
import sqlalchemy as sa
from pgadmin.utils.constants import RESTRICTION_TYPE_DATABASES

# revision identifiers, used by Alembic.
revision = '1f0eddc8fc79'
down_revision = 'e982c040d9b5'
branch_labels = None
depends_on = None


def upgrade():
op.add_column('server',
sa.Column('db_res_type', sa.String(length=32),
server_default=RESTRICTION_TYPE_DATABASES))


def downgrade():
# pgAdmin only upgrades, downgrade not implemented.
pass
23 changes: 15 additions & 8 deletions web/pgadmin/browser/server_groups/servers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
from pgadmin.browser.server_groups.servers.utils import \
(is_valid_ipaddress, get_replication_type, convert_connection_parameter,
check_ssl_fields)
check_ssl_fields, get_db_restriction)
from pgadmin.utils.constants import UNAUTH_REQ, MIMETYPE_APP_JS, \
SERVER_CONNECTION_CLOSED
SERVER_CONNECTION_CLOSED, RESTRICTION_TYPE_SQL
from sqlalchemy import or_
from sqlalchemy.orm.attributes import flag_modified
from pgadmin.utils.preferences import Preferences
Expand Down Expand Up @@ -746,6 +746,7 @@ def update(self, gid, sid):
'comment': 'comment',
'role': 'role',
'db_res': 'db_res',
'db_res_type': 'db_res_type',
'passexec_cmd': 'passexec_cmd',
'passexec_expiration': 'passexec_expiration',
'bgcolor': 'bgcolor',
Expand Down Expand Up @@ -776,12 +777,11 @@ def update(self, gid, sid):
'role': gettext('Role')
}

idx = 0
data = request.form if request.form else json.loads(
request.data
)

if 'db_res' in data:
if 'db_res' in data and isinstance(data['db_res'], list):
data['db_res'] = ','.join(data['db_res'])

# Update connection parameter if any.
Expand Down Expand Up @@ -948,7 +948,7 @@ def list(self, gid):
'connected': connected,
'version': manager.ver,
'server_type': manager.server_type if connected else 'pg',
'db_res': server.db_res.split(',') if server.db_res else None
'db_res': get_db_restriction(server.db_res_type, server.db_res)
})

return ajax_response(
Expand Down Expand Up @@ -1031,7 +1031,8 @@ def properties(self, gid, sid):
'server_type': manager.server_type if connected else 'pg',
'bgcolor': server.bgcolor,
'fgcolor': server.fgcolor,
'db_res': server.db_res.split(',') if server.db_res else None,
'db_res': get_db_restriction(server.db_res_type, server.db_res),
'db_res_type': server.db_res_type,
'passexec_cmd':
server.passexec_cmd if server.passexec_cmd else None,
'passexec_expiration':
Expand Down Expand Up @@ -1137,6 +1138,12 @@ def create(self, gid):
data['connection_params'] = connection_params

server = None
db_restriction = None
if 'db_res' in data and isinstance(data['db_res'], list):
db_restriction = ','.join(data['db_res'])
elif 'db_res' in data and 'db_res_type' in data and \
data['db_res_type'] == RESTRICTION_TYPE_SQL:
db_restriction = data['db_res']

try:
server = Server(
Expand All @@ -1151,8 +1158,8 @@ def create(self, gid):
config.ALLOW_SAVE_PASSWORD else 0,
comment=data.get('comment', None),
role=data.get('role', None),
db_res=','.join(data['db_res']) if 'db_res' in data and
isinstance(data['db_res'], list) else None,
db_res=db_restriction,
db_res_type=data.get('db_res_type', None),
bgcolor=data.get('bgcolor', None),
fgcolor=data.get('fgcolor', None),
service=data.get('service', None),
Expand Down
33 changes: 7 additions & 26 deletions web/pgadmin/browser/server_groups/servers/databases/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from functools import wraps

import json
from flask import render_template, current_app, request, jsonify
from flask import render_template, current_app, request, jsonify, Response
from flask_babel import gettext as _
from flask_security import current_user

Expand All @@ -24,7 +24,7 @@
parse_sec_labels_from_db, parse_variables_from_db, \
get_attributes_from_db_info
from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
parse_priv_to_db
parse_priv_to_db, get_db_disp_restriction
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import gone
from pgadmin.utils.ajax import make_json_response, \
Expand Down Expand Up @@ -266,14 +266,7 @@ def wrapped(self, *args, **kwargs):
def list(self, gid, sid):
last_system_oid = self.retrieve_last_system_oid()

db_disp_res = None
params = None
if self.manager and self.manager.db_res:
db_disp_res = ", ".join(
['%s'] * len(self.manager.db_res.split(','))
)
params = tuple(self.manager.db_res.split(','))

db_disp_res, params = get_db_disp_restriction(self.manager)
SQL = render_template(
"/".join([self.template_path, self._PROPERTIES_SQL]),
conn=self.conn,
Expand Down Expand Up @@ -351,15 +344,7 @@ def get_nodes(self, gid, sid, is_schema_diff=False):
self.manager.did in self.manager.db_info:
last_system_oid = self._DATABASE_LAST_SYSTEM_OID

server_node_res = self.manager

db_disp_res = None
params = None
if server_node_res and server_node_res.db_res:
db_disp_res = ", ".join(
['%s'] * len(server_node_res.db_res.split(','))
)
params = tuple(server_node_res.db_res.split(','))
db_disp_res, params = get_db_disp_restriction(self.manager)
SQL = render_template(
"/".join([self.template_path, self._NODES_SQL]),
last_system_oid=last_system_oid,
Expand Down Expand Up @@ -411,6 +396,8 @@ def get_nodes(self, gid, sid, is_schema_diff=False):
@check_precondition(action="nodes")
def nodes(self, gid, sid, is_schema_diff=False):
res = self.get_nodes(gid, sid, is_schema_diff)
if isinstance(res, Response):
return res

return make_json_response(
data=res,
Expand Down Expand Up @@ -1251,13 +1238,7 @@ def statistics(self, gid, sid, did=None):
"""
last_system_oid = self.retrieve_last_system_oid()

db_disp_res = None
params = None
if self.manager and self.manager.db_res:
db_disp_res = ", ".join(
['%s'] * len(self.manager.db_res.split(','))
)
params = tuple(self.manager.db_res.split(','))
db_disp_res, params = get_db_disp_restriction(self.manager)

conn = self.manager.connection()
status, res = conn.execute_dict(render_template(
Expand Down
41 changes: 36 additions & 5 deletions web/pgadmin/browser/server_groups/servers/static/js/server.ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ export default class ServerSchema extends BaseUISchema {
connect_now: true,
password: undefined,
save_password: false,
db_res: [],
db_res: undefined,
db_res_type: 'databases',
passexec: undefined,
passexec_expiration: undefined,
service: undefined,
Expand Down Expand Up @@ -468,11 +469,41 @@ export default class ServerSchema extends BaseUISchema {
readonly: obj.isConnected,
},
{
id: 'db_res', label: gettext('DB restriction'), type: 'select', group: gettext('Advanced'),
options: [],
id: 'db_res_type', label: gettext('DB restriction type'), type: 'toggle',
mode: ['properties', 'edit', 'create'], group: gettext('Advanced'),
options: [
{'label': gettext('Databases'), value: 'databases'},
{'label': gettext('SQL'), value: 'sql'},
],
readonly: obj.isConnectedOrShared,
depChange: ()=>{
return {
db_res: null,
};
}
},
{
id: 'db_res', label: gettext('DB restriction'), group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], readonly: obj.isConnectedOrShared,
controlProps: {
multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: 'Specify the databases to be restrict...'
deps: ['db_res_type'],
type: (state) => {
if (state.db_res_type == 'databases') {
return {
type: 'select',
options: [],
controlProps: {
multiple: true,
allowClear: false,
creatable: true,
noDropdown: true,
placeholder: 'Specify the databases to be restrict...'
}
};
} else {
return {
type: 'sql',
};
}
},
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,45 @@
"expected_data": {
"status_code": 200
}
},
{
"name": "Add server with post connection sql",
"url": "/browser/server/obj/",
"is_positive_test": true,
"test_data": {
"post_connection_sql": "set timezone to 'Asia/Kolkata'"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Add server with DB Restriction (Database names)",
"url": "/browser/server/obj/",
"is_positive_test": true,
"test_data": {
"db_res": ["dev", "qa"]
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Add server with DB Restriction (SQL)",
"url": "/browser/server/obj/",
"is_positive_test": true,
"test_data": {
"db_res": "SELECT datname FROM pg_database\nWHERE datistemplate = false AND datname ILIKE '%myprefix_%'\nORDER BY datname"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
}
],
"is_password_saved": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ def runTest(self):
if 'tags' in self.test_data:
self.server['tags'] = self.test_data['tags']

if 'post_connection_sql' in self.test_data:
self.server['post_connection_sql'] = (
self.test_data)['post_connection_sql']

if 'db_res' in self.test_data:
self.server['db_res'] = (
self.test_data)['db_res']

if self.is_positive_test:
if hasattr(self, 'with_save'):
self.server['save_password'] = self.with_save
Expand Down
Loading