Compare commits

...
54 Commits
Author SHA1 Message Date
Pravesh Sharma 500a5c7101 Fixed an issue where the pgAdmin configuration database wasn't being created on a fresh install when an external database was used for the configuration. #9125 2025-09-02 19:25:04 +05:30
Khushboo Vashi 5498682934 Update version for release v9.8 2025-09-01 16:39:08 +05:30
Khushboo Vashi cdeb18fcbb Fixed Cross-Origin Opener Policy (COOP) vulnerability in the OAuth 2.0 authentication flow (CVE-2025-9636). #9114 2025-09-01 16:34:18 +05:30
Aditya Toshniwal 0de635cc4c Fixed an issue where editor shortcuts fail when using Option key combinations on macOS, due to macOS treating Option+Key as a different key input. #9116 2025-09-01 16:21:03 +05:30
Akshay Joshi e09af0a0e9 Updated release note. 2025-08-28 16:18:38 +05:30
Akshay Joshi ade57d4ec4 Updated Python and JavaScript dependencies. 2025-08-28 12:56:20 +05:30
Rohit Bhati fc41c795f1 Add menu items for truncating foreign tables. #6396 2025-08-28 10:23:46 +05:30
Pravesh Sharma 7caaf2de82 Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7 #9095 2025-08-28 10:15:35 +05:30
Akshay Joshi 09bc544e83 Pin Paramiko to version 3.5.1 to fix the DSSKey error introduced in the latest release. #9090 2025-08-25 18:09:45 +05:30
Domenico Sgarbossa 42a47126aa Italian translation update 2025-08-25 18:08:23 +05:30
Aditya Toshniwal db87df8f82 Remove usage of BrowserFS as it is deprecated. #7448 2025-08-25 14:34:33 +05:30
Aditya Toshniwal 15c212a2df Change the default pgAdmin theme to System. #9093 2025-08-25 12:33:27 +05:30
Yogesh Mahajan 68627ab44e Allow user to configure security related gunicorn parameters. #8891 2025-08-25 11:52:07 +05:30
Pravesh Sharma 800fdce316 Unpin Electron version 35.x as the GTK crash issue has been resolved upstream. 2025-08-22 11:32:58 +05:30
Akshay Joshi a5557b466c Updated Python and Javascript dependencies. 2025-08-21 13:54:07 +05:30
Anil Sahoo cad6025ce9 Fixed an issue on Linux systems where the /upgrade_check API in server mode raised a KeyError. 2025-08-20 08:29:11 +05:30
Aditya Toshniwal f8b894d52b Update version for release v9.7 2025-08-18 15:59:05 +05:30
Aditya Toshniwal 9d0ec04667 Add support for changing cursor blink rate within the editors. #8712 2025-08-18 12:34:31 +05:30
webmasterzamora 24b4d35565 Spanish translation for version 9.7 2025-08-18 12:27:09 +05:30
Libor M. 0c8571de9e Czech translation for version 9.7 2025-08-18 12:26:16 +05:30
Akshay Joshi 2075dd5270 Updated message catalogs for v9.7 2025-08-14 16:23:49 +05:30
Yogesh Mahajan 7d0241008b Ensure that disabling "Save Application State" in Preferences prevents tool data from being saved and stops it from being restored on application restart. #9067 2025-08-14 13:47:30 +05:30
Akshay Joshi ecb652c7c5 Updated Python and JavaScript dependencies. 2025-08-12 11:54:03 +05:30
Pravesh Sharma 9b5f0f0f63 Fixed an issue where downloading images on the ERD tool was not working in desktop mode. #9047 2025-08-12 11:51:58 +05:30
Akshay Joshi 1eb7c9d3e2 Added support for builtin locale provider while creating Collation. #8931 2025-08-11 11:42:14 +05:30
Akshay Joshi a4c5e5683c Remove python version check for setuptools. 2025-08-07 18:56:51 +05:30
Khushboo Vashi d53a34b8df Fixed the issue where updating the name of a table column does not reflect in the corresponding primary key constraint. #7617 2025-08-07 16:36:38 +05:30
Anil Sahoo 8a4a3814ab Fixed an issue where custom column widths in the result grid of Query Tool or View/Edit Data were reset after re-executing a query. #7057 2025-08-07 15:32:40 +05:30
Akshay Joshi 6aaf47ddb4 Fixed JavaScript test cases. Replace deprecated inputProps with slotProps. 2025-08-06 18:54:04 +05:30
Akshay Joshi 4d078ab747 Updated Python and JavaScript dependecies. 2025-08-05 13:10:42 +05:30
excavator-matt 96de08212a Clarify version in issue template (#9020) 2025-08-04 14:43:55 +01:00
Anil Sahoo 2dd27d20c1 Fixed an issue where adding breakpoints caused errors, and stepping out of a nested function removed breakpoints from the parent function. #8982 2025-08-04 16:59:38 +05:30
Aditya Toshniwal 86c2769221 Check if sharp is failing on bundle before running image minimizer 2025-08-04 12:35:29 +05:30
Akshay Joshi 1316a3d7d8 Added all new connection string parameters introduced in PostgreSQL 16 and later. #8935 2025-08-04 12:02:30 +05:30
Aditya Toshniwal 9f866b0b27 Make image minimising optional based on sharp package availability. 2025-08-04 11:38:47 +05:30
Pravesh Sharma 829d6323b8 Fixed an issue where CPU usage was very high on Windows when opening the psql tool. #8864 2025-08-01 12:41:16 +05:30
Akshay Joshi 559c6cfa65 Added GENERIC_PLAN, MEMORY, SERIALIZE option to EXPLAIN/EXPLAIN ANALYZE command. #6456 2025-08-01 12:40:07 +05:30
Yogesh Mahajan 1f7fbb91f7 Ensure the scratch pad in the Query Tool is not restored after it is closed. #9007
Update the documentation for parameters that require file paths. #9008
2025-07-31 17:24:27 +05:30
Rohit Bhati 3b9359945e Added PKEY index in the index statistics summary. #8971 2025-07-31 17:12:15 +05:30
Aditya Toshniwal 99b822e472 Add support for server tag-based filtering in the Object Explorer. #8917 2025-07-31 17:06:40 +05:30
Aditya Toshniwal b2ec3a5acc Fixed an issue where pgAdmin failed to update the server connection status when the server was disconnected in the background and a refresh was performed on that server. #8149 2025-07-31 12:43:49 +05:30
Anil Sahoo 9eec4f5b8c Add support for automatic updates in the pgAdmin 4 Desktop application on macOS. #5766 2025-07-31 11:30:19 +05:30
Dave Page 6db0cc5c5d Ensure we have the tools to configure SELinux policies.
The semanage utility is required to configure the policy for the 
pgAdmin log/lib directories in server mode, but it may not always 
be installed on a system.
2025-07-30 11:45:07 +05:30
Aditya Toshniwal a197c85f3a Replace shortened URLs using goog.gl as goog.gl links will no longer be available 2025-07-30 10:17:06 +05:30
Akshay Joshi aa0a1f676d Fix PEP8 2025-07-29 18:12:40 +05:30
Akshay Joshi 5963593fed Replace keyword PROCEDURE with FUNCTION while creating trigger and event trigger. #6384 2025-07-29 17:48:41 +05:30
Akshay Joshi e0e99ddbb5 1) Updated release note.
2) Updated logic for release converter
2025-07-29 15:48:16 +05:30
Rohit Bhati 85510b961f Make Dashboard tables to be vertically resizable. #8650 2025-07-29 15:31:47 +05:30
Rohit Bhati bf229c0d79 Ensure DB restriction type is preserved while import and export server. #8867 2025-07-29 15:26:01 +05:30
Akshay Joshi 33dcb36db5 1) Added release note
2) Updated JavaScript dependencies
2025-07-29 11:57:07 +05:30
Pravesh Sharma 8a4eb4f8ad Fixed an issue in Firefox where the query window would shift to the left after opening the history tab or selecting a column header in the results grid. #8756 2025-07-28 16:27:31 +05:30
Pravesh Sharma 62eb330423 Introduced an ‘Editor’ preferences category and migrated all editor related settings into it. #8861 2025-07-28 14:09:16 +05:30
Murtuza Zabuawala e73d8f4532 Fixed incorrect behaviour of the option deduplicate items after creating the index. #8969 2025-07-28 13:23:39 +05:30
Akshay Joshi 79aeb5204c Updated Python and JavaScript dependencies. 2025-07-28 11:31:40 +05:30
237 changed files with 25338 additions and 16731 deletions

No files matched your search

+1 -1
View File
@@ -34,7 +34,7 @@ If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows 10, OSX monterey]
- Version: [e.g. 6.15]
- pgAdmin version: [e.g. 6.15]
- Mode: [e.g Desktop or Server]
- Browser (if running in server mode): [e.g. chrome, safari]
- Package type: [e.g. RPM, DEB, Python, Container, etc.]
+1 -1
View File
@@ -282,7 +282,7 @@ REM Main build sequence Ends
REM WGET
FOR /f "tokens=*" %%i IN ('npm info electron version') DO SET "ELECTRON_VERSION=%%i"
SET ELECTRON_VERSION="35.4.0"
:GET_NW
wget https://github.com/electron/electron/releases/download/v%ELECTRON_VERSION%/electron-v%ELECTRON_VERSION%-win32-x64.zip -O "%TMPDIR%\electron-v%ELECTRON_VERSION%-win32-x64.zip"
IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
+2 -1
View File
@@ -20,8 +20,9 @@ APP_REVISION := $(shell grep ^APP_REVISION web/version.py | awk -F"=" '{print $$
# Include only platform-independent builds in all
all: docs pip src
# Add BUILD_OPTS variable to pass arguments
appbundle:
./pkg/mac/build.sh
./pkg/mac/build.sh $(BUILD_OPTS)
install-node:
cd web && yarn install
File diff suppressed because it is too large. Load diff
+96
View File
@@ -0,0 +1,96 @@
Auto-Update of pgAdmin 4 Desktop Application
********************************************
pgAdmin 4's desktop application includes an automated update system built using
Electron's ``autoUpdater`` module. This feature enables users to receive and install
updates seamlessly, ensuring they always have access to the latest features and security fixes.
Supported Platforms
===================
- **macOS:** Fully supported with automatic updates enabled by default
- **Windows:** Not supported
- **Linux:** Not supported
Update Process Overview
=======================
1. **Check for Updates:**
- Automatic check on application startup
- Manual check available via pgAdmin 4 menu > Check for Updates
- Uses Electron's ``autoUpdater`` API to query update server
2. **Download Process:**
- Updates download automatically when detected
- Progress shown via notifications
- Background download prevents interruption of work
3. **Installation Flow:**
- User prompted to Install & Restart or Restart Later when update ready
- Update applied during application restart
The flow chart for the update process is as follows:
.. image:: images/auto_update_desktop_app.png
:alt: Auto-update Desktop App
:align: center
User Interface Components
=========================
1. **Notification Types:**
- Update available
- Download progress
- Update ready to install
- Error notifications
2. **Menu Integration:**
- Check for Updates option in pgAdmin 4 menu
- Restart to Update option when update available
Error Handling
==============
The system includes comprehensive error handling:
1. **Network Errors:**
- Connection timeouts
- Download failures
- Server unavailability
2. **Installation Errors:**
- Corrupted downloads
3. **Recovery Mechanisms:**
- Fallback to manual update
- Error reporting to logs
Security Considerations
=======================
The update system implements below security measures:
1. **Secure Communication:**
- Protected update metadata
Platform-Specific Notes
=======================
1. **macOS:**
- Uses native update mechanisms
- Requires signed packages
References
==========
- `Electron autoUpdater API Documentation <https://www.electronjs.org/docs/latest/api/auto-updater>`_
+3 -2
View File
@@ -40,6 +40,9 @@ settings:
as the existing one, but will be an independent object. If you choose to copy
an existing collation, you cannot modify the collation properties displayed on
this tab.
* Use the drop-down listbox next to *Provider* to select a locale services associated
with the collation. Possible values are: icu, libc and builtin.
**Note:** The builtin option is available from v17 onwards.
* Use the *Locale* field to specify a locale; a locale specifies language and
language formatting characteristics. If you specify this, you cannot specify
either of the following parameters. To view a list of locales supported by
@@ -50,8 +53,6 @@ settings:
* Use the *LC_CTYPE* field to specify a locale with specified character
classification. The locale must be applicable to the current database encoding.
(See CREATE DATABASE for details.)
* Use the drop-down listbox next to *Locale Provider* to select a locale services associated
with the collation. Possible values are: icu, libc. libc is the default.
* Move the switch next to *Deterministic* to *YES* to specify whether the collation should use
deterministic comparisons. By default, this option is set to true. In a
deterministic comparison, strings that are not byte-wise equal are considered
+7
View File
@@ -128,3 +128,10 @@ The configuration settings are stored in *runtime_config.json* file, which
will be available on Unix systems (~/.local/share/pgadmin/),
on Mac OS X (~/Library/Preferences/pgadmin),
and on Windows (%APPDATA%/pgadmin).
For details on the auto-update system for the desktop application, see
.. toctree::
:maxdepth: 1
auto_update_desktop_app
Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

+3 -1
View File
@@ -194,4 +194,6 @@ The following example shows both a minimally defined and a fully defined server:
"PostConnectionSQL": "set timezone='America/New_York'"
}
}
}
}
.. note:: If you're running pgAdmin in Server Mode, the values for parameters that require file paths should be specified relative to the user's home directory which is /<DATA_DIR>/storage/<user_id>/. If DATA_DIR is not specified, then it will /var/libe/pgadmin/storage/<user_id>/
+3 -3
View File
@@ -81,9 +81,9 @@ following options (in alphabetical order):
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Trigger(s)* | Click to *Disable* or *Enable* trigger(s) for the currently selected table. Options are displayed on the flyout menu. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Truncate* | Click to remove all rows from a table (*Truncate*), to remove all rows from a table and its child tables |
| | (*Truncate Cascade*) or to remove all rows from a table and automatically restart sequences owned by columns |
| | (*Truncate Restart Identity*). Options are displayed on the flyout menu. |
| *Truncate* | Click to remove all rows from a table/foreign tables (*Truncate*), to remove all rows from a table/foreign tables and |
| | its child tables (*Truncate Cascade*) or to remove all rows from a table/foreign tables and automatically restart |
| | sequences owned by columns (*Truncate Restart Identity*). Options are displayed on the flyout menu. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *View Data* | Click to access a context menu that provides several options for viewing data (see below). |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
+87 -68
View File
@@ -262,6 +262,93 @@ Use the fields on the *Options* panel to manage ERD preferences.
It allows to set the limit on the depth level pgAdmin should traverse
to find the relations. Use -1 to set no limit.
The Editor Node
*****************
Expand the *Editor* node to access panels that allow you to specify your preferences
for the editor across pgAdmin 4
.. image:: images/preferences_editor_keyboard_shortcuts.png
:alt: Preferences editor keyboard shortcuts section
:align: center
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
editor.
.. image:: images/preferences_editor_options.png
:alt: Preferences editor options section
:align: center
Use the fields on the *Options* panel to manage editor preferences.
* Use the *Auto-indent new line?* switch to specify whether the editor will
automatically indent new lines. When set to *True*, the editor will indent
new lines according to the indentation of the previous line.
* When the *Brace matching?* switch is set to *True*, the editor will highlight
pairs of matched braces.
* When the *Code folding?* switch is set to *False*, the editor will disable
code folding. Disabling will improve editor performance with large files.
* Use the *Cursor blink rate* field to adjust the speed at which the text cursor
blinks within the editors.
* Use the *Font family* field to be used for all SQL editors. The specified
font should already be installed on your system. If the font is not found,
the editor will fall back to the default font, Source Code Pro.
* When the *Font ligatures?* switch is set to *True*, ligatures will be
enabled in SQL text boxes and editors provided the configured font family
supports them.
* Use the *Font size* field to specify the font size that will be used in text
boxes and editors.
* When the *Highlight selection matches?* switch is set to *True*, the editor will
highlight matched selected text.
* When the *Insert bracket pairs?* switch is set to *True*, the editor will
automatically insert paired brackets.
* When the *Line wrapping* switch is set to *True*, the editor will implement
line-wrapping behavior.
* When the *Plain text mode?* switch is set to *True*, the editor mode will be
changed to text/plain. Keyword highlighting and code folding will be disabled.
This will improve editor performance with large files.
.. image:: images/preferences_editor_sql_formatting.png
:alt: Preferences SQL Formatting section
:align: center
Use the fields on the *SQL formatting* panel to specify your preferences for
reformatting of SQL.
* Use the *Data type case* option to specify whether to change data types
into upper, lower, or preserve case.
* Use the *Expression width* option to specify maximum number of characters
in parenthesized expressions to be kept on single line.
* Use the *Function case* option to specify whether to change function
names into upper, lower, or preserve case.
* Use the *Identifier case* option to specify whether to change identifiers
(object names) into upper, lower, or capitalized case.
* Use the *Keyword case* option to specify whether to change keywords into
upper, lower, or preserve case.
* Use *Lines between queries* to specify how many empty lines to leave
between SQL statements. If set to zero it puts no new line.
* Use *Logical operator new line* to specify newline placement before or
after logical operators (AND, OR, XOR).
* Use *New line before semicolon?* to specify whether to place query
separator (;) on a separate line.
* Use the *Spaces around operators?* option to specify whether or not to include
spaces on either side of operators.
* Use the *Tab size* option to specify the number of spaces per tab or indent.
* Use the *Use spaces?* option to select whether to use spaces or tabs when
indenting.
The Graphs Node
***************
@@ -427,46 +514,6 @@ Tool display.
* When the *Show query success notification?* switch is set to *True*, the Query Tool
will show notifications on successful query execution.
.. image:: images/preferences_sql_editor.png
:alt: Preferences sqleditor editor settings
:align: center
Use the fields on the *Editor* panel to change settings of the query editor.
* Use the *Auto-indent new line?* switch to specify whether the editor will
automatically indent new lines. When set to *True*, the editor will indent
new lines according to the indentation of the previous line.
* When the *Brace matching?* switch is set to *True*, the editor will highlight
pairs of matched braces.
* When the *Code folding?* switch is set to *False*, the editor will disable
code folding. Disabling will improve editor performance with large files.
* Use the *Font family* field to be used for all SQL editors. The specified
font should already be installed on your system. If the font is not found,
the editor will fall back to the default font, Source Code Pro.
* When the *Font ligatures?* switch is set to *True*, ligatures will be
enabled in SQL text boxes and editors provided the configured font family
supports them.
* Use the *Font size* field to specify the font size that will be used in text
boxes and editors.
* When the *Highlight selection matches?* switch is set to *True*, the editor will
highlight matched selected text.
* When the *Insert bracket pairs?* switch is set to *True*, the editor will
automatically insert paired brackets.
* When the *Line wrapping* switch is set to *True*, the editor will implement
line-wrapping behavior.
* When the *Plain text mode?* switch is set to *True*, the editor mode will be
changed to text/plain. Keyword highlighting and code folding will be disabled.
This will improve editor performance with large files.
.. image:: images/preferences_sql_explain.png
:alt: Preferences sqleditor explain options
:align: center
@@ -592,34 +639,6 @@ preferences for copied data.
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
Query Tool window navigation.
.. image:: images/preferences_sql_formatting.png
:alt: Preferences SQL Formatting section
:align: center
Use the fields on the *SQL formatting* panel to specify your preferences for
reformatting of SQL.
* Use the *Data type case* option to specify whether to change data types
into upper, lower, or preserve case.
* Use the *Expression width* option to specify maximum number of characters
in parenthesized expressions to be kept on single line.
* Use the *Function case* option to specify whether to change function
names into upper, lower, or preserve case.
* Use the *Identifier case* option to specify whether to change identifiers
(object names) into upper, lower, or capitalized case.
* Use the *Keyword case* option to specify whether to change keywords into
upper, lower, or preserve case.
* Use *Lines between queries* to specify how many empty lines to leave
between SQL statements. If set to zero it puts no new line.
* Use *Logical operator new line* to specify newline placement before or
after logical operators (AND, OR, XOR).
* Use *New line before semicolon?* to specify whether to place query
separator (;) on a separate line.
* Use the *Spaces around operators?* option to specify whether or not to include
spaces on either side of operators.
* Use the *Tab size* option to specify the number of spaces per tab or indent.
* Use the *Use spaces?* option to select whether to use spaces or tabs when
indenting.
The Schema Diff Node
********************
+12 -5
View File
@@ -140,20 +140,27 @@ Query Execution
| | | |
| | Navigate through the *Explain Options* menu to select options for the EXPLAIN command: | |
| | | |
| | * Select *Verbose* to display additional information regarding the query plan. | |
| | * Select *Buffers* to include information on buffer usage. | |
| | | |
| | * Select *Costs* to include information on the estimated startup and total cost of each | |
| | plan node, as well as the estimated number of rows and the estimated width of each | |
| | row. | |
| | | |
| | * Select *Buffers* to include information on buffer usage. | |
| | * Select *Generic Plan* to include the information on the Generic Plan. | |
| | | |
| | * Select *Memory* to include the information on memory consumption by the query planning phase. | |
| | | |
| | * Select *Serialize* to include information on the cost of serializing the query's output data, | |
| | that is converting it to text or binary format to send to the client. | |
| | | |
| | * Select *Settings* to include the information on the configuration parameters. | |
| | | |
| | * Select *Summary* to include the summary information about the query plan. | |
| | | |
| | * Select *Timing* to include information about the startup time and the amount of time | |
| | spent in each node of the query. | |
| | | |
| | * Select *Summary* to include the summary information about the query plan. | |
| | | |
| | * Select *Settings* to include the information on the configuration parameters. | |
| | * Select *Verbose* to display additional information regarding the query plan. | |
| | | |
| | * Select *Wal* to include the information on WAL record generation. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
+2
View File
@@ -12,6 +12,8 @@ notes for it.
:maxdepth: 1
release_notes_9_8
release_notes_9_7
release_notes_9_6
release_notes_9_5
release_notes_9_4
+51
View File
@@ -0,0 +1,51 @@
***********
Version 9.7
***********
Release date: 2025-08-21
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.6.
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 #5766 <https://github.com/pgadmin-org/pgadmin4/issues/5766>`_ - Add support for automatic updates in the pgAdmin 4 Desktop application on macOS.
| `Issue #6456 <https://github.com/pgadmin-org/pgadmin4/issues/6456>`_ - Added GENERIC_PLAN, MEMORY, SERIALIZE option to EXPLAIN/EXPLAIN ANALYZE command.
| `Issue #8712 <https://github.com/pgadmin-org/pgadmin4/issues/8712>`_ - Add support for changing cursor blink rate within the editors.
| `Issue #8917 <https://github.com/pgadmin-org/pgadmin4/issues/8917>`_ - Add support for server tag-based filtering in the Object Explorer.
| `Issue #8931 <https://github.com/pgadmin-org/pgadmin4/issues/8931>`_ - Added support for builtin locale provider while creating Collation.
Housekeeping
************
| `Issue #6384 <https://github.com/pgadmin-org/pgadmin4/issues/6384>`_ - Replace keyword PROCEDURE with FUNCTION while creating trigger and event trigger.
| `Issue #8861 <https://github.com/pgadmin-org/pgadmin4/issues/8861>`_ - Introduced an ‘Editor’ preferences category and migrated all editor related settings into it.
Bug fixes
*********
| `Issue #7057 <https://github.com/pgadmin-org/pgadmin4/issues/7057>`_ - Fixed an issue where custom column widths in the result grid of Query Tool or View/Edit Data were reset after re-executing a query.
| `Issue #7617 <https://github.com/pgadmin-org/pgadmin4/issues/7617>`_ - Fixed the issue where updating the name of a table column does not reflect in the corresponding primary key constraint.
| `Issue #8149 <https://github.com/pgadmin-org/pgadmin4/issues/8149>`_ - Fixed an issue where pgAdmin failed to update the server connection status when the server was disconnected in the background and a refresh was performed on that server.
| `Issue #8650 <https://github.com/pgadmin-org/pgadmin4/issues/8650>`_ - Make Dashboard tables to be vertically resizable.
| `Issue #8756 <https://github.com/pgadmin-org/pgadmin4/issues/8756>`_ - Fixed an issue in Firefox where the query window would shift to the left after opening the history tab or selecting a column header in the results grid.
| `Issue #8864 <https://github.com/pgadmin-org/pgadmin4/issues/8864>`_ - Fixed an issue where CPU usage was very high on Windows when opening the psql tool.
| `Issue #8867 <https://github.com/pgadmin-org/pgadmin4/issues/8867>`_ - Ensure DB restriction type is preserved while import and export server.
| `Issue #8969 <https://github.com/pgadmin-org/pgadmin4/issues/8969>`_ - Fixed incorrect behaviour of the option deduplicate items after creating the index.
| `Issue #8971 <https://github.com/pgadmin-org/pgadmin4/issues/8971>`_ - Added PKEY index in the index statistics summary.
| `Issue #9073 <https://github.com/pgadmin-org/pgadmin4/issues/9073>`_ - Fixed an issue where adding breakpoints caused errors, and stepping out of a nested function removed breakpoints from the parent function.
| `Issue #9007 <https://github.com/pgadmin-org/pgadmin4/issues/9007>`_ - Ensure the scratch pad in the Query Tool is not restored after it is closed.
| `Issue #9008 <https://github.com/pgadmin-org/pgadmin4/issues/9008>`_ - Update the documentation for parameters that require file paths.
| `Issue #9047 <https://github.com/pgadmin-org/pgadmin4/issues/9047>`_ - Fixed an issue where downloading images on the ERD tool was not working in desktop mode.
| `Issue #9067 <https://github.com/pgadmin-org/pgadmin4/issues/9067>`_ - Ensure that disabling "Save Application State" in Preferences prevents tool data from being saved and stops it from being restored on application restart.
+40
View File
@@ -0,0 +1,40 @@
***********
Version 9.8
***********
Release date: 2025-09-04
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.7.
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.5
New features
************
| `Issue #6396 <https://github.com/pgadmin-org/pgadmin4/issues/6396>`_ - Add menu items for truncating foreign tables.
| `Issue #7928 <https://github.com/pgadmin-org/pgadmin4/issues/7928>`_ - Added Debian Trixie as a supported platform for pgAdmin.
| `Issue #8891 <https://github.com/pgadmin-org/pgadmin4/issues/8891>`_ - Allow user to configure security related gunicorn parameters.
| `Issue #9093 <https://github.com/pgadmin-org/pgadmin4/issues/9093>`_ - Change the default pgAdmin theme to System.
Housekeeping
************
| `Issue #7448 <https://github.com/pgadmin-org/pgadmin4/issues/7448>`_ - Remove usage of BrowserFS as it is deprecated.
Bug fixes
*********
| `Issue #9090 <https://github.com/pgadmin-org/pgadmin4/issues/9090>`_ - Pin Paramiko to version 3.5.1 to fix the DSSKey error introduced in the latest release.
| `Issue #9095 <https://github.com/pgadmin-org/pgadmin4/issues/9095>`_ - Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7.
| `Issue #9114 <https://github.com/pgadmin-org/pgadmin4/issues/9114>`_ - Fixed Cross-Origin Opener Policy (COOP) vulnerability in the OAuth 2.0 authentication flow (CVE-2025-9636).
| `Issue #9116 <https://github.com/pgadmin-org/pgadmin4/issues/9116>`_ - Fixed an issue where editor shortcuts fail when using Option key combinations on macOS, due to macOS treating Option+Key as a different key input.
| `Issue #9125 <https://github.com/pgadmin-org/pgadmin4/issues/9125>`_ - Fixed an issue where the pgAdmin configuration database wasn't being created on a fresh install when an external database was used for the configuration.
+23
View File
@@ -13,6 +13,8 @@ the selected object node.
:alt: pgAdmin Toolbar
:align: center
* Use the :ref:`Object filter <object-explorer-filter>` button to access
the Object Filter popup. It helps you filter objects in the Object Explorer tree.
* Use the :ref:`Query Tool <query_tool>` button to open the Query Tool in the
current database context.
* Use the :ref:`View Data <editgrid>` button to view/edit the data stored in a
@@ -23,3 +25,24 @@ the selected object node.
dialog. It helps you search any database object.
* Use the :ref:`PSQL Tool <psql_tool>` button to open the PSQL in the current
database context.
.. _object-explorer-filter:
*******************************
`Object Explorer Filter`:index:
*******************************
.. image:: /images/object_explorer_filter.png
:alt: Object Explorer Filter Dialog
:align: center
Use this tool to filter objects in the Object Explorer by
following fields:
* Use the *Tags* field to filter the servers with one or more server tags. The
servers with any of the selected tags will be displayed in the Object Explorer.
You can also create a new tag by typing in the field and pressing Enter.
Click the **Apply** button to apply the filter. Please note the object explorer will
refresh after applying the filter.
+2 -2
View File
@@ -5,8 +5,8 @@ Debian and Ubuntu.
## Supported platforms
* Debian 11 & 12
* Ubuntu 22.04, 24.04 & 24.10
* Debian 11, 12 & 13
* Ubuntu 22.04, 24.04 & 25.04
## Build configuration
+1 -1
View File
@@ -188,5 +188,5 @@ fi
if [ -n "${PGADMIN_ENABLE_TLS}" ]; then
exec /venv/bin/gunicorn --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" --timeout "${TIMEOUT}" --bind "${BIND_ADDRESS}" -w 1 --threads "${GUNICORN_THREADS:-25}" --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" --keyfile /certs/server.key --certfile /certs/server.cert -c gunicorn_config.py run_pgadmin:app
else
exec /venv/bin/gunicorn --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" --timeout "${TIMEOUT}" --bind "${BIND_ADDRESS}" -w 1 --threads "${GUNICORN_THREADS:-25}" --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" -c gunicorn_config.py run_pgadmin:app
exec /venv/bin/gunicorn --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" --limit-request-fields "${GUNICORN_LIMIT_REQUEST_FIELDS:-100}" --limit-request-field_size "${GUNICORN_LIMIT_REQUEST_FIELD_SIZE:-8190}" --timeout "${TIMEOUT}" --bind "${BIND_ADDRESS}" -w 1 --threads "${GUNICORN_THREADS:-25}" --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" -c gunicorn_config.py run_pgadmin:app
fi
-2
View File
@@ -140,8 +140,6 @@ _build_runtime() {
fi
ELECTRON_VERSION="$(npm info electron version)"
# Can't use the latest as it is not compatible with few OS versions.
ELECTRON_VERSION="35.4.0"
pushd "${BUILDROOT}" > /dev/null || exit
while true;do
+6 -2
View File
@@ -31,11 +31,15 @@ Either build the sources or get them from macports or similar:
*notarization.conf* and set the values accordingly. Note that notarization
will fail if the code isn't signed.
4. To build, go to pgAdmin4 source root directory and execute:
4. To build only DMG file, go to pgAdmin4 source root directory and execute:
make appbundle
To build both DMG and ZIP files, go to pgAdmin4 source root directory and execute:
make appbundle BUILD_OPTS="--zip"
This will create the python virtual environment and install all the required
python modules mentioned in the requirements file using pip, build the
runtime code and finally create the app bundle and the DMG in *./dist*
runtime code and finally create the app bundle and the DMG and/or ZIP in *./dist*
directory.
+55 -16
View File
@@ -34,7 +34,6 @@ _build_runtime() {
fi
ELECTRON_VERSION="$(npm info electron version)"
ELECTRON_VERSION="35.4.0"
pushd "${BUILD_ROOT}" > /dev/null || exit
while true;do
@@ -385,6 +384,24 @@ _codesign_bundle() {
-i org.pgadmin.pgadmin4 \
--sign "${DEVELOPER_ID}" \
"${BUNDLE_DIR}"
echo "Verifying the signature from bundle dir..."
codesign --verify --deep --verbose=4 "${BUNDLE_DIR}"
}
_create_zip() {
ZIP_NAME="${DMG_NAME%.dmg}.zip"
echo "ZIP_NAME: ${ZIP_NAME}"
echo "Compressing pgAdmin 4.app in bundle dir into ${ZIP_NAME}..."
ditto -c -k --sequesterRsrc --keepParent "${BUNDLE_DIR}" "${ZIP_NAME}"
if [ $? -ne 0 ]; then
echo "Failed to create the ZIP file. Exiting."
exit 1
fi
echo "Successfully created ZIP file: ${ZIP_NAME}"
}
_create_dmg() {
@@ -426,17 +443,22 @@ _codesign_dmg() {
"${DMG_NAME}"
}
_notarize_pkg() {
local FILE_NAME="$1"
local STAPLE_TARGET="$2"
local FILE_LABEL="$3"
if [ "${CODESIGN}" -eq 0 ]; then
return
fi
echo "Uploading DMG for Notarization ..."
STATUS=$(xcrun notarytool submit "${DMG_NAME}" \
--team-id "${DEVELOPER_TEAM_ID}" \
--apple-id "${DEVELOPER_USER}" \
--password "${DEVELOPER_ASP}" 2>&1)
echo "Uploading ${FILE_LABEL} for Notarization ..."
STATUS=$(xcrun notarytool submit "${FILE_NAME}" \
--team-id "${DEVELOPER_TEAM_ID}" \
--apple-id "${DEVELOPER_USER}" \
--password "${DEVELOPER_ASP}" 2>&1)
echo "${STATUS}"
# Get the submission ID
SUBMISSION_ID=$(echo "${STATUS}" | awk -F ': ' '/id:/ { print $2; exit; }')
@@ -444,16 +466,16 @@ _notarize_pkg() {
echo "Waiting for Notarization to be completed ..."
xcrun notarytool wait "${SUBMISSION_ID}" \
--team-id "${DEVELOPER_TEAM_ID}" \
--apple-id "${DEVELOPER_USER}" \
--password "${DEVELOPER_ASP}"
--team-id "${DEVELOPER_TEAM_ID}" \
--apple-id "${DEVELOPER_USER}" \
--password "${DEVELOPER_ASP}"
# Print status information
REQUEST_STATUS=$(xcrun notarytool info "${SUBMISSION_ID}" \
--team-id "${DEVELOPER_TEAM_ID}" \
--apple-id "${DEVELOPER_USER}" \
--password "${DEVELOPER_ASP}" 2>&1 | \
awk -F ': ' '/status:/ { print $2; }')
--team-id "${DEVELOPER_TEAM_ID}" \
--apple-id "${DEVELOPER_USER}" \
--password "${DEVELOPER_ASP}" 2>&1 | \
awk -F ': ' '/status:/ { print $2; }')
if [[ "${REQUEST_STATUS}" != "Accepted" ]]; then
echo "Notarization failed."
@@ -461,11 +483,28 @@ _notarize_pkg() {
fi
# Staple the notarization
echo "Stapling the notarization to the pgAdmin DMG..."
if ! xcrun stapler staple "${DMG_NAME}"; then
echo "Stapling the notarization to the ${FILE_LABEL}..."
if ! xcrun stapler staple "${STAPLE_TARGET}"; then
echo "Stapling failed."
exit 1
fi
# For ZIP, recreate the zip after stapling
if [[ "${FILE_LABEL}" == "ZIP" ]]; then
ditto -c -k --keepParent "${BUNDLE_DIR}" "${ZIP_NAME}"
if [ $? != 0 ]; then
echo "ERROR: could not staple ${ZIP_NAME}"
exit 1
fi
fi
echo "Notarization completed successfully."
}
_notarize_zip() {
_notarize_pkg "${ZIP_NAME}" "${BUNDLE_DIR}" "ZIP"
}
_notarize_dmg() {
_notarize_pkg "${DMG_NAME}" "${DMG_NAME}" "DMG"
}
+39 -3
View File
@@ -57,6 +57,32 @@ if [ "${PGADMIN_PYTHON_VERSION}" == "" ]; then
export PGADMIN_PYTHON_VERSION=3.13.1
fi
# Initialize variables
CREATE_ZIP=0
CREATE_DMG=1
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--zip)
CREATE_ZIP=1
shift
;;
--help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " --zip Create both ZIP and DMG files"
echo " --help Display this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/build-functions.sh"
@@ -69,6 +95,16 @@ _complete_bundle
_generate_sbom
_codesign_binaries
_codesign_bundle
_create_dmg
_codesign_dmg
_notarize_pkg
# Handle ZIP creation if requested
if [ "${CREATE_ZIP}" -eq 1 ]; then
_create_zip
_notarize_zip
fi
# Handle DMG creation if not disabled
if [ "${CREATE_DMG}" -eq 1 ]; then
_create_dmg
_codesign_dmg
_notarize_dmg
fi
+1 -1
View File
@@ -163,7 +163,7 @@ BuildArch: noarch
Summary: The web interface for pgAdmin, hosted under Apache HTTPD.
License: PostgreSQL
URL: https://www.pgadmin.org/
Requires: ${APP_NAME}-server = ${RPM_VERSION}, httpd, ${PYTHON_BINARY_WITHOUT_DOTS}-mod_wsgi
Requires: ${APP_NAME}-server = ${RPM_VERSION}, httpd, ${PYTHON_BINARY_WITHOUT_DOTS}-mod_wsgi, policycoreutils-python-utils
%description
The web interface for pgAdmin, hosted under Apache HTTPD. pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.
+5 -4
View File
@@ -13,12 +13,12 @@
###############################################################################
Authlib==1.6.*
azure-identity==1.23.0
azure-identity==1.24.0
azure-mgmt-rdbms==10.1.0
azure-mgmt-resource==24.0.0
azure-mgmt-subscription==3.1.1
bcrypt==4.3.*
boto3==1.39.*
boto3==1.40.*
cryptography==45.0.*
Flask-Babel==4.0.*
Flask-Compress==1.*
@@ -39,15 +39,16 @@ jsonformatter~=0.3.4
keyring==25.*
ldap3==2.*
libgravatar==1.0.*
paramiko==3.5.1
passlib==1.*
psutil==7.0.*
psycopg[c]==3.2.9
pyotp==2.*
python-dateutil==2.*
pytz==2025.*
pywinpty==2.0.*; sys_platform=="win32"
pywinpty==3.0.*; sys_platform=="win32"
qrcode[pil]==8.*; python_version >= '3.9'
setuptools==80.*; python_version >= '3.12'
setuptools==80.*
SQLAlchemy==2.*
sqlparse==0.*
sshtunnel==0.*
+5 -5
View File
@@ -10,14 +10,14 @@
"start": "electron .",
"linter": "yarn run eslint -c .eslintrc.js ."
},
"packageManager": "yarn@3.8.7",
"packageManager": "yarn@4.9.2",
"devDependencies": {
"electron": "37.2.0",
"eslint": "^9.30.1",
"eslint-plugin-unused-imports": "^4.1.4"
"electron": "37.3.1",
"eslint": "^9.34.0",
"eslint-plugin-unused-imports": "^4.2.0"
},
"dependencies": {
"axios": "^1.10.0",
"axios": "^1.11.0",
"electron-context-menu": "^4.1.0",
"electron-store": "^10.1.0"
}
+128
View File
@@ -0,0 +1,128 @@
import { autoUpdater, ipcMain } from 'electron';
import { refreshMenus } from './menu.js';
import * as misc from './misc.js';
// This function stores the flags in configStore that are needed
// for auto-update and refreshes menus
export function updateConfigAndMenus(event, configStore, pgAdminMainScreen, menuCallbacks) {
const flags = {
'update-available': { update_downloading: true },
'update-not-available': { update_downloading: false },
'update-downloaded': { update_downloading: false, update_downloaded: true },
'error-close': { update_downloading: false, update_downloaded: false },
};
const flag = flags[event];
if (flag) {
Object.entries(flag).forEach(([k, v]) => configStore.set(k, v));
refreshMenus(pgAdminMainScreen, configStore, menuCallbacks);
}
}
// This function registers autoUpdater event listeners ONCE
function registerAutoUpdaterEvents({ pgAdminMainScreen, configStore, menuCallbacks }) {
autoUpdater.on('checking-for-update', () => {
misc.writeServerLog('[Auto-Updater]: Checking for update...');
});
autoUpdater.on('update-available', () => {
updateConfigAndMenus('update-available', configStore, pgAdminMainScreen, menuCallbacks);
misc.writeServerLog('[Auto-Updater]: Update downloading...');
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', { update_downloading: true });
});
autoUpdater.on('update-not-available', () => {
updateConfigAndMenus('update-not-available', configStore, pgAdminMainScreen, menuCallbacks);
misc.writeServerLog('[Auto-Updater]: No update available...');
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', { no_update_available: true });
});
autoUpdater.on('update-downloaded', () => {
updateConfigAndMenus('update-downloaded', configStore, pgAdminMainScreen, menuCallbacks);
misc.writeServerLog('[Auto-Updater]: Update downloaded...');
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', { update_downloaded: true });
});
autoUpdater.on('error', (message) => {
updateConfigAndMenus('error-close', configStore, pgAdminMainScreen, menuCallbacks);
misc.writeServerLog(`[Auto-Updater]: ${message}`);
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', { error: true, errMsg: message });
});
}
// Handles 'sendDataForAppUpdate' IPC event: updates config, refreshes menus, triggers update check, or installs update if requested.
function handleSendDataForAppUpdate({
pgAdminMainScreen,
configStore,
menuCallbacks,
baseUrl,
UUID,
forceQuitAndInstallUpdate,
}) {
return (_, data) => {
// Only update the auto-update enabled flag and refresh menus if the value has changed or is not set
if (typeof data.check_for_updates !== 'undefined') {
const currentFlag = configStore.get('auto_update_enabled');
if (typeof currentFlag === 'undefined' || currentFlag !== data.check_for_updates) {
configStore.set('auto_update_enabled', data.check_for_updates);
refreshMenus(pgAdminMainScreen, configStore, menuCallbacks);
}
}
// If auto-update is enabled, proceed with the update check
if (
data.auto_update_url &&
data.upgrade_version &&
data.upgrade_version_int &&
data.current_version_int &&
data.product_name
) {
const ftpUrl = encodeURIComponent(
`${data.auto_update_url}/pgadmin4-${data.upgrade_version}-${process.arch}.zip`
);
let serverUrl = `${baseUrl}/misc/auto_update/${data.current_version_int}/${data.upgrade_version}/${data.upgrade_version_int}/${data.product_name}/${ftpUrl}/?key=${UUID}`;
try {
autoUpdater.setFeedURL({ url: serverUrl });
misc.writeServerLog('[Auto-Updater]: Initiating update check...');
autoUpdater.checkForUpdates();
} catch (err) {
misc.writeServerLog('[Auto-Updater]: Error setting autoUpdater feed URL: ' + err.message);
if (pgAdminMainScreen) {
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', {
error: true,
errMsg: 'Failed to check for updates. Please try again later.',
});
}
return;
}
}
// If the user has requested to install the update immediately
if (data.install_update_now) {
forceQuitAndInstallUpdate();
}
};
}
export function setupAutoUpdater({
pgAdminMainScreen,
configStore,
menuCallbacks,
baseUrl,
UUID,
forceQuitAndInstallUpdate,
}) {
// For now only macOS is supported for electron auto-update
if (process.platform === 'darwin') {
registerAutoUpdaterEvents({ pgAdminMainScreen, configStore, menuCallbacks });
ipcMain.on(
'sendDataForAppUpdate',
handleSendDataForAppUpdate({
pgAdminMainScreen,
configStore,
menuCallbacks,
baseUrl,
UUID,
forceQuitAndInstallUpdate,
})
);
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
import { app, ipcMain, dialog, BrowserWindow, shell } from 'electron';
import fs from 'fs';
import path from 'path';
import Buffer from 'buffer';
import { Buffer } from 'buffer';
import { setBadge, clearBadge, clearProgress, setProgress } from './progress.js';
import { writeServerLog } from './misc.js';
+120 -67
View File
@@ -12,6 +12,7 @@ import { app, Menu, ipcMain, BrowserWindow, globalShortcut } from 'electron';
const isMac = process.platform == 'darwin';
const isLinux = process.platform == 'linux';
let mainMenu;
let cachedMenus;
// Use to convert shortcut to accelerator for electron.
function convertShortcutToAccelerator({ control, meta, shift, alt, key } = {}) {
@@ -29,18 +30,27 @@ function convertShortcutToAccelerator({ control, meta, shift, alt, key } = {}) {
return [...mods, k].join('+');
}
function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) {
const template = [];
// bind all menus click event.
pgadminMenus = pgadminMenus.map((menuItem)=>{
return {
...menuItem,
submenu: menuItem.submenu?.map((subMenuItem)=>{
const smName = `${menuItem.name}_${subMenuItem.name}`;
return {
...subMenuItem,
accelerator: convertShortcutToAccelerator(subMenuItem.shortcut),
// Binds click events to all menu and submenu items recursively.
function bindMenuClicks(pgadminMenus, pgAdminMainScreen) {
return pgadminMenus.map((menuItem) => ({
...menuItem,
submenu: menuItem.submenu?.map((subMenuItem) => {
const smName = `${menuItem.name}_${subMenuItem.name}`;
return {
...subMenuItem,
accelerator: convertShortcutToAccelerator(subMenuItem.shortcut),
click: (_menuItem, _browserWindow, event)=>{
if(event?.triggeredByAccelerator) {
// We will ignore the click event if it is triggered by an accelerator.
// We use accelerator to only show the shortcut title in the menu.
// The actual shortcut is already handled by pgAdmin.
return;
}
pgAdminMainScreen.webContents.send('menu-click', smName);
},
submenu: subMenuItem.submenu?.map((deeperSubMenuItem) => ({
...deeperSubMenuItem,
accelerator: convertShortcutToAccelerator(deeperSubMenuItem.shortcut),
click: (_menuItem, _browserWindow, event)=>{
if(event?.triggeredByAccelerator) {
// We will ignore the click event if it is triggered by an accelerator.
@@ -48,66 +58,94 @@ function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) {
// The actual shortcut is already handled by pgAdmin.
return;
}
pgAdminMainScreen.webContents.send('menu-click', smName);
pgAdminMainScreen.webContents.send('menu-click', `${smName}_${deeperSubMenuItem.name}`);
},
submenu: subMenuItem.submenu?.map((deeperSubMenuItem)=>{
return {
...deeperSubMenuItem,
accelerator: convertShortcutToAccelerator(deeperSubMenuItem.shortcut),
click: (_menuItem, _browserWindow, event)=>{
if(event?.triggeredByAccelerator) {
// We will ignore the click event if it is triggered by an accelerator.
// We use accelerator to only show the shortcut title in the menu.
// The actual shortcut is already handled by pgAdmin.
return;
}
pgAdminMainScreen.webContents.send('menu-click', `${smName}_${deeperSubMenuItem.name}`);
},
};
}),
};
}),
};
});
})),
};
}),
}));
}
// Handles auto-update related menu items for macOS.
// Adds or disables update menu items based on config state.
function handleAutoUpdateMenu(menuFile, configStore, callbacks) {
if (!configStore.get('auto_update_enabled')) return;
if (configStore.get('update_downloaded')) {
// Add "Restart to Update" if update is downloaded
menuFile.submenu.unshift({
name: 'mnu_restart_to_update',
id: 'mnu_restart_to_update',
label: 'Restart to Update...',
enabled: true,
priority: 998,
click: callbacks['restart_to_update'],
});
} else {
// Add "Check for Updates" if update is not downloaded
menuFile.submenu.unshift({
name: 'mnu_check_updates',
id: 'mnu_check_updates',
label: 'Check for Updates...',
enabled: true,
priority: 998,
click: callbacks['check_for_updates'],
});
}
// Disable "Check for Updates" if update is downloading
if (configStore.get('update_downloading')) {
menuFile.submenu.forEach((item) => {
if (item.id == 'mnu_check_updates') item.enabled = false;
});
}
}
// Remove About pgAdmin 4 from help menu and add it to the top of menuFile submenu.
function moveAboutMenuToTop(pgadminMenus, menuFile) {
const helpMenu = pgadminMenus.find((menu) => menu.name == 'help');
if (!helpMenu) return;
const aboutItem = helpMenu.submenu.find((item) => item.name === 'mnu_about');
if (!aboutItem) return;
helpMenu.submenu = helpMenu.submenu.filter((item) => item.name !== 'mnu_about');
menuFile.submenu.unshift(aboutItem);
menuFile.submenu.splice(2, 0, { type: 'separator' });
}
// Builds the application menu template and binds menu click events.
// Handles platform-specific menu structure and dynamic menu items.
function buildMenu(pgadminMenus, pgAdminMainScreen, configStore, callbacks) {
const template = [];
pgadminMenus = bindMenuClicks(pgadminMenus, pgAdminMainScreen);
let menuFile = pgadminMenus.shift();
// macOS-specific menu modifications
if (isMac) {
// Remove About pgAdmin 4 from help menu and add it to the top of menuFile submenu.
const helpMenu = pgadminMenus.find((menu) => menu.name == 'help');
if (helpMenu) {
const aboutItem = helpMenu.submenu.find((item) => item.name === 'mnu_about');
if (aboutItem) {
helpMenu.submenu = helpMenu.submenu.filter((item) => item.name !== 'mnu_about');
menuFile.submenu.unshift(aboutItem);
menuFile.submenu.splice(1, 0, { type: 'separator' });
}
}
handleAutoUpdateMenu(menuFile, configStore, callbacks);
moveAboutMenuToTop(pgadminMenus, menuFile);
}
template.push({
...menuFile,
submenu: [
...menuFile.submenu,
{ type: 'separator' },
{
label: 'View Logs...', click: callbacks['view_logs'],
},
{
label: 'Configure runtime...', click: callbacks['configure'],
},
{ label: 'View Logs...', click: callbacks['view_logs'] },
{ label: 'Configure runtime...', click: callbacks['configure'] },
{ type: 'separator' },
...(isMac ? [
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
] : []),
...(isMac
? [
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
]
: []),
{ role: 'quit' },
],
});
if(isMac) {
if (isMac) {
template[0].label = app.name;
}
@@ -119,8 +157,14 @@ function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) {
{
label: 'View',
submenu: [
{ label: 'Reload', click: callbacks['reloadApp']},
{ label: 'Toggle Developer Tools', click: ()=>BrowserWindow.getFocusedWindow().webContents.openDevTools({ mode: 'bottom' })},
{ label: 'Reload', click: callbacks['reloadApp'] },
{
label: 'Toggle Developer Tools',
click: () =>
BrowserWindow.getFocusedWindow().webContents.openDevTools({
mode: 'bottom',
}),
},
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
@@ -128,25 +172,34 @@ function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) {
{ type: 'separator' },
].concat(isLinux ? [] : [{ role: 'togglefullscreen' }]),
},
{ role: 'windowMenu' },
{ role: 'windowMenu' }
);
template.push(pgadminMenus[pgadminMenus.length-1]);
template.push(pgadminMenus[pgadminMenus.length - 1]);
return Menu.buildFromTemplate(template);
}
export function setupMenu(pgAdminMainScreen, callbacks={}) {
function buildAndSetMenus(menus, pgAdminMainScreen, configStore, callbacks={}) {
mainMenu = buildMenu(menus, pgAdminMainScreen, configStore, callbacks);
if(isMac) {
Menu.setApplicationMenu(mainMenu);
} else {
pgAdminMainScreen.setMenu(mainMenu);
}
}
export function refreshMenus(pgAdminMainScreen, configStore, callbacks={}) {
buildAndSetMenus(cachedMenus, pgAdminMainScreen, configStore, callbacks);
}
export function setupMenu(pgAdminMainScreen, configStore, callbacks={}) {
ipcMain.on('setMenus', (event, menus)=>{
mainMenu = buildMenu(menus, pgAdminMainScreen, callbacks);
// this is important because the shortcuts are registered multiple times
// when the menu is set multiple times using accelerators.
globalShortcut.unregisterAll();
if(isMac) {
Menu.setApplicationMenu(mainMenu);
} else {
pgAdminMainScreen.setMenu(mainMenu);
}
cachedMenus = menus; //It will be used later for refreshing the menus
buildAndSetMenus(menus, pgAdminMainScreen, configStore, callbacks);
ipcMain.on('enable-disable-menu-items', (event, menu, menuItem)=>{
const menuItemObj = mainMenu.getMenuItemById(menuItem?.id);
+110 -34
View File
@@ -6,7 +6,7 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { app, BrowserWindow, dialog, ipcMain, Menu, shell, screen } from 'electron';
import { app, BrowserWindow, dialog, ipcMain, Menu, shell, screen, autoUpdater } from 'electron';
import axios from 'axios';
import Store from 'electron-store';
import fs from 'fs';
@@ -17,6 +17,7 @@ import { fileURLToPath } from 'url';
import { setupMenu } from './menu.js';
import contextMenu from 'electron-context-menu';
import { setupDownloader } from './downloader.js';
import { setupAutoUpdater, updateConfigAndMenus } from './autoUpdaterHandler.js';
const configStore = new Store({
defaults: {
@@ -35,9 +36,13 @@ let configureWindow = null,
viewLogWindow = null;
let serverPort = 5050;
let UUID = crypto.randomUUID();
let appStartTime = (new Date()).getTime();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
let baseUrl = `http://127.0.0.1:${serverPort}`;
let docsURLSubStrings = ['www.enterprisedb.com', 'www.postgresql.org', 'www.pgadmin.org', 'help/help'];
process.env['ELECTRON_ENABLE_SECURITY_WARNINGS'] = false;
@@ -45,6 +50,40 @@ process.env['ELECTRON_ENABLE_SECURITY_WARNINGS'] = false;
// Paths to the rest of the app
let [pythonPath, pgadminFile] = misc.getAppPaths(__dirname);
const menuCallbacks = {
'check_for_updates': ()=>{
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', {check_version_update: true});
},
'restart_to_update': ()=>{
forceQuitAndInstallUpdate();
},
'view_logs': ()=>{
if(viewLogWindow === null || viewLogWindow?.isDestroyed()) {
viewLogWindow = new BrowserWindow({
show: false,
width: 800,
height: 460,
position: 'center',
resizable: false,
parent: pgAdminMainScreen,
icon: '../../assets/pgAdmin4.png',
webPreferences: {
preload: path.join(__dirname, 'other_preload.js'),
},
});
viewLogWindow.loadFile('./src/html/view_log.html');
viewLogWindow.once('ready-to-show', ()=>{
viewLogWindow.show();
});
} else {
viewLogWindow.hide();
viewLogWindow.show();
}
},
'configure': openConfigure,
'reloadApp': reloadApp,
};
// Do not allow a second instance of pgAdmin to run.
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
@@ -153,6 +192,28 @@ function reloadApp() {
currWin.webContents.reload();
}
// Remove auto_update_enabled from configStore on app close or quit
function cleanupAutoUpdateFlag() {
if (configStore.has('auto_update_enabled')) {
configStore.delete('auto_update_enabled');
}
}
// This function will force quit and install update and restart the app
function forceQuitAndInstallUpdate() {
// Disable beforeunload handlers
const preventUnload = (event) => {
event.preventDefault();
pgAdminMainScreen.webContents.off('will-prevent-unload', preventUnload);
};
pgAdminMainScreen.webContents.on('will-prevent-unload', preventUnload);
// Set flag to show notification after restart
configStore.set('update_installed', true);
cleanupAutoUpdateFlag();
autoUpdater.quitAndInstall();
}
// This functions is used to start the pgAdmin4 server by spawning a
// separate process.
function startDesktopMode() {
@@ -162,7 +223,6 @@ function startDesktopMode() {
return;
let pingIntervalID;
let UUID = crypto.randomUUID();
// Set the environment variables so that pgAdmin 4 server
// starts listening on the appropriate port.
process.env.PGADMIN_INT_PORT = serverPort;
@@ -170,7 +230,7 @@ function startDesktopMode() {
process.env.PGADMIN_SERVER_MODE = 'OFF';
// Start Page URL
const baseUrl = `http://127.0.0.1:${serverPort}`;
baseUrl = `http://127.0.0.1:${serverPort}`;
startPageUrl = `${baseUrl}/?key=${UUID}`;
serverCheckUrl = `${baseUrl}/misc/ping?key=${UUID}`;
@@ -307,36 +367,10 @@ function launchPgAdminWindow() {
splashWindow.close();
pgAdminMainScreen.webContents.session.clearCache();
setupMenu(pgAdminMainScreen, {
'view_logs': ()=>{
if(viewLogWindow === null || viewLogWindow?.isDestroyed()) {
viewLogWindow = new BrowserWindow({
show: false,
width: 800,
height: 460,
position: 'center',
resizable: false,
parent: pgAdminMainScreen,
icon: '../../assets/pgAdmin4.png',
webPreferences: {
preload: path.join(__dirname, 'other_preload.js'),
},
});
viewLogWindow.loadFile('./src/html/view_log.html');
viewLogWindow.once('ready-to-show', ()=>{
viewLogWindow.show();
});
} else {
viewLogWindow.hide();
viewLogWindow.show();
}
},
'configure': openConfigure,
'reloadApp': reloadApp,
});
setupDownloader();
setupMenu(pgAdminMainScreen, configStore, menuCallbacks);
setupDownloader()
pgAdminMainScreen.loadURL(startPageUrl);
const bounds = configStore.get('bounds');
@@ -346,6 +380,15 @@ function launchPgAdminWindow() {
pgAdminMainScreen.show();
setupAutoUpdater({
pgAdminMainScreen,
configStore,
menuCallbacks,
baseUrl,
UUID,
forceQuitAndInstallUpdate,
});
pgAdminMainScreen.webContents.setWindowOpenHandler(({url})=>{
let openDocsInBrowser = configStore.get('openDocsInBrowser', true);
let isDocURL = false;
@@ -377,18 +420,50 @@ function launchPgAdminWindow() {
});
pgAdminMainScreen.on('closed', ()=>{
cleanupAutoUpdateFlag();
misc.cleanupAndQuitApp();
});
pgAdminMainScreen.on('close', () => {
configStore.set('bounds', pgAdminMainScreen.getBounds());
updateConfigAndMenus('error-close', configStore, pgAdminMainScreen, menuCallbacks);
pgAdminMainScreen.removeAllListeners('close');
pgAdminMainScreen.close();
});
// Notify if update was installed (fix: always check after main window is ready)
notifyUpdateInstalled();
}
let splashWindow;
// Helper to notify update installed after restart
function notifyUpdateInstalled() {
if (configStore.get('update_installed')) {
try {
// Notify renderer
if (pgAdminMainScreen) {
misc.writeServerLog('[Auto-Updater]: Update installed successfully...');
setTimeout(() => {
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', {update_installed: true});
}, 10000);
} else {
// If main screen not ready, wait and send after it's created
app.once('browser-window-created', (event, window) => {
misc.writeServerLog('[Auto-Updater]: Update installed successfully...');
setTimeout(() => {
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', {update_installed: true});
}, 10000);
});
}
// Reset the flag
configStore.set('update_installed', false);
} catch (err) {
misc.writeServerLog(`[Auto-Updater]: ${err}`);
}
}
}
// setup preload events.
ipcMain.handle('showOpenDialog', (e, options) => dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender), options));
ipcMain.handle('showSaveDialog', (e, options) => dialog.showSaveDialog(BrowserWindow.fromWebContents(e.sender), options));
@@ -406,7 +481,7 @@ ipcMain.on('restartApp', ()=>{
app.relaunch();
app.exit(0);
});
ipcMain.on('log', (_e, text) => ()=>{
ipcMain.on('log', (_e, text) => {
misc.writeServerLog(text);
});
ipcMain.on('focus', (e) => {
@@ -428,6 +503,7 @@ ipcMain.handle('checkPortAvailable', async (_e, fixedPort)=>{
});
ipcMain.handle('openConfigure', openConfigure);
app.whenReady().then(() => {
splashWindow = new BrowserWindow({
transparent: true,
+6
View File
@@ -32,4 +32,10 @@ contextBridge.exposeInMainWorld('electronUI', {
downloadStreamSaveEnd: (...args) => ipcRenderer.send('download-stream-save-end', ...args),
downloadBase64UrlData: (...args) => ipcRenderer.invoke('download-base64-url-data', ...args),
downloadTextData: (...args) => ipcRenderer.invoke('download-text-data', ...args),
//Auto-updater related functions
sendDataForAppUpdate: (data) => ipcRenderer.send('sendDataForAppUpdate', data),
notifyAppAutoUpdate: (callback) => {
ipcRenderer.removeAllListeners('notifyAppAutoUpdate'); // Clean up previous listeners
ipcRenderer.on('notifyAppAutoUpdate', (_, data) => callback(data));
},
});
+527 -536
View File
File diff suppressed because it is too large. Load diff
+16 -8
View File
@@ -173,7 +173,15 @@ def format_email_html(data, skip_issues_set=None):
num_bugs_housekeeping = len(filtered_bugs)
# Build HTML output
output = f"<h2><strong>pgAdmin 4 v{version} Released</strong></h2>\n"
output = """<style>
* {
font-size: 14px;
font-family: sans-serif;
}
li {
margin-bottom: 4px;
}
</style>"""
output += (
f"<p>The pgAdmin Development Team is pleased to announce "
f"pgAdmin 4 version {version}.</p>\n"
@@ -186,8 +194,9 @@ def format_email_html(data, skip_issues_set=None):
f"{'s' if num_bugs_housekeeping != 1 else ''}. "
)
output += (
f'For more details please see the '
f'<a href="{release_url}">Release Notes</a>.</p>\n'
f'For more details please see the release notes at:'
f'<p>&nbsp;&nbsp;&nbsp;&nbsp;'
f'<a href="{release_url}">{release_url}</a></p>\n'
)
output += (
'<p>pgAdmin is the leading Open Source graphical management '
@@ -199,7 +208,7 @@ def format_email_html(data, skip_issues_set=None):
# Add features section only if items remain after filtering
if filtered_features:
output += "<p><strong>Features</strong></p>\n<ul>\n"
output += "<p><strong>Features:</strong></p>\n<ul>\n"
for item in filtered_features:
desc = html.escape(item.get('description', 'N/A').strip())
output += f" <li>{desc}.</li>\n"
@@ -209,7 +218,7 @@ def format_email_html(data, skip_issues_set=None):
if filtered_bugs:
# Note: Heading was "Bugs/Housekeeping" in pasted code,
# changed to "Bug fixes" to match GDoc style mentioned earlier
output += "<p><strong>Bug fixes</strong></p>\n<ul>\n"
output += "<p><strong>Bug fixes:</strong></p>\n<ul>\n"
for item in filtered_bugs:
desc = html.escape(item.get('description', 'N/A').strip())
output += f" <li>{desc}.</li>\n"
@@ -222,9 +231,8 @@ def format_email_html(data, skip_issues_set=None):
output += (
"Docker Container, RPM, DEB Package, and source code tarball from:<br>"
)
output += f'<a href="{download_url}">{download_url}</a></p>\n'
# Note: Adjusted sign-off slightly from pasted code to match GDoc examples
output += "<p>--<br>The pgAdmin Team</p>\n"
output += (f'&nbsp;&nbsp;&nbsp;&nbsp;'
f'<a href="{download_url}">{download_url}</a></p>\n')
return output
+11
View File
@@ -129,6 +129,17 @@ WEB_SERVER = 'Python'
# See https://tools.ietf.org/html/rfc7034 for more info.
X_FRAME_OPTIONS = "SAMEORIGIN"
# The Cross-Origin-Opener-Policy allows a website to control whether
# a new top-level document, opened using Window.open() or by navigating
# to a new page, is opened in the same browsing context group (BCG)
# or in a new browsing context group.
# Set to 'unsafe-none', 'same-origin-allow-popups', 'same-origin',
# or 'noopener-allow-popups'
CROSS_ORIGIN_OPENER_POLICY = "same-origin"
# The Content-Security-Policy header allows you to restrict how resources
# such as JavaScript, CSS, or pretty much anything that the browser loads.
# see https://content-security-policy.com/#source_list for more info
+9 -1
View File
@@ -1,5 +1,13 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""empty message
"""
Revision ID: e24b1c4def17
Revises: c62bcc14c3d6
+160
View File
@@ -0,0 +1,160 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Migrate the user saved preferences from the 'sqleditor' module to
the 'editor' module.
Revision ID: e6ed5dac37c2
Revises: e24b1c4def17
Create Date: 2025-07-15 12:27:48.780562
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.orm.session import Session
from pgadmin.model import Preferences, ModulePreference, PreferenceCategory
# revision identifiers, used by Alembic.
revision = 'e6ed5dac37c2'
down_revision = 'e24b1c4def17'
branch_labels = None
depends_on = None
def upgrade():
session = Session(bind=op.get_bind())
old_pref_categories = ['keyboard_shortcuts', 'editor', 'Editor']
new_pref_categories = ['keyboard_shortcuts', 'sql_formatting', 'options']
prefs = ['find', 'replace', 'goto_line_col',
'comment', 'format_sql', 'plain_editor_mode',
'code_folding', 'wrap_code', 'insert_pair_brackets',
'highlight_selection_matches', 'brace_matching', 'sql_font_size',
'sql_font_ligatures', 'sql_font_family', 'indent_new_line',
'keyword_case', 'identifier_case', 'function_case',
'data_type_case', 'spaces_around_operators', 'tab_size',
'use_spaces', 'expression_width', 'logical_operator_new_line',
'lines_between_queries', 'new_line_before_semicolon']
preference_map = {
'keyboard_shortcuts': [
'find', 'replace', 'goto_line_col', 'comment', 'format_sql'
],
'options': [
'plain_editor_mode', 'code_folding', 'cursor_blink_rate',
'wrap_code', 'insert_pair_brackets', 'highlight_selection_matches',
'brace_matching', 'sql_font_size', 'sql_font_ligatures',
'sql_font_family', 'indent_new_line'
],
'sql_formatting': [
'keyword_case', 'identifier_case', 'function_case',
'data_type_case', 'spaces_around_operators', 'tab_size',
'use_spaces', 'expression_width', 'logical_operator_new_line',
'lines_between_queries', 'new_line_before_semicolon'
]
}
category_ids = []
new_ids = []
pref_map = {}
# get metadata from current connection
meta = sa.MetaData()
# define table representation
meta.reflect(op.get_bind(), only=('user_preferences', 'module_preference',
'preference_category', 'preferences'))
module_pref_table = sa.Table('module_preference', meta)
module_id = session.query(ModulePreference).filter_by(
name='editor').first()
# Insert the 'editor' module in module_preference table
if not module_id:
op.execute(
module_pref_table.insert().values(name='editor')
)
module_id = session.query(ModulePreference).filter_by(
name='editor').first().id
# Insert the new preference categories in preference_category table
pref_category_table = sa.Table('preference_category', meta)
op.bulk_insert(pref_category_table, [
{'mid': module_id, 'name': 'keyboard_shortcuts'},
{'mid': module_id, 'name': 'sql_formatting'},
{'mid': module_id, 'name': 'options'},
])
# Insert the new preferences in preferences table
prefs_table = sa.Table('preferences', meta)
for category in new_pref_categories:
category_id = session.query(PreferenceCategory
).filter_by(name=category,
mid=module_id).first().id
op.bulk_insert(
prefs_table,
[
{'cid':category_id, 'name': pref_name}
for pref_name in preference_map[category]
]
)
# Migrate the preferences from 'sqleditor' module to 'editor'
category_data = session.query(ModulePreference, PreferenceCategory,).join(
PreferenceCategory).filter(
ModulePreference.name == 'sqleditor',
PreferenceCategory.name.in_(old_pref_categories)).all()
for module_data, pref_cat in category_data:
category_ids.append(pref_cat.id)
new_data = session.query(ModulePreference, PreferenceCategory).join(
PreferenceCategory).filter(
ModulePreference.name == 'editor', PreferenceCategory.name.in_(
new_pref_categories)).all()
for module_data, pref_cat in new_data:
new_ids.append(pref_cat.id)
prefs_data = session.query(Preferences).filter(Preferences.cid.in_(
category_ids), Preferences.name.in_(prefs)).all()
new_prefs_data = session.query(Preferences).filter(Preferences.cid.in_(
new_ids), Preferences.name.in_(prefs)).all()
for pref in prefs_data:
for new_pref in new_prefs_data:
if pref.name == new_pref.name:
pref_map[pref.id] = new_pref.id
user_pref_table = sa.Table('user_preferences', meta)
# Update the user preferences with new preference ids
for key, val in pref_map.items():
op.execute(
user_pref_table.update().where(
user_pref_table.c.pid == key).values(pid=val)
)
# Delete the old preferences and categories
session.query(Preferences).filter(Preferences.cid.in_(category_ids)
).delete(synchronize_session=False)
session.query(PreferenceCategory).filter(
PreferenceCategory.name.in_(['editor', 'Editor'])).delete(
synchronize_session=False)
session.commit()
def downgrade():
# pgAdmin only upgrades, downgrade not implemented.
pass
+24 -23
View File
@@ -7,12 +7,12 @@
],
"license": "PostgreSQL",
"devDependencies": {
"@babel/core": "^7.28.0",
"@babel/core": "^7.28.3",
"@babel/eslint-parser": "^7.26.5",
"@babel/eslint-plugin": "^7.26.10",
"@babel/plugin-proposal-object-rest-spread": "^7.10.1",
"@babel/plugin-syntax-jsx": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.27.4",
"@babel/plugin-transform-runtime": "^7.28.3",
"@babel/preset-env": "^7.28.0",
"@babel/preset-typescript": "^7.24.7",
"@emotion/memoize": "^0.9.0",
@@ -20,8 +20,8 @@
"@emotion/styled": "^11.11.0",
"@emotion/utils": "^1.0.0",
"@svgr/webpack": "^8.1.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "^6.7.0",
"@testing-library/react": "16.3.0",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^30.0.0",
@@ -30,60 +30,62 @@
"babel-loader": "^10.0.0",
"browserify": "^17.0.0",
"buffer": "^6.0.3",
"copy-webpack-plugin": "^13.0.0",
"cross-env": "^7.0.3",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "^10.0.0",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"eslint": "^9.30.1",
"eslint": "^9.34.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-unused-imports": "^4.2.0",
"exports-loader": "^5.0.0",
"globals": "^16.3.0",
"html-react-parser": "^5.2.0",
"image-minimizer-webpack-plugin": "^4.1.3",
"image-minimizer-webpack-plugin": "^4.1.4",
"imports-loader": "^5.0.0",
"jest": "^30.0.2",
"jest-environment-jsdom": "^29.6.4",
"jest-environment-jsdom": "^30.0.4",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "^2.9.2",
"postcss-loader": "^8.1.1",
"process": "^0.11.10",
"prop-types": "^15.7.2",
"resize-observer-polyfill": "^1.5.1",
"sharp": "^0.34.3",
"shim-loader": "^1.0.1",
"style-loader": "^4.0.0",
"stylis": "^4.3.4",
"svgo": "^4.0.0",
"svgo-loader": "^4.0.0",
"terser-webpack-plugin": "^5.3.12",
"typescript": "^5.4.5",
"typescript": "^5.9.2",
"typescript-eslint": "^8.35.0",
"url-loader": "^4.1.1",
"webfonts-loader": "^8.0.1",
"webpack": "^5.99.9",
"webpack": "^5.101.0",
"webpack-bundle-analyzer": "^4.8.0",
"webpack-cli": "^6.0.1",
"yarn-audit-html": "7.3.2"
},
"optionalDependencies": {
"sharp": "^0.34.3"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-react": "^7.12.13",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-sql": "^6.9.0",
"@codemirror/lang-sql": "^6.9.1",
"@date-io/core": "^3.0.0",
"@date-io/date-fns": "3.x",
"@emotion/sheet": "^1.0.1",
"@fortawesome/fontawesome-free": "latest",
"@mui/icons-material": "^7.1.1",
"@mui/material": "^7.1.0",
"@mui/x-date-pickers": "^8.5.2",
"@mui/icons-material": "^7.3.1",
"@mui/material": "^7.3.1",
"@mui/x-date-pickers": "^8.10.0",
"@nozbe/microfuzz": "^1.0.0",
"@projectstorm/react-diagrams": "^7.0.4",
"@simonwep/pickr": "^1.5.1",
"@szhsin/react-menu": "^4.4.1",
"@tanstack/react-query": "^5.81.5",
"@tanstack/react-query": "^5.84.2",
"@tanstack/react-table": "^8.16.0",
"@tanstack/react-virtual": "^3.13.6",
"@types/classnames": "^2.3.4",
@@ -96,18 +98,17 @@
"ajv": "^8.8.2",
"anti-trojan-source": "^1.4.0",
"aspen-decorations": "^1.0.2",
"axios": "^1.10.0",
"axios": "^1.11.0",
"babelify": "~10.0.0",
"bignumber.js": "^9.2.0",
"brace": "^0.11.1",
"browserfs": "^1.4.3",
"chart.js": "^4.4.7",
"chartjs-plugin-zoom": "^2.0.1",
"codemirror": "^6.0.1",
"convert-units": "^2.3.4",
"date-fns": "^4.1.0",
"diff-arrays-of-objects": "^1.1.8",
"hotkeys-js": "^3.13.14",
"hotkeys-js": "^3.13.15",
"html-to-image": "1.11.11",
"immutability-helper": "^3.0.0",
"insert-if": "^1.1.0",
@@ -151,7 +152,7 @@
"valid-filename": "^4.0.0",
"vanilla-jsoneditor": "^3.3.1",
"wkx": "^0.5.0",
"zustand": "^5.0.5"
"zustand": "^5.0.7"
},
"resolutions": {
"rc-resize-observer": "1.4.0"
@@ -171,7 +172,7 @@
"test:js-coverage": "yarn run test:js-once --collect-coverage",
"test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests",
"test": "yarn run test:js-once && yarn run bundle && python regression/runtests.py",
"pep8": "pycodestyle --config=../.pycodestyle ../docs && pycodestyle --config=../.pycodestyle ../pkg && pycodestyle --config=../.pycodestyle ../tools && pycodestyle --config=../.pycodestyle ../web",
"pep8": "pycodestyle --config=../.pycodestyle ../docs ../pkg ../tools ../web",
"auditjs-html": "yarn audit --json | yarn run yarn-audit-html --output ../auditjs.html",
"auditjs": "yarn audit --groups dependencies",
"auditpy": "safety check --full-report -i 51668 -i 52495",
+3
View File
@@ -52,6 +52,8 @@ from pgadmin.utils.csrf import pgCSRFProtect
from pgadmin.utils.preferences import Preferences
from pgadmin.browser.register_browser_preferences import \
register_browser_preferences
from pgadmin.browser.register_editor_preferences import \
register_editor_preferences
from pgadmin.utils.master_password import validate_master_password, \
set_masterpass_check_text, cleanup_master_password, get_crypt_key, \
set_crypt_key, process_masterpass_disabled, \
@@ -87,6 +89,7 @@ class BrowserModule(PgAdminModule):
def register_preferences(self):
register_browser_preferences(self)
register_editor_preferences(self)
def get_exposed_url_endpoints(self):
"""
@@ -0,0 +1,369 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from flask_babel import gettext
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.constants import PREF_LABEL_OPTIONS,\
PREF_LABEL_KEYBOARD_SHORTCUTS, PREF_LABEL_SQL_FORMATTING
from pgadmin.utils import SHORTCUT_FIELDS as shortcut_fields
UPPER_CASE_STR = gettext('Upper case')
LOWER_CASE_STR = gettext('Lower case')
PRESERVE_STR = gettext('Preserve')
def register_editor_preferences(self):
"""
Registers the editor preferences
"""
self.editor_preference = Preferences(
'editor', gettext('Editor')
)
self.editor_preference.register(
'keyboard_shortcuts',
'find',
gettext('Find'),
'keyboardshortcut',
{
'alt': False,
'shift': False,
'control': True,
'ctrl_is_meta': True,
'key': {
'key_code': 70,
'char': 'f'
}
},
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
fields=shortcut_fields
)
self.editor_preference.register(
'keyboard_shortcuts',
'replace',
gettext('Replace'),
'keyboardshortcut',
{
'alt': True,
'shift': False,
'control': True,
'ctrl_is_meta': True,
'key': {
'key_code': 70,
'char': 'f'
}
},
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
fields=shortcut_fields
)
self.editor_preference.register(
'keyboard_shortcuts',
'goto_line_col',
gettext('Go to line/column'),
'keyboardshortcut',
{
'alt': False,
'shift': False,
'control': True,
'ctrl_is_meta': True,
'key': {
'key_code': 76,
'char': 'l'
}
},
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
fields=shortcut_fields
)
self.editor_preference.register(
'keyboard_shortcuts',
'comment',
gettext('Toggle comment'),
'keyboardshortcut',
{
'alt': False,
'shift': False,
'control': True,
'ctrl_is_meta': True,
'key': {
'key_code': 191,
'char': '/'
}
},
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
fields=shortcut_fields
)
self.editor_preference.register(
'keyboard_shortcuts',
'format_sql',
gettext('Format SQL'),
'keyboardshortcut',
{
'alt': False,
'shift': False,
'control': True,
'ctrl_is_meta': True,
'key': {
'key_code': 75,
'char': 'k'
}
},
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
fields=shortcut_fields
)
self.editor_preference.register(
'options', 'plain_editor_mode',
gettext("Plain text mode?"), 'boolean', False,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'When set to True, keywords won\'t be highlighted and code '
'folding will be disabled. Plain text mode will improve editor '
'performance with large files.'
)
)
self.editor_preference.register(
'options', 'code_folding',
gettext("Code folding?"), 'boolean', True,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'Enable or disable code folding. In plain text mode, this will '
'have no effect as code folding is always disabled in that mode. '
'Disabling will improve editor performance with large files.'
)
)
self.editor_preference.register(
'options', 'cursor_blink_rate',
gettext("Cursor blink rate"), 'options', 'medium',
category_label=PREF_LABEL_OPTIONS,
options=[{'label': gettext('None'), 'value': 'none'},
{'label': gettext('Slow'), 'value': 'slow'},
{'label': gettext('Medium'), 'value': 'medium'},
{'label': gettext('Fast'), 'value': 'fast'}],
control_props={
'allowClear': False,
'creatable': False,
},
help_str=gettext(
'Adjust the speed at which the text cursor blinks within '
'the editors.'
)
)
self.editor_preference.register(
'options', 'wrap_code',
gettext("Line wrapping?"), 'boolean', False,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'Specifies whether or not to wrap SQL code in the editor.'
)
)
self.editor_preference.register(
'options', 'insert_pair_brackets',
gettext("Insert bracket pairs?"), 'boolean', True,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'Specifies whether or not to insert paired brackets in the '
'editor.'
)
)
self.editor_preference.register(
'options', 'highlight_selection_matches',
gettext("Highlight selection matches?"), 'boolean', True,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'Specifies whether or not to highlight matched selected text '
'in the editor.'
)
)
self.editor_preference.register(
'options', 'brace_matching',
gettext("Brace matching?"), 'boolean', True,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'Specifies whether or not to highlight matched braces '
'in the editor.'
)
)
self.editor_preference.register(
'options', 'sql_font_size',
gettext("Font size"), 'numeric', '1',
min_val=0.1,
max_val=10,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'The font size to use for the SQL text boxes and editors. '
'The value specified is in "em" units, in which 1 is the '
'default relative font size. For example, to increase the '
'font size by 20 percent use a value of 1.2, or to reduce '
'by 20 percent, use a value of 0.8. Minimum 0.1, maximum 10.'
)
)
self.editor_preference.register(
'options', 'sql_font_ligatures',
gettext("Font ligatures?"), 'boolean',
False, category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'If set to true, ligatures will be enabled in SQL text boxes '
'and editors provided the configured font family supports them.'
)
)
self.editor_preference.register(
'options', 'sql_font_family',
gettext("Font family"), 'text', 'Source Code Pro',
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'Specify the font family to be used for all SQL editors. '
'The specified font should already be installed on your system. '
'If the font is not found, the editor will fall back to the '
'default font, Source Code Pro.'
)
)
self.editor_preference.register(
'options', 'indent_new_line',
gettext("Auto-indent new line?"), 'boolean', True,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'Specifies whether the newly added line using enter key should '
'be auto-indented or not'
)
)
self.editor_preference.register(
'sql_formatting', 'keyword_case',
gettext("Keyword case"), 'radioModern', 'upper',
options=[{'label': UPPER_CASE_STR, 'value': 'upper'},
{'label': LOWER_CASE_STR, 'value': 'lower'},
{'label': PRESERVE_STR, 'value': 'preserve'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Convert keywords to upper, lower, or preserve casing.'
)
)
self.editor_preference.register(
'sql_formatting', 'identifier_case',
gettext("Identifier case"), 'radioModern', 'upper',
options=[{'label': UPPER_CASE_STR, 'value': 'upper'},
{'label': LOWER_CASE_STR, 'value': 'lower'},
{'label': PRESERVE_STR, 'value': 'preserve'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Convert identifiers to upper, lower, or preserve casing.'
)
)
self.editor_preference.register(
'sql_formatting', 'function_case',
gettext("Function case"), 'radioModern', 'upper',
options=[{'label': UPPER_CASE_STR, 'value': 'upper'},
{'label': LOWER_CASE_STR, 'value': 'lower'},
{'label': PRESERVE_STR, 'value': 'preserve'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Convert function names to upper, lower, or preserve casing.'
)
)
self.editor_preference.register(
'sql_formatting', 'data_type_case',
gettext("Data type case"), 'radioModern', 'upper',
options=[{'label': UPPER_CASE_STR, 'value': 'upper'},
{'label': LOWER_CASE_STR, 'value': 'lower'},
{'label': PRESERVE_STR, 'value': 'preserve'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Convert data types to upper, lower, or preserve casing.'
)
)
self.editor_preference.register(
'sql_formatting', 'spaces_around_operators',
gettext("Spaces around operators?"), 'boolean', True,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext('If set to True, spaces are used around all '
'operators.')
)
self.editor_preference.register(
'sql_formatting', 'tab_size',
gettext("Tab size"), 'integer', 4,
min_val=2,
max_val=8,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'The number of spaces per tab. Minimum 2, maximum 8.'
)
)
self.editor_preference.register(
'sql_formatting', 'use_spaces',
gettext("Use spaces?"), 'boolean', False,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Specifies whether or not to insert spaces instead of tabs '
'when the tab key or auto-indent are used.'
)
)
self.editor_preference.register(
'sql_formatting', 'expression_width',
gettext("Expression Width"), 'integer', 50,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'maximum number of characters in parenthesized expressions to be '
'kept on single line.'
)
)
self.editor_preference.register(
'sql_formatting', 'logical_operator_new_line',
gettext("Logical operator new line"), 'radioModern', 'before',
options=[{'label': gettext('Before'), 'value': 'before'},
{'label': gettext('After'), 'value': 'after'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Decides newline placement before or after logical operators '
'(AND, OR, XOR).'
)
)
self.editor_preference.register(
'sql_formatting', 'lines_between_queries',
gettext("Lines between queries"), 'integer', 1,
min_val=0,
max_val=5,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Decides how many empty lines to leave between SQL statements. '
'If zero it puts no new line.'
)
)
self.editor_preference.register(
'sql_formatting', 'new_line_before_semicolon',
gettext("New line before semicolon?"), 'boolean', False,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Whether to place query separator (;) on a separate line.'
)
)
@@ -31,7 +31,7 @@ from config import PG_DEFAULT_DRIVER
from pgadmin.model import db, Server, ServerGroup, User, SharedServer
from pgadmin.utils.driver import get_driver
from pgadmin.utils.master_password import get_crypt_key
from pgadmin.utils.exception import CryptKeyMissing
from pgadmin.utils.exception import CryptKeyMissing, ConnectionLost
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,
@@ -43,6 +43,7 @@ from sqlalchemy.orm.attributes import flag_modified
from pgadmin.utils.preferences import Preferences
from .... import socketio as sio
from pgadmin.utils import get_complete_file_path
from pgadmin.settings.utils import with_object_filters
def has_any(data, keys):
@@ -220,8 +221,25 @@ class ServerModule(sg.ServerGroupPluginModule):
return servers
def has_tag(self, server, object_filters):
try:
# No tags filter, show all
if len(object_filters['tags']) == 0:
return True
# No tags on server, don't show
if server.tags is None or len(server.tags) == 0:
return False
# Check if any of the tag exists
return any([t['text'] in object_filters['tags']
for t in server.tags])
except Exception as _:
return True
@with_object_filters
@pga_login_required
def get_nodes(self, gid):
def get_nodes(self, gid, object_filters):
"""Return a JSON document listing the server groups for the user"""
hide_shared_server = get_preferences()
@@ -241,6 +259,10 @@ class ServerModule(sg.ServerGroupPluginModule):
wal_paused = None
server_type = 'pg'
user_info = None
if not self.has_tag(server, object_filters):
continue
try:
manager = driver.connection_manager(server.id)
conn = manager.connection()
@@ -627,12 +649,18 @@ class ServerNode(PGChildNodeView):
in_recovery = None
wal_paused = None
if connected:
status, result, in_recovery, wal_paused =\
recovery_state(conn, manager.version)
if not status:
try:
status, result, in_recovery, wal_paused =\
recovery_state(conn, manager.version)
if not status:
connected = False
manager.release()
errmsg = "{0} : {1}".format(server.name, result)
except ConnectionLost:
connected = False
manager.release()
errmsg = "{0} : {1}".format(server.name, result)
return make_json_response(
result=self.blueprint.generate_browser_node(
@@ -920,7 +948,7 @@ class ServerNode(PGChildNodeView):
)
@pga_login_required
def list(self, gid):
def list(self, gid, object_filters):
"""
Return list of attributes of all servers.
"""
@@ -3,5 +3,5 @@ CREATE EVENT TRIGGER {{ conn|qtIdent(data.name) }} ON {{data.eventname}}
{% if data.when %}
WHEN TAG IN ({{data.when}})
{% endif %}
EXECUTE PROCEDURE {{data.eventfunname}}();
EXECUTE FUNCTION {{data.eventfunname}}();
{% endif %}
@@ -12,7 +12,7 @@ CREATE EVENT TRIGGER {{ conn|qtIdent(data.name) if data.name else conn|qtIdent(o
{% if data.when or o_data.when %}
WHEN TAG IN ({{ data.when if data.when else o_data.when }})
{% endif %}
EXECUTE PROCEDURE {{ data.eventfunname if data.eventfunname else o_data.eventfunname }}();
EXECUTE FUNCTION {{ data.eventfunname if data.eventfunname else o_data.eventfunname }}();
ALTER EVENT TRIGGER {{ conn|qtIdent(o_data.name) }}
OWNER TO {{ conn|qtIdent(o_data.eventowner) }};
@@ -50,8 +50,8 @@ def create_event_trigger(server, db_name, schema_name, func_name,
set_isolation_level(connection, 0)
pg_cursor = connection.cursor()
pg_cursor.execute('''CREATE EVENT TRIGGER "%s" ON DDL_COMMAND_END
EXECUTE PROCEDURE "%s"."%s"()''' % (trigger_name, schema_name,
func_name))
EXECUTE FUNCTION "%s"."%s"()''' % (trigger_name, schema_name,
func_name))
set_isolation_level(connection, old_isolation_level)
connection.commit()
# Get 'oid' from newly created event trigger
@@ -79,7 +79,8 @@ define('pgadmin.node.collation', [
{
owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
schema: ('schema' in treeNodeInfo)? treeNodeInfo.schema.label : ''
}
},
treeNodeInfo
);
}
});
@@ -12,7 +12,7 @@ import gettext from 'sources/gettext';
import { isEmptyString } from 'sources/validators';
export default class CollationSchema extends BaseUISchema {
constructor(fieldOptions = {},initValues={}) {
constructor(fieldOptions = {}, initValues={}, nodeInfo={}) {
super({
name: undefined,
oid: undefined,
@@ -30,6 +30,7 @@ export default class CollationSchema extends BaseUISchema {
this.schemaList = fieldOptions.schemaList;
this.ownerList = fieldOptions.rolesList;
this.collationsList = fieldOptions.collationsList;
this.nodeInfo = nodeInfo;
}
get idAttribute() {
@@ -86,16 +87,24 @@ export default class CollationSchema extends BaseUISchema {
deps: ['locale', 'lc_collate', 'lc_type'],
},
{
id: 'provider', label: gettext('Locale Provider'),
id: 'provider', label: gettext('Provider'),
editable: false, type: 'select',mode: ['create'], group: gettext('Definition'),
readonly: function (state) { return !obj.isNew(state); },
options: [{
label: gettext('icu'),
value: 'icu',
}, {
label: gettext('libc'),
value: 'libc',
}],
options: function() {
let options = [{
label: gettext('icu'),
value: 'icu',
}, {
label: gettext('libc'),
value: 'libc',
}];
if(obj.getServerVersion() >= 170000) {
options.push({
label: gettext('builtin'), value: 'builtin',
});
}
return Promise.resolve(options);
},
min_version: 110000,
deps: ['copy_collation'],
depChange: (state)=>{
@@ -110,7 +119,7 @@ export default class CollationSchema extends BaseUISchema {
}
},
{
id: 'provider', label: gettext('Locale Provider'),
id: 'provider', label: gettext('Provider'),
type: 'text',mode: ['properties', 'edit'], group: gettext('Definition'),
readonly: true,
min_version: 110000,
@@ -1,7 +1,7 @@
SELECT c.oid, c.collname AS name, c.collcollate AS lc_collate, c.collctype AS lc_type,
pg_catalog.pg_get_userbyid(c.collowner) AS owner, c.collisdeterministic AS is_deterministic, c.collversion AS version,
c.collprovider AS provider, des.description, n.nspname AS schema
CASE WHEN c.collprovider = 'i' THEN 'icu' ELSE 'libc' END provider,
des.description, n.nspname AS schema
FROM pg_catalog.pg_collation c
JOIN pg_catalog.pg_namespace n ON n.oid=c.collnamespace
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=c.oid AND des.classoid='pg_collation'::regclass)
@@ -1,6 +1,6 @@
SELECT c.oid, c.collname AS name, COALESCE(c.collcollate, c.colliculocale) AS lc_collate,
COALESCE(c.collctype, c.colliculocale) AS lc_type, c.collisdeterministic AS is_deterministic, c.collversion AS version,
c.collprovider AS provider,
CASE WHEN c.collprovider = 'i' THEN 'icu' ELSE 'libc' END provider,
pg_catalog.pg_get_userbyid(c.collowner) AS owner, description, n.nspname AS schema
FROM pg_catalog.pg_collation c
JOIN pg_catalog.pg_namespace n ON n.oid=c.collnamespace
@@ -1,6 +1,6 @@
SELECT c.oid, c.collname AS name, COALESCE(c.collcollate, c.colliculocale) AS lc_collate,
COALESCE(c.collctype, c.colliculocale) AS lc_type, c.collisdeterministic AS is_deterministic, c.collversion AS version,
c.collprovider AS provider, c.collicurules AS rules,
CASE WHEN c.collprovider = 'i' THEN 'icu' ELSE 'libc' END provider, c.collicurules AS rules,
pg_catalog.pg_get_userbyid(c.collowner) AS owner, description, n.nspname AS schema
FROM pg_catalog.pg_collation c
JOIN pg_catalog.pg_namespace n ON n.oid=c.collnamespace
@@ -1,6 +1,7 @@
SELECT c.oid, c.collname AS name, COALESCE(c.collcollate, c.colllocale) AS lc_collate,
COALESCE(c.collctype, c.colllocale) AS lc_type, c.collisdeterministic AS is_deterministic, c.collversion AS version,
c.collprovider AS provider, c.collicurules AS rules,
CASE WHEN c.collprovider = 'i' THEN 'icu' WHEN c.collprovider = 'b' THEN 'builtin'
ELSE 'libc' END provider, c.collicurules AS rules,
pg_catalog.pg_get_userbyid(c.collowner) AS owner, description, n.nspname AS schema
FROM pg_catalog.pg_collation c
JOIN pg_catalog.pg_namespace n ON n.oid=c.collnamespace
@@ -3,7 +3,7 @@
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#a";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'c', DETERMINISTIC = true);
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
OWNER TO <OWNER>;
@@ -3,7 +3,7 @@
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#"
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'c', DETERMINISTIC = true);
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
OWNER TO <OWNER>;
@@ -3,7 +3,7 @@
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
(LC_COLLATE = 'locale', LC_CTYPE = 'locale', PROVIDER = 'i', DETERMINISTIC = true, VERSION = '1');
(LC_COLLATE = 'locale', LC_CTYPE = 'locale', PROVIDER = 'icu', DETERMINISTIC = true, VERSION = '1');
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
OWNER TO <OWNER>;
@@ -0,0 +1,12 @@
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#b;
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true, VERSION = '1');
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
OWNER TO <OWNER>;
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
IS 'libc';
@@ -33,12 +33,12 @@
"name": "Drop Collation",
"endpoint": "NODE-collation.delete_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#a"
}
},
{
"type": "create",
"name": "Create Collation with extra parameters",
"name": "Create Collation with icu provider",
"endpoint": "NODE-collation.obj",
"sql_endpoint": "NODE-collation.sql_id",
"data": {
@@ -53,6 +53,40 @@
},
"store_object_id": true,
"expected_sql_file": "create_collation_with_extra_params.sql"
},
{
"type": "delete",
"name": "Drop Collation",
"endpoint": "NODE-collation.delete_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
}
},
{
"type": "create",
"name": "Create Collation with libc provider",
"endpoint": "NODE-collation.obj",
"sql_endpoint": "NODE-collation.sql_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b",
"schema": "testschema",
"owner": "postgres",
"description": "libc",
"locale": "C",
"provider": "libc",
"is_deterministic": true,
"version": "1"
},
"store_object_id": true,
"expected_sql_file": "create_collation_with_libc.sql"
},
{
"type": "delete",
"name": "Drop Collation",
"endpoint": "NODE-collation.delete_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
}
}
]
}
@@ -0,0 +1,12 @@
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#a;
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#a";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
OWNER TO <OWNER>;
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
IS 'Description for alter';
@@ -0,0 +1,12 @@
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#;
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#"
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
OWNER TO <OWNER>;
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
IS 'Description';
@@ -0,0 +1,12 @@
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#b;
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'builtin', DETERMINISTIC = true, VERSION = '1');
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
OWNER TO <OWNER>;
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
IS 'builtin';
@@ -0,0 +1,12 @@
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#b;
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
(LC_COLLATE = 'locale', LC_CTYPE = 'locale', PROVIDER = 'icu', DETERMINISTIC = true, VERSION = '1');
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
OWNER TO <OWNER>;
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
IS 'Description for extra params';
@@ -0,0 +1,12 @@
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#b;
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
OWNER TO <OWNER>;
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
IS 'libc';
@@ -0,0 +1,5 @@
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
IS 'Description for alter';
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
RENAME TO "Cl1_$%{}[]()&*^!@""'`\/#a";
@@ -0,0 +1,117 @@
{
"scenarios": [
{
"type": "create",
"name": "Create Collation",
"endpoint": "NODE-collation.obj",
"sql_endpoint": "NODE-collation.sql_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#",
"schema": "testschema",
"copy_collation": "pg_catalog.\"C\"",
"description": "Description"
},
"store_object_id": true,
"expected_sql_file": "create_collation.sql"
},
{
"type": "alter",
"name": "Alter Collation",
"endpoint": "NODE-collation.obj_id",
"sql_endpoint": "NODE-collation.sql_id",
"msql_endpoint": "NODE-collation.msql_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#a",
"schema": "testschema",
"description": "Description for alter"
},
"expected_sql_file": "alter_collation.sql",
"expected_msql_file": "msql_collation.sql"
},
{
"type": "delete",
"name": "Drop Collation",
"endpoint": "NODE-collation.delete_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#a"
}
},
{
"type": "create",
"name": "Create Collation with icu provider",
"endpoint": "NODE-collation.obj",
"sql_endpoint": "NODE-collation.sql_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b",
"schema": "testschema",
"owner": "postgres",
"description": "Description for extra params",
"locale": "locale",
"provider": "icu",
"is_deterministic": true,
"version": "1"
},
"store_object_id": true,
"expected_sql_file": "create_collation_with_extra_params.sql"
},
{
"type": "delete",
"name": "Drop Collation",
"endpoint": "NODE-collation.delete_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
}
},
{
"type": "create",
"name": "Create Collation with libc provider",
"endpoint": "NODE-collation.obj",
"sql_endpoint": "NODE-collation.sql_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b",
"schema": "testschema",
"owner": "postgres",
"description": "libc",
"locale": "C",
"provider": "libc",
"is_deterministic": true
},
"store_object_id": true,
"expected_sql_file": "create_collation_with_libc.sql"
},
{
"type": "delete",
"name": "Drop Collation",
"endpoint": "NODE-collation.delete_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
}
},
{
"type": "create",
"name": "Create Collation with builtin provider",
"endpoint": "NODE-collation.obj",
"sql_endpoint": "NODE-collation.sql_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b",
"schema": "testschema",
"owner": "postgres",
"description": "builtin",
"locale": "C",
"provider": "builtin",
"is_deterministic": true,
"version": "1"
},
"store_object_id": true,
"expected_sql_file": "create_collation_with_builtin.sql"
},
{
"type": "delete",
"name": "Drop Collation",
"endpoint": "NODE-collation.delete_id",
"data": {
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
}
}
]
}
@@ -37,6 +37,8 @@ from pgadmin.browser.server_groups.servers.databases.schemas.tables.\
columns import utils as column_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tables.\
triggers import utils as trigger_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tables.\
utils import BaseTableView
class ForeignTableModule(SchemaChildModule):
@@ -102,7 +104,7 @@ class ForeignTableModule(SchemaChildModule):
blueprint = ForeignTableModule(__name__)
class ForeignTableView(PGChildNodeView, DataTypeReader,
class ForeignTableView(BaseTableView, DataTypeReader,
SchemaDiffObjectCompare):
"""
class ForeignTableView(PGChildNodeView)
@@ -187,6 +189,9 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
* compare(**kwargs):
- This function will compare the foreign table nodes from two different
schemas.
* truncate(gid, sid, scid, tid):
- This function will truncate foreign table object
"""
node_type = blueprint.node_type
@@ -211,6 +216,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'truncate': [{'put': 'truncate'}],
'children': [{'get': 'children'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
@@ -409,6 +415,9 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
self.template_path = \
self.BASE_TEMPLATE_PATH.format(self.manager.version)
self.table_template_path = compile_template_path(
'tables/sql', self.manager.version)
self.foreign_table_column_template_path = compile_template_path(
'foreign_table_columns/sql', self.manager.version)
@@ -884,6 +893,39 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
except Exception as e:
return internal_server_error(errormsg=str(e))
@check_precondition
def truncate(self, gid, sid, did, scid, foid):
"""
This function will truncate the foreign table.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
scid: Schema ID
foid: Foreign Table ID
"""
try:
SQL = render_template(
"/".join([self.template_path, self._PROPERTIES_SQL]),
did=did, scid=scid, foid=foid,
datlastsysoid=self._DATABASE_LAST_SYSTEM_OID
)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(gettext(self.not_found_error_msg()))
return super().truncate(
gid, sid, did, scid, foid, res
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@check_precondition
def sql(self, gid, sid, did, scid, foid=None, **kwargs):
"""
@@ -12,6 +12,7 @@ import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.u
import ForeignTableSchema from './foreign_table.ui';
import _ from 'lodash';
import Notify from '../../../../../../../../static/js/helpers/Notifier';
import getApiInstance from '../../../../../../../../static/js/api_instance';
/* Create and Register Foreign Table Collection and Node. */
define('pgadmin.node.foreign_table', ['pgadmin.tables.js/enable_disable_triggers',
@@ -72,6 +73,21 @@ define('pgadmin.node.foreign_table', ['pgadmin.tables.js/enable_disable_triggers
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 4, label: gettext('Foreign Table...'),
data: {action: 'create', check: false}, enable: 'canCreate',
},{
name: 'truncate_foreign_table', node: 'foreign_table', module: this,
applies: ['object', 'context'], callback: 'truncate_foreign_table',
category: gettext('Truncate'), priority: 3, label: gettext('Truncate'),
enable : 'canCreate',
},{
name: 'truncate_foreign_table_cascade', node: 'foreign_table', module: this,
applies: ['object', 'context'], callback: 'truncate_foreign_table_cascade',
category: gettext('Truncate'), priority: 3, label: gettext('Truncate Cascade'),
enable : 'canCreate',
},{
name: 'truncate_foreign_table_identity', node: 'foreign_table', module: this,
applies: ['object', 'context'], callback: 'truncate_foreign_table_identity',
category: gettext('Truncate'), priority: 3, label: gettext('Truncate Restart Identity'),
enable : 'canCreate',
},{
// To enable/disable all triggers for the table
name: 'enable_all_triggers', node: 'foreign_table', module: this,
@@ -112,6 +128,56 @@ define('pgadmin.node.foreign_table', ['pgadmin.tables.js/enable_disable_triggers
args
);
},
/* Truncate foreign table */
truncate_foreign_table: function(args) {
let params = {'cascade': false };
this.callbacks.truncate.apply(this, [args, params]);
},
/* Truncate foreign table with cascade */
truncate_foreign_table_cascade: function(args) {
let params = {'cascade': true };
this.callbacks.truncate.apply(this, [args, params]);
},
/* Truncate foreign table with identity */
truncate_foreign_table_identity: function(args) {
let params = {'identity': true };
this.callbacks.truncate.apply(this, [args, params]);
},
truncate: function(args, params) {
let input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i ? t.itemData(i) : undefined;
if (!d)
return false;
pgBrowser.notifier.confirm(
gettext('Truncate Foreign Table'),
gettext('Are you sure you want to truncate foreign table <b>%s</b>?', d.label),
function () {
let data = d;
getApiInstance().put(obj.generate_url(i, 'truncate' , d, true), params)
.then(({data: res})=>{
if (res.success == 1) {
pgBrowser.notifier.success(res.info);
t.removeIcon(i);
data.icon = data.is_partitioned ? 'icon-partition': 'icon-table';
t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data);
}
if (res.success == 2) {
pgBrowser.notifier.error(res.info);
}
})
.catch((error)=>{
pgBrowser.notifier.pgRespErrorNotify(error);
t.refresh(i);
});
}, function() {/*This is intentional (SonarQube)*/}
);
},
},
// Check to whether table has disable trigger(s)
canCreate_with_trigger_enable: function(itemData) {
@@ -6,6 +6,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS "Idx1_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
INCLUDE(name, id)
WITH (fillfactor=100)
TABLESPACE pg_default;
COMMENT ON INDEX public."Idx1_$%{}[]()&*^!@""'`\/#"
@@ -6,4 +6,5 @@ CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
INCLUDE(name, id)
WITH (fillfactor=100)
TABLESPACE pg_default;
@@ -0,0 +1,14 @@
-- Index: Idx3_$%{}[]()&*^!@"'`\/#
-- DROP INDEX IF EXISTS public."Idx3_$%{}[]()&*^!@""'`\/#";
CREATE UNIQUE INDEX IF NOT EXISTS "Idx3_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id ASC NULLS LAST, lower(name) COLLATE pg_catalog."POSIX" text_pattern_ops ASC NULLS LAST)
INCLUDE(name, id)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
COMMENT ON INDEX public."Idx3_$%{}[]()&*^!@""'`\/#"
IS 'Test Comment';
@@ -0,0 +1,16 @@
-- Index: Idx1_$%{}[]()&*^!@"'`\/#
-- DROP INDEX IF EXISTS public."Idx1_$%{}[]()&*^!@""'`\/#";
CREATE UNIQUE INDEX IF NOT EXISTS "Idx1_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
INCLUDE(name, id)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.test_table_for_indexes
CLUSTER ON "Idx1_$%{}[]()&*^!@""'`\/#";
COMMENT ON INDEX public."Idx1_$%{}[]()&*^!@""'`\/#"
IS 'Test Comment';
@@ -0,0 +1,13 @@
-- Index: Idx1_$%{}[]()&*^!@"'`\/#
-- DROP INDEX IF EXISTS public."Idx1_$%{}[]()&*^!@""'`\/#";
CREATE UNIQUE INDEX IF NOT EXISTS "Idx1_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
INCLUDE(name, id)
WITH (fillfactor=100, deduplicate_items=True)
TABLESPACE pg_default;
COMMENT ON INDEX public."Idx1_$%{}[]()&*^!@""'`\/#"
IS 'Test Comment';
@@ -0,0 +1,10 @@
-- Index: Idx_$%{}[]()&*^!@"'`\/#
-- DROP INDEX IF EXISTS public."Idx_$%{}[]()&*^!@""'`\/#";
CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
INCLUDE(name, id)
WITH (fillfactor=100, deduplicate_items=True)
TABLESPACE pg_default;
@@ -0,0 +1,14 @@
-- Index: Idx3_$%{}[]()&*^!@"'`\/#
-- DROP INDEX IF EXISTS public."Idx3_$%{}[]()&*^!@""'`\/#";
CREATE UNIQUE INDEX IF NOT EXISTS "Idx3_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id ASC NULLS LAST, lower(name) COLLATE pg_catalog."POSIX" text_pattern_ops ASC NULLS LAST)
INCLUDE(name, id)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
COMMENT ON INDEX public."Idx3_$%{}[]()&*^!@""'`\/#"
IS 'Test Comment';
@@ -7,7 +7,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS "Idx1_$%{}[]()&*^!@""'`\/#"
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
INCLUDE(name, id)
NULLS NOT DISTINCT
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.test_table_for_indexes
@@ -7,6 +7,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS "Idx1_$%{}[]()&*^!@""'`\/#"
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
INCLUDE(name, id)
NULLS NOT DISTINCT
WITH (fillfactor=100, deduplicate_items=True)
TABLESPACE pg_default;
COMMENT ON INDEX public."Idx1_$%{}[]()&*^!@""'`\/#"
@@ -7,7 +7,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
(id ASC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops ASC NULLS FIRST)
INCLUDE(name, id)
NULLS NOT DISTINCT
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
@@ -7,7 +7,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
(id ASC NULLS LAST, name COLLATE pg_catalog."POSIX" text_pattern_ops ASC NULLS LAST)
INCLUDE(name, id)
NULLS NOT DISTINCT
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
@@ -7,4 +7,5 @@ CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
INCLUDE(name, id)
NULLS NOT DISTINCT
WITH (fillfactor=100, deduplicate_items=True)
TABLESPACE pg_default;
@@ -7,7 +7,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
(id DESC NULLS LAST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS LAST)
INCLUDE(name, id)
NULLS NOT DISTINCT
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
@@ -5,7 +5,7 @@
CREATE UNIQUE INDEX IF NOT EXISTS "Idx3_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id ASC NULLS LAST, lower(name) COLLATE pg_catalog."POSIX" text_pattern_ops ASC NULLS LAST)
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
@@ -5,7 +5,7 @@
CREATE UNIQUE INDEX IF NOT EXISTS "Idx1_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id DESC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS FIRST)
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.test_table_for_indexes
@@ -5,7 +5,7 @@
CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id ASC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops ASC NULLS FIRST)
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
@@ -1,7 +1,7 @@
CREATE UNIQUE INDEX "Idx_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id ASC NULLS FIRST, name COLLATE pg_catalog."POSIX" text_pattern_ops ASC NULLS FIRST)
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
@@ -5,7 +5,7 @@
CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id ASC NULLS LAST, name COLLATE pg_catalog."POSIX" text_pattern_ops ASC NULLS LAST)
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
@@ -5,7 +5,7 @@
CREATE UNIQUE INDEX IF NOT EXISTS "Idx_$%{}[]()&*^!@""'`\/#"
ON public.test_table_for_indexes USING btree
(id DESC NULLS LAST, name COLLATE pg_catalog."POSIX" text_pattern_ops DESC NULLS LAST)
WITH (fillfactor=10)
WITH (fillfactor=10, deduplicate_items=True)
TABLESPACE pg_default
WHERE id < 100;
Loaded 100 of 237 files, more files were not shown because too many files have changed in this diff. Show more