The MASTER_PASSWORD_HOOK setting lets administrators specify an external
command that returns a per-user encryption key, with %u in the configured
string replaced by the current user's name. The previous implementation
substituted the username into the command string and executed the result
with subprocess.Popen(..., shell=True). Because the username can originate
from an external authentication source (OAuth/OIDC claims, Kerberos,
webserver auth), a username containing shell metacharacters allowed an
authenticated user to execute arbitrary commands as the pgAdmin service
account in deployments where the hook uses %u.
Tokenise the trusted hook string into an argument vector first, substitute
the untrusted username into the individual arguments, and execute with
shell=False. The username is therefore always confined to a single argv
element and any shell metacharacters it contains are inert.
Note for administrators: hooks that previously relied on shell features
(pipes, redirection, environment-variable expansion, globbing) in the
MASTER_PASSWORD_HOOK string itself will no longer have those interpreted;
such logic should be moved into the hook script. The documented form,
'<PATH>/script.sh %u', is unaffected.
Adds regression tests covering usernames containing ';', '$()', backticks,
pipes, '&&' and newlines, plus an end-to-end marker-file proof that no
shell execution occurs.
Reported-by: B1gN0Se
Extends the tool-permission enforcement pattern from the reported CVE
fix to the backup/restore/maintenance/import_export blueprints, which
had the identical front-door-only gap: only create_*_job was gated,
while objects()/check_utility_exists()/get_import_export_settings()
relied on @pga_login_required alone.
Also clears password/save_password/tunnel_password on adhoc server
clones, not just ownership fields - Server.clone() copies every
column, so a non-owner cloning another user's shared server was still
persisting that user's stored credentials into a row the non-owner
now owns.
Adds missing socket test scenarios (erd tables, psql socket_input/
socket_set_role/resize) that the existing test docstring claimed to
cover but didn't.
Add regression coverage for the authorisation fixes:
* test_tool_permissions_required: a consolidated, per-blueprint check
that logs in as a user with no roles (hence no tool permissions) and
asserts every gated backend HTTP route across the query tool, grant
wizard, schema diff, ERD, PSQL and debugger returns 403. This catches
any future route added to these blueprints without the decorator.
* test_tool_socket_permissions_required: asserts the schema diff
compare_database/compare_schema and psql start_process Socket.IO
handlers refuse a user lacking the tool permission.
* test_adhoc_connect_server_ownership: asserts that an adhoc connect
triggered by a non-owner against an administrator-owned shared server
persists a server row owned by the caller and not shared.
All three are skipped in DESKTOP mode, where every request is
auto-authenticated as the all-permissions DESKTOP_USER.
When /misc/workspace/adhoc_connect_server is given a sid, it clones the
existing server. Server.clone() copies every column of the source row,
including user_id, shared and shared_username. When a non-owner triggered
an adhoc connect against an administrator-owned shared server, the clone
inherited the administrator's ownership and shared flag, so pgAdmin
persisted a new, administrator-owned, shared adhoc server row created at
the behest of another user; the row is committed before the connection is
attempted, so it survived even when the connection failed.
Force the cloned adhoc record to belong to the current user and to be
private (user_id, shared, shared_username) before committing, mirroring
the new-server branch, so a non-owner can no longer persist a
cross-tenant server record.
The tool permissions (tools_query_tool, tools_grant_wizard,
tools_schema_diff, tools_erd_tool, tools_psql_tool, tools_debugger) were
enforced only on a single "front door" route per tool, whilst the rest of
each tool's backend workflow relied on pga_login_required alone. An
authenticated user who had been denied a tool could therefore still drive
the tool through its other routes and Socket.IO handlers:
* Query Tool: View/Edit Data via sqleditor.initialize_viewdata and the
rest of the view-data chain, bypassing the gate on initialize_sqleditor.
* Grant Wizard: object discovery (objects), SQL preview (modified_sql)
and the actual privilege change (apply) were ungated; only acl_list
was protected. This allowed real GRANTs to be generated and applied.
* Schema Diff: initialize, servers, get_server, connect_server,
connect_database, databases, schemas, ddl_compare and the
compare_database/compare_schema socket handlers were ungated; only
panel was protected.
* ERD: initialize, prequisite, sql (table DDL generation) and the
tables socket handler were ungated; only panel was protected.
* PSQL: the panel route and the entire /pty Socket.IO namespace
(start_process, socket_input, socket_set_role, resize) had no tool
permission check at all, so a denied user could still obtain an
interactive psql session.
* Debugger: the directly addressable get_arguments/set_arguments/
clear_arguments routes were ungated.
Apply permissions_required to the HTTP routes and socket_permissions_required
to the Socket.IO handlers so the tool permission is enforced consistently
across each tool's surface. The permission check is the outermost
decorator, so it runs before any connection or transaction lookup.
Flask-Security's permissions_required only guards HTTP routes; pgAdmin's
Socket.IO event handlers had no permission-aware equivalent and relied on
socket_login_required, which checks authentication but not the tool
permission. Add socket_permissions_required as the socket counterpart of
permissions_required so that event handlers can enforce the same
tool-level RBAC as the routes.
It reads the user's permissions via has_permission() rather than
flask_principal's Permission().can(), so it does not depend on the
principal identity having been loaded onto the socket request context,
and it honours pgAdmin's Administrator bypass. On failure it disconnects
and raises ConnectionRefusedError, mirroring socket_login_required.
Geometry Viewer's base-layer choices were hardcoded (Empty, Street,
Topography, Gray Style, Light Color/Dark Matter), with no way to
point at a private/internal tile server or a provider not in that
built-in list.
Add a geometry_viewer preferences category (custom_tile_url, _name,
_crs, _attribution, _max_zoom) under sqleditor. When a valid custom
tile provider URL is configured (must be http(s) and contain {x},
{y}, {z}), it's used as the default base layer; built-in tiled
layers are hidden unless the custom provider's CRS is also
EPSG:3857 (Web Mercator), since they can't be reprojected together.
Non-mercator custom providers (e.g. EPSG:4326) only apply to SRID
4326 geometry data; other SRIDs still render on the existing blank
Cartesian plane.
Custom name/attribution are DOMPurify-sanitized before being passed
to Leaflet (both are innerHTML sinks). URL is not sanitized but is
gated by the http(s):// prefix check, so javascript:/data: URLs are
rejected; it's consumed as a tile-image src template, not an HTML
sink.
Adds GeometryViewerUtils.spec.js covering URL validation, defaults,
sanitization, CRS resolution/fallback, and base-layer composition.
The failure path for the second query (node.sql, resolving the
trigger function's node data) reported errormsg=rset — the result
dict from the first, already-successful get_function_oid.sql query
— instead of res, the actual error from the failed query. Any
failure here (permissions, catalog access) surfaced a stale,
misleading payload instead of the real cause.
Replace the per-server ServerGroupModule.has_shared_server() loop and
scattered ad hoc shared-server checks with two centralized helpers,
get_server_groups_for_user() and get_server_groups_for_user_query(),
in server_access.py — a single SQL EXISTS query instead of iterating
servers per group. get_server_group() reuses the same shared-access
logic, and returned groups now carry is_shared_group metadata so the
browser tree can render the correct icon and derive edit/delete
permissions consistently.
update() now resolves the target group via get_server_group(gid,
hide_shared=True), so a non-owner can no longer rename another
user's shared group; delete() already blocked deleting shared or
first-user groups and now reports clearer errors for both cases.
Desktop mode's non-SERVER_MODE query path uses
literal(0).label('is_shared_group') (a bare int has no .label()).
Adds test_sg_permissions.py covering owned/shared/desktop group
visibility and access.
* docs: add basic starter contributing guide
Add a CONTRIBUTING.md file to document the contribution workflow for pgAdmin 4.
Includes:
- Getting started instructions
- Issue reporting guidelines
- Development workflow and coding practices
- Pull request requirements
- Development environment setup
- Database migration guidance
- Security reporting process
- Community support resources
This provides a single reference for new and existing contributors.
* add language tag on the opening fence of the Windows command block
* Add architecture overview, define $PGADMIN4_SRC, and link GitHub URLs
* docs: update contribution guidance
Clarify contributor workflow, fix the runtime link, and add references to existing contributor docs.
On packaged Linux installs the bundled venv is created with
--system-site-packages (issue #7173) so it can reach system packages such
as dbus-python. That also exposes the deprecated system oauth2client and
whatever pyOpenSSL ships alongside it. googleapiclient imports oauth2client
optionally, and on Ubuntu 24.04 that drags in a pyOpenSSL too old for our
bundled cryptography, aborting startup at blueprint registration with
"AttributeError: module 'lib' has no attribute 'GEN_EMAIL'".
pgAdmin only ever authenticates to Google via google-auth and
google-auth-oauthlib, so park a None sentinel under
sys.modules['oauth2client'] before importing googleapiclient in both the
web module and the standalone pgacloud provider. The optional import then
fails cleanly and googleapiclient falls back to google-auth.
Closes#10110
Expanding a Trigger node under a Table to view its function threw
'Response' object is not iterable, crashing the tree.
get_children_nodes() can legitimately return a Flask Response instead
of a list (e.g. gone("Could not find the specified trigger
function") when the trigger's function has no matching row in the
trigger-function node query, as happens for a genuine internal-
language function such as suppress_redundant_updates_trigger).
NodeView.children() / PGChildNodeView.children() unconditionally
sorted whatever get_children_nodes() returned, crashing on a
Response instead of passing it through.
Separately, TriggerView.get_children_nodes() rendered the trigger-
function node.sql template (which quotes fnid via qtLiteral(conn))
without passing conn into the template context. This call site
predates qtLiteral requiring a connection; after qtLiteral was
hardened in 658bb585d to raise ValueError instead of silently
degrading when conn is missing, this broke node.sql rendering for
every trigger, not just the internal-language case.
Fix both: check isinstance(children, flask.Response) in children()
before sorting/iterating and return it as-is, and pass
conn=self.conn into the node.sql render call, matching every other
render_template call site in the file.
Fixes#10117
Extension UI gives error with PG 19
column reference "comment" is ambiguous LINE 5: e.comment
Issue caused because in pg19 :
SELECT * FROM pg_catalog.pg_available_extensions();
now outputs an additional column `location`
after default_version and comment is now 4th column
_verify_bundle_linkage's host-library scan only matched files by
.so/.dylib name suffix, so a shipped executable (Contents/MacOS/*)
or a Python.framework payload with a host-linked dependency but no
matching suffix could slip through unchecked — the same class of
bug #10135 was added to catch.
Detect Mach-O executables/libraries by content via `file` instead
(same pattern the codesign step in this file already uses),
regardless of extension or executable permission bits, so readable
non-executable dylibs are covered too.
9.16 failed to start on Intel Macs (#10123): cryptography had no
prebuilt Intel macOS wheel, pip compiled it from source, and its
openssl-sys build linked the builder's Homebrew OpenSSL into
_rust.abi3.so instead of the bundled one. _fixup_imports deliberately
skips _rust.abi3.so, so the dangling reference shipped unnoticed and
the app died on startup for anyone without that external dylib.
Add _verify_bundle_linkage, run after the bundle is assembled and
relocated but before code-signing: walks every .so/.dylib and fails
the build if any install-name points at a build-host prefix
(/usr/local, /opt/homebrew, /opt/local, $SLAVE_HOME). OS libraries
and @loader_path/@rpath/@executable_path references pass untouched.
Turns this class of bug into a build-time failure instead of a
runtime one.
Column-owns-sequence was detected by guessing the sequence name as
<table>_<col>_seq and string-matching it in nextval(...). Renaming
a table/column broke the guess (false negative, #10100); an
unrelated sequence with the same guessed name broke it the other
way (false positive, #10101).
Check col.get('seqrelid') (real pg_depend ownership) instead, and
exclude identity columns. SERIAL is now emitted iff a genuine
ownership dependency exists, independent of naming.
Fixes#10100, #10101
selectFilterOption() computed newOptions inside the functional
setSelectedFilters(prev => {...}) updater, then used that same
variable to dispatch TRIGGER_CHANGE_FILTER synchronously. Since the
updater runs lazily on next render, every chip toggle broadcast an
empty filter, so the tree always showed "No difference found" until
a full re-Compare.
Compute the new selection synchronously from current state and use
it for both the state update and the dispatched event.
Fixes#10102
getSelectionFromState() joined ranges with the chosen EOL but sliced
each range via state.sliceDoc(), which always uses the document's
own \n regardless of the LF/CRLF preference. Introduced in #8691
when the copy path switched from doc.sliceString(..., lineSep) to
sliceDoc(...) to fix multi-cursor copying.
Slice with state.doc.sliceString(range.from, range.to, lineSep) so
the chosen EOL applies within a range too; the #8691 multi-cursor
join behavior is unchanged.
Fixes#10158
OAuth2 provider settings are only read from the OAUTH2_CONFIG list;
bare top-level keys like OAUTH2_CLIENT_ID or
OAUTH2_SSL_CERT_VERIFICATION are silently ignored. This is an easy
trap in container deployments where every other option is set via
an individual PGADMIN_CONFIG_<KEY> env var.
Add warn_on_misplaced_oauth2_config(), called once at OAuth2 module
startup, that logs when per-provider OAUTH2_* keys exist at the top
level with no provider configured in OAUTH2_CONFIG. No behavior
change for correctly configured or unconfigured deployments.
Documents the supported container approach in oauth2.rst.
Closes#10053
Disconnect handlers in server.js/database.js built the transitional
tree-node label as an HTML string and passed it to setLabel(), which
writes via label.textContent — correctly escaping markup, so the
literal "<span class='text-muted'>[Disconnecting...]</span>" text
was shown instead of a greyed-out label.
Pass plain text plus a className: 'text-muted' instead; setLabel()
applies the className to the label span (default file-name class
otherwise), so muted styling comes from CSS, not injected markup.
Fixes#10106
The unqualified "View Data" action always ran with no LIMIT, unlike
"First/Last 100 Rows" which hardcode limit=100 — unusable on large
tables with no way to cap it.
Add view_data_default_row_limit preference (int, default 0 =
unlimited) under sqleditor Options. GridCommand.__init__ applies it
as self.limit when cmd_type == VIEW_ALL_ROWS and the value is
positive; objectquery.sql already renders LIMIT only when > 0, so
no template changes needed. First/Last 100 Rows untouched.
Closes#10104
evaluate_and_patch_config() only disabled USE_OS_SECRET_STORAGE when
keyring.get_keyring().name == 'fail Keyring'. On Debian 13 over RDP, a
real backend (e.g. SecretService) is selected because no D-Bus/GNOME
Keyring session is running, so the name check passes even though every
actual keyring call fails — leaving the crypt key unset and every
operation raising a bare CryptKeyMissing.
Probe the selected backend with a harmless read of a never-existing
entry after selection. A healthy backend returns None without
prompting; an unusable one raises, and we disable
USE_OS_SECRET_STORAGE so the app falls back to the master-password /
in-app crypt key mechanism instead of failing outright.
Fixes#10107
triggerExecution() only fell back to getQueryAt(cursor) when
getSelection() was falsy. Since #7293/#8691 (non-continuous
highlighted-block support), getSelection() flattens and joins all
ranges, so multiple empty cursor ranges can produce a truthy but
whitespace-only string (e.g. "\n"), skipping the fallback entirely —
ALT+F5 silently tried to execute whitespace. Same stale check existed
in checkUnderlineQueryCursorWarning(). Also fixes a latent
state.selection.head reference (EditorSelection has no such
property; correct field is state.selection.main.head).
Treat a whitespace-only selection as empty before the fallback check
in both functions. Real highlighted selections always contain
non-whitespace, so #7293/#9570 behavior is unaffected.
Fixes#10109
Consolidates 20 open dependabot PRs (JS + Python) into one bump, applied
directly rather than cherry-picked (PR branches were stale and would have
reverted unrelated fixes like the yarn packageManager pin). Adds further
same-major patch/minor bumps found by auditing beyond dependabot's own PR
list.
JS (web + runtime): axios, brace-expansion, form-data, undici, js-yaml,
dompurify, @babel/core, webpack, sharp, electron, eslint, react-checkbox-tree,
autoprefixer, eslint-plugin-jest, globals, jest, jest-environment-jsdom, svgo,
terser-webpack-plugin, typescript-eslint, webpack-bundle-analyzer,
@date-io/date-fns, @szhsin/react-menu, @tanstack/react-query, @types/react,
ajv, anti-trojan-source, ip-address, marked, moment-timezone, papaparse,
postcss, react, react-dom, react-draggable, react-timer-hook, sql-formatter,
zustand.
Python: certifi, selenium (version-gated: 4.45.0 requires Python >=3.10,
4.44.0 kept for <=3.9 to preserve Python 3.9 support).
Reverted / excluded, with reasons:
- azure-mgmt-resource 26.0.0: moved ResourceManagementClient from
azure.mgmt.resource to azure.mgmt.resource.resources, breaking
pgadmin/misc/cloud/azure/__init__.py at import time. Caught by the Python
regression suite. Kept at 25.0.0.
- @simonwep/pickr 1.10.0: switched its build tool to tsup, which marks its
UMD bundle as an ES module via Symbol.toStringTag instead of the
__esModule flag Babel's interop helper checks for. Babel double-wraps the
export, so `new Pickr(...)` resolves to a non-constructor and crashes
every dialog that mounts a color picker -- reproduces only in the
production/minified webpack build, not the dev bundle or Jest. Pinned to
~1.9.1 (tilde, not caret) so a future install can't silently float back to
1.10.x.
- paramiko 3->5 (#9927): structurally blocked by sshtunnel 0.4.0 still
referencing paramiko.DSSKey.
- @mui/material / @mui/x-date-pickers 7/8->9 (#10091, #10092): known
UI-breaking, needs the accompanying component fixes tracked on a separate
branch, not a bare version bump.
- react-arborist, @tanstack/react-virtual, react-frame-component: same-major
bumps available but excluded -- core object-browser tree, already-fragile
virtualization code, or tilde-pinned range respectively.
- A handful of JS packages hit Yarn 4.15's registry quarantine gate (blocks
just-published versions); backed off to the next-older version instead of
forcing through.
Added core-js as an explicit devDependency: it was never declared despite
webpack's Babel config (useBuiltIns: 'usage', corejs: 3) requiring it --
it only worked because @simonwep/pickr 1.9.1 happened to pull it in
transitively, which broke when pickr was briefly bumped.
Verified: eslint (web + runtime), full JS test suite (149/149 suites,
916/916 tests), webpack production build compiles clean, Python regression
suite (2388/2388, excl. Selenium), Selenium feature_tests (17/19 pass; the
2 failures trace to a local pldbgapi-extension gap, unrelated to any
bumped dependency).
#10156 fixed CI build failures caused by Yarn 4.x fetching a newer patch
with different builtin compat hashes, breaking --immutable lockfile
validation, but only patched pkg/linux/build-functions.sh and the GHA
workflows. Make.bat, pkg/mac/build-functions.sh, pkg/pip/build.sh, and
Dockerfile still hardcoded 'yarn set version 4', which is what broke the
Windows Jenkins snapshot build (job 1669).
* fix: pin Yarn version from packageManager field in build scripts
Replace hardcoded 'yarn set version 4' in build-functions.sh with a
dynamic lookup from each workspace's package.json packageManager field.
Also syncs runtime yarn version to 4.15.0 to match web/package.json.
Fixes CI build failures caused by Yarn 4.x fetching a newer patch that
produces different builtin compat hashes, breaking --immutable lockfile
validation.
The Server and SharedServer models each declared a relationship named
'servers' that actually pointed at the parent ServerGroup, with backrefs
('server'/'sharedserver') providing the collections. The naming was the
inverse of what it modelled and made the call sites read oddly.
Redefine the relationships in the natural direction using explicit
back_populates: ServerGroup gains 'servers' and 'sharedservers'
collections (carrying the existing delete-orphan cascade), and Server and
SharedServer each gain a 'servergroup' reference. The two call sites that
read the group name are updated from server.servers.name to
server.servergroup.name accordingly.
This is a naming/modelling cleanup with no functional or schema change, so
no migration is required.
Add an opt-in Gateway API HTTPRoute template to the Helm chart as an
alternative to the existing Ingress, addressing #9942. It is disabled by
default (httpRoute.enabled: false) so existing installs are unaffected.
The template mirrors the existing ingress.yaml conventions: the
pgadmin4.fullname backend, commonLabels/commonAnnotations propagation, and
a hostname that falls back to ingress.hostname when httpRoute.hostnames is
unset. parentRefs is required (a fail guard fires when it is empty),
apiVersion defaults to gateway.networking.k8s.io/v1, and the default rule
forwards "/" (PathPrefix) to the pgAdmin service. Custom hostnames and
rules can be supplied for full control. The new values are documented in
the chart README.
Set an X-Remote-User response header containing the authenticated username
on every request when the LOG_AUTHENTICATED_USER config option is enabled
(disabled by default). This allows the HTTP access log to be configured to
include user identity via standard log format directives
(%({x-remote-user}o)s in gunicorn, %{X-Remote-User}o in Apache) without
requiring any changes to pgAdmin's session or authentication behaviour. The
default gunicorn access log format is updated to surface the header.
The username is sanitised to a header-safe value: it is transliterated to
Latin-1 (HTTP headers are Latin-1 only) and any non-printable characters,
including CR/LF, are stripped, so unusual usernames cannot cause a 500 on
every response.
A signed, notarised and stapled pgAdmin 4.app was rejected by Gatekeeper
with "invalid destination for symbolic link in bundle". Gatekeeper walks
every symlink in the bundle and rejects the whole app if any link does
not resolve to a real file inside it; notarisation does not catch this, so
a broken link slips through stapling and only surfaces as a Gatekeeper
failure on the end user's machine.
The embedded Python.framework ships such links: an arm64-only build still
carries a bin/python3-intel64 launcher symlink (whose target
_strip_architecture deletes when it removes the foreign-arch files), and
the bundled Tcl/Tk frameworks carry PrivateHeaders links pointing at a
Versions/Current that has none.
Add a _prune_dangling_symlinks step that removes every dangling symlink in
the bundle after architecture stripping and before signing, then fails the
build if any remain, so this cannot slip past notarisation again.
PR #10047 made `Preferences.save_cli()` validate against the registered
preference object, but `setup.py set-prefs` only enters `app_context()`
and never calls `run_before_app_start()`, leaving `Preferences.modules`
empty. Every CLI write then fell into the "Module 'X' is no longer in
use." path and was reported as "Invalid value provided".
Trigger registration explicitly (run_before_app_start enters both
app and test_request contexts internally, satisfying registration
callbacks that touch current_user) and propagate the typed error
message from save_cli so users see the actual reason a value was
rejected.
The PDF docs build in the Check documentation builds workflow began
failing after the 9.16 release notes added "Mai Phạm Hiền" as the
reporter for CVE-2026-12049. pdflatex's default utf8 inputenc maps
Latin-1 and Latin-Extended-A but rejects the precomposed Vietnamese
code points U+1EA1 (a with dot below) and U+1EC1 (e with circumflex
and grave) with:
! LaTeX Error: Unicode character ạ (U+1EA1)
not set up for use with LaTeX.
Declare the two characters in the LaTeX preamble via
\DeclareUnicodeCharacter so they typeset correctly:
ạ -> \d{a} (a with combining dot below)
ề -> \`{\^e} (e with circumflex + grave)
Picked this over switching to xelatex because it is a single-file
change, keeps the existing pdflatex toolchain, and the CI workflow
already has the apt packages it needs (no .github/workflows change).
Verified by running make docs-pdf in an ubuntu:22.04 container that
mirrors check-doc-builds.yml (same apt packages, locale-gen
en_US.UTF-8, sphinx + sphinxcontrib-youtube): 682-page pgadmin4.pdf
produced cleanly, zero Unicode-character errors in the log.
Master commit 7b9b8cdd0 (#10054 / Issue #10050) rebased the
version-specific SQL templates so the new default targets PG 14, and
collapsed the 11_plus/, 12_plus/, 14_plus/ buckets into default/.
After merging that into cve-9.16, the SQL-string-literal lint
introduced for CVE-2026-12044 (658bb585d, 2ae0d3610) reported:
4 NEW violations at the new default/ paths --
subscriptions/sql/default/create.sql :: '{{ data.streaming}}'
subscriptions/sql/default/update.sql :: '{{ data.streaming}}'
grant_wizard/pg/default/sql/function.sql :: '{{ kind }}'
grant_wizard/ppas/default/sql/function.sql :: '{{ kind }}'
25 STALE entries pointing at the removed _plus/ paths --
aggregates/sql/{11,12}_plus/create.sql (data.initial_val,
data.moving_initial_val)
tables/index_constraint/sql/11_plus/properties.sql
(constraint_type)
tables/sql/{11,12}_plus/properties.sql (pg_get_partkeydef)
subscriptions/sql/14_plus/{create,update}.sql (data.sync,
data.streaming)
grant_wizard/{pg,ppas}/11_plus/sql/function.sql (func_type,
icon, kind)
search_objects/sql/{pg,ppas}/11_plus/search.sql,
search_objects/sql/ppas/12_plus/search.sql (search_text,
obj_type, LABELS_SCHEMACOL)
Each new entry carries the same justification as the corresponding
removed _plus/ entry (no semantic change to what is allowlisted; only
the path universe shrank). The four 'data.streaming' and 'kind'
additions are not new violations in the source-code sense -- the
expressions themselves were already allowlisted at the version-suffix
paths -- they only became visible at default/ paths after the rebase.
Verified: test_sql_string_literal_lint passes on PG 17 and PG 18
(33/33 each); pycodestyle clean.
The login page hardcoded the 'fab' (brands) Font Awesome style for the
OAuth2 button icon, so non-brand icons could not be used. Use the
configured OAUTH2_ICON as-is when it already specifies a style class
(e.g. 'fas fa-key'), and fall back to 'fab' when only an icon name is
given, preserving backward compatibility.
The CLI set-prefs path (save_pref -> Preferences.save_cli) wrote the raw
value to the configuration database without any type validation and
always reported success, unlike the GUI path which validates via
_Preference.set(). Route save_cli through the same set() validation
(set() now accepts an explicit user_id so it works outside a request
context), and make setup.py set-prefs check the result and report
preferences whose value was invalid.
* Fix View/Edit Data crash on a stale/non-filter session transaction object
initialize_viewdata restores the filter and data-sorting from any command
object previously stored in session['gridData'] under the same trans_id. It
assumed that object was always a filter-capable (View/Edit Data) command and
accessed old_trans_obj._row_filter / ._data_sorting directly.
That assumption is wrong: the same trans_id may have been used by the Query
Tool (a QueryToolCommand, which does not inherit SQLFilter), or the session
may contain an incompatible object persisted by an older version after an
upgrade. In those cases the attribute access raised AttributeError, returning
a 500 from the endpoint and - in desktop mode, where this runs during startup
- preventing the application from loading at all.
Guard the restore with isinstance(old_trans_obj, SQLFilter) (short-circuited
before the did/obj_id checks) so a non-filter object is simply skipped. Add a
regression test that seeds a pickled QueryToolCommand under the trans_id and
asserts initialize/viewdata returns 200.
Closes#9744
* test: avoid hard-coded PK constraint name colliding across the suite
The regression test created its table with a fixed 'table_pk' primary key
constraint name, which collides with the same name used by TestViewData in
this package when the full suite runs against one database. That made the
CREATE TABLE fail in CI (table not found -> IndexError on the OID lookup).
Let PostgreSQL auto-name the primary key instead.
---------
Co-authored-by: Ashesh Vashi <ashesh.vashi@enterprisedb.com>
Add 14 release-note entries that were merged on master but not yet
captured in the 9.16 notes: 1 new feature (#2431), 6 housekeeping
(#9817, #9866, #9917, #9959, #10014, #10023) and 7 bug fixes (#9701,
#9782, #9933, #9952, #9985, #10013, #10030). Entries inserted in
numeric order within each section.
Also add an "Additional changes (no associated issue) -> Dependencies"
section mirroring the 9.15 format, listing net direct dep bumps
between REL-9_15 and HEAD across Python (requirements.txt,
tools/requirements.txt, web/regression/requirements.txt), web/
package.json, and runtime/package.json. Transitive yarn resolutions
and the setuptools pin (covered by bug fix#9829) are excluded.