Compare commits

...
152 Commits
Author SHA1 Message Date
alexandDaria Marutkina ac3ce9e085 dbeaver/pro#8728 add pricing policy (#4281)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-04-15 16:30:06 +02:00
Sychev AndreyandEvgenia 3cc9c3ce86 dbeaver/pro#8274 feat: add key icons for key columns in data editor (#4283)
* dbeaver/pro#8274 feat: add key icons for key columns in data editor

* dbeaver/pro#8274 feat: change key icon. put to core

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-04-15 16:29:02 +02:00
1660e62391 Save scripts to custom locations (use import/export buttons) (#4258)
* dbeaver/pro#7880 adds MVP for saving script to cloud storage

* removes registerScriptExportController

* finalizes the ui

* adds missing locales

* introduces footer slot to use CommonDialog api

* adds locales

* moves success message to the tab components itself

* pr small fixes

* saves file with timestamp

* send notification with correct filename

* fixes locales

* uses different approach for handling dialog save

* cleanup

* self review fixes

* adds lazy import for export dialog

* uses form API to save the dialog

* fixes imports (tab -> panel)

* uses react state for filename

* build fix

* handles form validation

* sanitizes file name

* removes redundant form layout

* fixes bug: allows to load tree root with useTreeData

* uses context api to resolve export script dialog instead of throwing errors approach

* dbeaver/pro#7880 do not show notification on local export

* dbeaver/pro#7880 remove notification from local export

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
2026-04-15 15:12:32 +02:00
Sychev AndreyandEvgenia c27fca442b dbeaver/pro#8687 fix(formatNumber): fix unit selection for floats and… (#4276)
- Use Math.log10 instead of toString().length to count integer digits — toString() on a float
  like 4036.29 has length 7, which incorrectly bucketed thousands-range values into millions
- Fix exponent formula for 3n-digit numbers (e.g. 100000 gave exponent=6→M instead of 3→k)
- Apply precision param d below 1000 (was hardcoded 2); default to 2
- Add formatNumber.test.ts

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-04-15 15:10:57 +02:00
318100300f Dbeaver/pro#8044 add api for database user preference (#4104)
* dbeaver/pro#8044 add api for database user preference

* dbeaver/pro#8044 set to prefstore

* dbeaver/pro#8044 add api for database user preference

* dbeaver/pro#8044 add api for database pref

* dbeaver/pro#8044 add api for database pref

* dbeaver/pro#8044 add api for database pref

* dbeaver/pro#8044 refactor api for database preferences

* dbeaver/pro#8044 refactor api for database preferences

* dbeaver/pro#8044 refactor api for database preferences

* dbeaver/pro#8044 add api for database user preference

* dbeaver/pro#8044 set to prefstore

* dbeaver/pro#8044 add api for database user preference

* dbeaver/pro#8044 add api for database pref

* dbeaver/pro#8044 add api for database pref

* dbeaver/pro#8044 add api for database pref

* dbeaver/pro#8044 refactor api for database preferences

* dbeaver/pro#8044 refactor api for database preferences

* dbeaver/pro#8044 refactor api for database preferences

* dbeaver/pro#8044 review fixes

* dbeaver/pro#8044 review fixes

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-04-15 13:57:51 +02:00
9c9e8e4496 dbeaver/pro#8716 added generate sql options (#4262)
* dbeaver/pro#8716 added generate sql options

* dbeaver/pro#8716 extract typed options

* dbeaver/pro#8716 added annotations

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-04-14 17:09:12 +02:00
alexandmr-anton-t 4789acb76a dbeaver/pro#8996 remove cve (#4280)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-04-14 13:08:55 +02:00
alexandmr-anton-t 39ad4568d0 dbeaver/pro#8962 add block padding to the menus (#4273)
* dbeaver/pro#8962 add block padding to the menus

* dbeaver/pro#8962 adjust padding size

* dbeaver/pro#8962 make inner radius smaller

* dbeaver/pro#8962 use separate tokens

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-04-13 17:15:02 +02:00
Serge Rider 860b8e2e16 dbeaver/pro#8559 Workbench launch redesign (#4277) 2026-04-10 20:16:35 +02:00
sergeyteleshevandDaria Marutkina 4b9ba00663 [CB] Multi-paste support in Data Editor (#4271)
* dbeaver/pro#8670 adds simple multiple paste for the selected cells

* cleanup

* moves logic to useGridSelectedCellsPaste

* cleanup

* adds getSelectedElementsWithFocused

* cleanup

* pr fixes

* adds readonly guard for paste cells

* adds check for is cell editable

* cleanup

* Revert "cleanup"

This reverts commit 309e254453.

* Revert "adds check for is cell editable"

This reverts commit 00b6b63cfa.

* allows to copy blobs base64 but forbids to paste to blob cell

* reuses isCellEditable

* fixes source

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-04-09 19:39:43 +02:00
MalteandDaria Marutkina 897dd994ba Pass arguments from entrypoint script (#4269)
* dbeaver/cloudbeaver#4268 fix order of arguments

This allows overriding the -web-config argument.

* dbeaver/cloudbeaver#4268 pass arguments to run-cloudbeaver-server.sh

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-04-09 19:39:21 +02:00
e81a150287 Dbeaver/pro#8655 cb ai functions support (#4254)
* dbeaver/pro#8655 added function confirmation and result events

* dbeaver/pro#8655 refactor action function processing

* dbeaver/pro#8655 support function role

* dbeaver/pro#8655 revert ai function changes

* Revert "dbeaver/pro#8655 support function role"

This reverts commit 7343af5e97.

* dbeaver/pro#8655 simplify types

* dbeaver/pro#8867 Add custom WS event handlers. Move AI-related function top pro.

* dbeaver/pro#8655 Remove async tasks from chat workflow

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: Serge Rider <serge@dbeaver.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
2026-04-09 19:38:15 +02:00
0d17208be4 dbeaver/pro#8746 add useQuery hook (#4241)
* dbeaver/pro#8746 add useQuery hook

* dbeaver/pro#8746 add language tokens

* dbeaver/pro#8746 set loading to false in case of abort

* dbeaver/pro#8746 use more advanced useQuery

* dbeaver/pro#8746 fix code style

* dbeaver/pro#8746 remove useQuery hook

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Serge Rider <serge@jkiss.org>
2026-04-08 20:22:06 +02:00
9c1678c256 dbeaver/pro#8824 Inject lock manager into CloudBeaver RM controllers (#4242)
* dbeaver/pro#8824 Inject lock manager into CloudBeaver RM controllers

* dbeaver/pro#8824 Use application lock manager provider in servlet applications

* dbeaver/pro#8824 Use default application lock manager

* dbeaver/pro#8824 Fix codestyle warnings

---------

Co-authored-by: Daria Markaryan <43252954+DariaMarkaryan@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Serge Rider <serge@jkiss.org>
2026-04-08 19:23:38 +02:00
Ainurandkseniaguzeeva 321d45bd48 dbeaver/pro#7912 support external oauth in te desktop (#4255)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-04-08 17:15:40 +02:00
alex 0f33fb5514 dbeaver/pro#4158 add special filter operators (#4264)
* dbeaver/pro#4158 add special filter operators

* dbeaver/pro#4158 rename arg
2026-04-08 13:15:13 +02:00
sergeyteleshevandDaria Marutkina e7648b1b4c [CB] Toggle switches are rendered incorrectly (#4272)
* dbeaver/pro#8946 adds specifity for dense switches

* wraps with field

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-04-08 13:04:34 +02:00
45cfded51f [CB] DB Navigator - add subgroup to context menu to reduce the list of actions (#4246)
* dbeaver/pro#8236 adds submenus for the connection tree

* adds disabled state for submenu with no items

* removes variable

* loads first menus then draws it

* reverts submenu fix

* splits manage handlers from root handlers

* removes loader for manage menu to prevent bug

* cleanup

* removes action leaks for tables

* removes leak of the action delete

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-04-08 11:06:12 +02:00
Ruslan Musaev 9822147d68 dbeaver/pro#8954 fix cloudbeaver driver properties (#4266) 2026-04-07 15:49:40 +02:00
Ruslan MusaevandEvgenia 3b0f52f3ed dbeaver/pro#5124 fix case-insensitive username in import (#4251)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-04-07 13:47:11 +02:00
Daria Marutkina 3b032a275d dbeaver/pro#8893 Update README file (#4263)
* dbeaver/pro#8893 Update README file

Added changelog entry for version 26.0.2 with UI improvements, authentication fixes, database updates, and security vulnerability resolutions.

* Update README.md
2026-04-06 11:17:33 +02:00
Sychev Andrey a11ca992fe dbeaver/pro#7251 feat: add skip navigation feature for improved acces… (#4245)
* dbeaver/pro#7251 feat: add skip navigation feature for improved accessibility

* dbeaver/pro#7251 refactor: replace direct import with lazy loading for SkipNavLinks and SkipNavShortcutsLink

* dbeaver/pro#7251 refactor: use css modules for skip nav styles

* dbeaver/pro#7251 refactor: remove SkipNavBootstrap
2026-04-06 10:59:22 +02:00
serge-rider 5138c73ae5 dbeaver/pro#8894 Fix object search + annotations 2026-04-03 15:28:05 +02:00
sergeyteleshevandmr-anton-t ea36247797 [CB] Replace material ui with native css variables and tailwind utilities (#4188)
* dbeaver/pro#8433 initial version of material ui refactor

* fixes arrow colors

* fixes arrow colors 2

* fixes colors

* pr fixes

* removes layers due to it loses styles specificity race

* fixes autocomplete styles

* adds variables for typography

* moves switch logic to module styles

* removes elevation styles

* removes unused code

* migrates ripple effect to tw class

* Revert "migrates ripple effect to tw class"

This reverts commit 78229ac007.

* uses classname theme-typography for button

* adds elevation styles

* adds elevation styles 2

* moves theme ripple

* reverts scss for sql editor

* reverts scss for sql editor 2

* adds tailwindcss utilities + example

* Revert "adds tailwindcss utilities + example"

This reverts commit 20ca1b787c.

* creates tailwindcss plugin with shadow tokens + spread it across the app

* moves typography to the tailwindcss styles

* refactors sql editor styles

* moves styles from tailwindcss folder to styles

* moves ripple to tailwindcss plugin

* removes unused files

* removes unused file index

* removes redundant tailwindcss import

* adds animation layer for tailwindcss

* refactors animations

* build fix

* removes redundant imports of tailwindcss for styles

* removes layer components to keep some styles for switch

* removes animation from ripple effect

* makes ripple effect more contrast

* fixes fonts across the app to json view has correct font-mono from the tailwindcss box

* deletes module ts

* Revert "removes layer components to keep some styles for switch"

This reverts commit ee8b0e199e.

* fixes race for switch bold style

* changes ripple from 7% to 9%

* Reduce hover opacity in ripple effect (#4260)

* Reduce hover opacity in ripple effect

* Update ripple.css

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-04-02 18:24:19 +02:00
Serge Rider a6a1916299 dbeaver/pro#8894 Fix tests 2026-04-02 16:09:06 +02:00
Viktor Kirst b8699e6bcb dbeaver/pro#8896 Fix driver description (#4257) 2026-04-02 16:01:58 +02:00
Serge Rider 8cff50b0cf dbeaver/pro#8894 Workspace dispose and session cleanup fixes (#4256)
* dbeaver/pro#8894 Workspace dispose and session cleanup fixes

* dbeaver/pro#8894 Fix session cleanup

* Revert "dbeaver/pro#8894 Fix session cleanup"

This reverts commit 170882f69d.

* dbeaver/pro#8894 Fix session cleanup
2026-04-02 11:54:52 +02:00
dbeaver-devops b06a96dc31 dbeaver/pro#8893 26.0.3 version bump 2026-03-31 23:17:03 +02:00
Viktor KirstandMashaKorax 446bd81e08 dbeaver/pro#8392 Add H2 2.4 JDBC driver (#4227)
* dbeaver/pro#8392 Add H2 2.4 JDBC driver

* dbeaver/pro#8392 Fixed h2 driver description

* dbeaver/pro#8392 Add H2 V.2.4 module

* dbeaver/pro#8392 Fixed module name

* dbeaver/pro#8392 Rename H2 v2.4 driver to v3

---------

Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2026-03-31 17:28:50 +02:00
Dmitrii Barnukov 09863ea260 dbeaver/pro#7963 extended openurl event, added client event (#4247)
* dbeaver/pro#7963 extended openurl event, added client event

* dbeaver/pro#7963 fix typo
2026-03-31 16:52:46 +02:00
sergeyteleshev 34b4556773 [CB] New and repeat passwords can be different (#4234)
* dbeaver/pro#8572 fixes password validity on blur

* Revert "dbeaver/pro#8572 fixes password validity on blur"

This reverts commit cefd174254.

* adds context to the form for correct validation + handles validation on change also to keep the actual validity state

* adds context check to prevent validation inconsistent behavior

* adds revalidate to handle depended validations cases

* adds useFormCustomInputValidation

* cleanup

* uses useFormCustomInputValidation to avoid regression after refactor
2026-03-31 14:07:47 +02:00
alexandmr-anton-t 9b4c874e51 dbeaver/pro#8805 do not move focus on programmatic tab select (#4248)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-31 14:00:57 +02:00
Serge Rider ddbc9789e7 Merge remote-tracking branch 'origin/devel' into devel 2026-03-30 16:43:02 +02:00
Serge Rider 810d0215cb dbeaver#40616 update sql server driver to 13.4.0 2026-03-30 16:42:39 +02:00
alexandEvgenia 3dadc17ff1 dbeaver/pro#8644 add temp icon for base sql tab (#4243)
* dbeaver/pro#8644 add temp icon for base sql tab

* dbeaver/pro#8644 replace old icons

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-30 12:20:27 +02:00
sergeyteleshevandEvgenia 8c69985083 dbeaver/pro#8518 adds message for string parameters (#4233)
* dbeaver/pro#8518 adds message for string parameters

* aligned message with dbeaver: "string values must be quoted"

* reverts branding and uses it optionally for bind parameters popup

* renames prop

* makes few versions of alert

* Revert "makes few versions of alert"

This reverts commit 5b9f09009e.

* adds gray message

* adds icon

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-27 19:04:39 +01:00
1d5b475482 dbeaver/pro#8553 sql execution plan diagram (#4218)
* dbeaver/pro#8553 feat: add execution plan diagram component

* dbeaver/pro#8553 feat: enhance ExecutionPlanDiagram component with selectedNodeId prop

* dbeaver/pro#8553 feat: add flex-shrink to metric value for better layout

* dbeaver/pro#8553 refactor: remove queryString from IPlanData interface

* dbeaver/pro#8553 feat: integrate execution plan diagram component

* dbeaver/pro#8553 refactor: execution plan diagram

separate state, add l10n, make toolbar composable

* dbeaver/pro#8553 feat: implement keyboard navigation for execution plan nodes

* dbeaver/pro#8553 feat: add translation support for execution plan diagram labels

* dbeaver/pro#8553 fix: execution plan node dimensions

* dbeaver/pro#8553 refactor: move formatNumber to helpers to use it

* dbeaver/pro#8553 feat: add translation support for execution plan diagram API

* dbeaver/pro#8553 docs: add README and API documentation for execution plan diagram component

* dbeaver/pro#8553 feat: adjust cost bar height and collapse indicator position

* dbeaver/pro#8553 feat: update PRIMARY_AXIS_WEIGHT for improved directional navigation

* dbeaver/pro#8553 feat: refactor diagram actions and add collapse state management

* dbeaver/pro#8553 refactor: move execution plan diagram to separate EE plugin

* dbeaver/pro#8553 docs: remove redundant comment

* dbeaver/pro#8553 docs: update comments for source and target anchors in ILayoutEdge interface

* dbeaver/pro#8553 docs: update comment in ILayoutNode interface

* dbeaver/pro#8553 refactor: simplify heavy route computation

* dbeaver/pro#8553 refactor: optimize node handling in computePlanLayout function

* dbeaver/pro#8553 refactor: improve tree structure handling in usePlanTree and buildTree functions

* dbeaver/pro#8553 refactor: simplify usePlanTree

* dbeaver/pro#8553 refactor: extract actions from useExecutionPlanDiagramState

* dbeaver/pro#8553 refactor: improve configuration in execution plan diagram

* dbeaver/pro#8553 refactor: simplify metric rendering in PlanNode component

* dbeaver/pro#8553 feat: collapse by hotkey, not an extra element

* dbeaver/pro#8553 feat: add tooltip hints for plan node interactions

* dbeaver/pro#8553 refactor: remove high cost calculation and add node kind attribute

* dbeaver/pro#8553 style: fix formatting in index.ts for ISqlExecutionPlanViewProps export

* dbeaver/pro#8553 ci: remove unused dependency on @dbeaver/ui-kit

* dbeaver/pro#8553 refactor: remove SQL execution plan diagram translations from locale files

* dbeaver/pro#8553 refactor: update translation keys for SQL execution plan components

* dbeaver/pro#8553 refactor: rename components

* dbeaver/pro#8553 style: const

* dbeaver/pro#8553 refactor: reset execution plan diagram state properties

* dbeaver/pro#8553 refactor: update component imports and extract a component

* dbeaver/pro#8553 refactor: rename PlanNodeKind to EPlanNodeKind and update references

* dbeaver/pro#8553 feat: add zoom icons and update action references

* dbeaver/pro#8553 refactor: enhance translation functionality by adding args interpolation

* dbeaver/pro#8553 fix: Polynomial regular expression used on uncontrolled data

* dbeaver/pro#8553 refactor: update ActionsCapture to use ref for toolbar actions

* dbeaver/pro#8553 refactor: use jsx

* dbeaver/pro#8553 refactor: simplify

* dbeaver/pro#8553 refactor: replace useContext with use in DiagramContext

* dbeaver/pro#8553 refactor: convert ExecutionPlanEdge to memoized component

* dbeaver/pro#8553 refactor: convert ExecutionPlanNode to memoized component

* dbeaver/pro#8553 refactor: unify ref prop in ExecutionPlanNode and ExecutionPlanViewport

* dbeaver/pro#8553 refactor: extract ExecutionPlanViewportContent component and clean up CSS

* dbeaver/pro#8553 refactor: convert ExecutionPlanNodeMetric to memoized component

* dbeaver/pro#8553 refactor: improve usePlanLayout hook by utilizing useEffectEvent for stable callback

* dbeaver/pro#8553 refactor: simplify diagram actions handling in createExecutionPlanDiagram

* dbeaver/pro#8553 refactor:  move out execution plan diagram component from

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
2026-03-27 16:07:31 +01:00
sergeyteleshev b779b9d855 [CB] Fix high CVE-2026-33671 in picomatch (#4244)
* dbeaver/pro#8836 fixes picomatch CVE

* fixes happy-dom CVE
2026-03-27 14:24:43 +01:00
Sychev Andrey df676913a0 dbeaver/pro#8808 feat: add shortcut for search in data viewer (#4239)
* dbeaver/pro#8808 feat: add shortcut for search in data viewer

* dbeaver/pro#8808 feat: update shortcut ID for search functionality
2026-03-27 14:22:14 +01:00
fab9d1dd43 dbeaver/pro#7806 remove MenuBarSmallItem (#4224)
* dbeaver/pro#7806 remove MenuBarSmallItem

* dbeaver/pro#7806 remove reakit

* dbeaver/pro#7806 remove extra class

* dbeaver/pro#7806 increase base padding and background color

* dbeaver/pro#7806 use more safe style

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-27 14:18:35 +01:00
36dfdc8b33 dbeaver/pro#8825 Add tests for translating SQL CREATE/ALTER TABLE sta… (#4237)
* dbeaver/pro#8825 Add tests for translating SQL CREATE/ALTER TABLE statements with ASCII columns

* dbeaver/pro#8825 improve script normalization

* dbeaver/pro#8825 Update `SQLQueryTranslatorTest` to include `NOT NULL` constraint for ASCII column validation

---------

Co-authored-by: Ivan Gagarkin <gagarkin@Ivans-MacBook-Pro.local>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-25 13:51:45 +01:00
Alexander Skoblikovandkseniaguzeeva 8b0a20cfd7 dbeaver/pro#8768 fix federated logout (#4230)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-03-25 08:22:55 +01:00
Sychev AndreyandEvgenia 9553c30ee5 dbeaver/pro#8739 feat: add isEnabled option to tree filter for enhanc… (#4236)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-24 19:26:36 +01:00
Sychev AndreyandDaria Marutkina 9c36651aa0 dbeaver/pro#8760 feat: set max width for search panel in SQL editor (#4231)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-24 16:44:14 +01:00
sergeyteleshev 24a251cd5b [CB] Fix high CVE-2026-33228 in flatted (2) (#4228) 2026-03-24 16:43:43 +01:00
alexandEvgenia a44ba89cd9 dbeaver/pro#8339 show array value as array (#4229)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-24 15:00:59 +01:00
44077ad27a dbeaver/pro#8260 Provide a method to create or restore a WebSession… (#4222)
* dbeaver/pro#8260 Provide a method to create or restore a `WebSession` from `smAccessToken`

* dbeaver/pro#8260 Annotations and constants

---------

Co-authored-by: Ivan Gagarkin <gagarkin@Ivans-MacBook-Pro.local>
Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: serge-rider <serge@dbeaver.com>
2026-03-24 10:56:19 +01:00
Dmitrii BarnukovandEvgenia 3ae74763ff dbeaver/pro#8635 added message description (#4220)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-23 16:42:49 +01:00
Daria Marutkina 45a63f70ee dbeaver/pro#8732 Update README file (#4232)
Added detailed changelog for version 26.0.1, including updates to Data Editor, SQL Editor, general improvements, driver updates, and security fixes.
2026-03-23 12:42:04 +01:00
1bafe29e99 dbeaver/pro#7696 feat: replace slide component with dialog for improv… (#4192)
* dbeaver/pro#7696 feat: replace slide component with dialog for improved UI interaction

* dbeaver/pro#7696 refactor: update persistent elements getter

* dbeaver/pro#7696 fix: notifications persistence

* dbeaver/pro#7696 fix: add min-width to container in RightArea and Administration CSS

* dbeaver/pro#7696 refactor: remove persist prop from SideBarPanel component

make SideBarPanel persist by default

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-19 18:11:31 +01:00
Dmitrii Barnukov 10ad601f8b dbeaver/pro#5562 revert hide password if not applicable (#4225) 2026-03-19 10:31:47 +01:00
db9f145609 [CB] refactor data editor features readonly states [2] (#4213)
* dbeaver/pro#8299 adds features to data sources

* adds feature flags for data sources

* adds features to mobx

* pr fixes

* pr fixes

* fixes hasFeature

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-18 17:10:16 +01:00
d920227db0 dbeaver/pro#8470 migrate reakit to the ariakit (#4209)
* dbeaver/pro#8470 migrate reakit to the ariakit

* dbeaver/pro#8470 fix format

* dbeaver/pro#8470 remove deprecated TabsBox

* dbeaver/pro#8470 control currentTabId via effect

* dbeaver/pro#8470 restore navigationSettings.hasMultipleRows

* dbeaver/pro#8470 fallback to null for defaultSelectedId

* dbeaver/pro#8470 remove getComputed

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-18 17:08:38 +01:00
874760f780 dbeaver/pro#8306 split event enums in gql schema (#4202)
* dbeaver/pro#8306 split event enums in gql schema

* dbeaver/pro#8306 use events from ee sdk

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-18 12:37:15 +01:00
dbeaver-devops 0e286f383f dbeaver/pro#8732 26.0.2 version bump 2026-03-17 20:06:58 +01:00
40385f3b1c [CB] Reuse DnD from base grid component for Data Editor (#4201)
* dbeaver/pro#7159 adds dnd to column body cells + introduces new drop column styles

* adds line on dnd

* removes rdg-cell-column-drop class

* cleanup for class names

* renames to useDnD box

* cleanup

* renames hook

* fixes colors scheme + border line index

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-17 18:50:05 +01:00
sergeyteleshevandEvgenia 4bc74883e0 [CB] Improve fuzzy search in autocompletion in the SQL Editor (#4204)
* dbeaver/pro#8418 adds fuzzy search for sql editor proposals

* sorts proposals so startsWith and includes word is showed first

* tunes DEFAULT_FUZZY_THRESHOLD

* tunes DEFAULT_FUZZY_THRESHOLD

* Revert "tunes DEFAULT_FUZZY_THRESHOLD"

This reverts commit 7ac8757215.

* fixes ranging and threshold

* rename variable

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-17 18:39:19 +01:00
33d1d97fb7 dbeaver/pro#8515 sync properties state (#4214)
* dbeaver/pro#8515 sync properties state

* dbeaver/pro#8515 fixed null to empty strings props conversion

* dbeaver/pro#8515 do not send property if its in default value

* Revert "dbeaver/pro#8515 fixed null to empty strings props conversion"

This reverts commit 5c4f181edf.

* dbeaver/pro#8515 fix firefox icon

* dbeaver/pro#8515 change prop order

* dbeaver/pro#8515 add comment

---------

Co-authored-by: Dmitrii Barnukov <dmitrii.barnukov@dbeaver.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-03-17 17:35:45 +01:00
296e03ec74 7023-cb-find-and-replace-new (#4085)
* dbeaver/pro#7023 feat: add SearchPanel component and related styles to UI-kit
add chevron Icon to UI-kit (used in Search Panel)

* dbeaver/pro#7023 feat: add SearchPanel story

* dbeaver/pro#7023 feat: update SearchPanel integration in CodeMirror

* dbeaver/pro#7023 feat: add onReplaceToggle prop to SearchPanel for toggle functionality

* dbeaver/pro#7023 fix: replace-input styles

* dbeaver/pro#7023 feat: implement search functionality in DataGrid v2

* dbeaver/pro#7023 fix: update react-data-grid workspace reference in yarn.lock

* dbeaver/pro#7023 feat: don't focus cell on replace

* dbeaver/pro#7023 feat: add searchReadOnly prop to DataGrid and GridSearchPanel components

* dbeaver/pro#7023 fix: update styles for search panel

* dbeaver/pro#7023 feat: add theme variables for improved styling in storybook

* dbeaver/pro#7023 feat: update search panel styles in datagrid

* dbeaver/pro#7023 feat: improve search functionality by resetting active match index and triggering search on find actions

* dbeaver/pro#7023 refactor: useGridSearch

* dbeaver/pro#7023 fix: search panel styles

* dbeaver/pro#7023 feat: implement grid search state management

* dbeaver/pro#7023 feat: add refresh functionality to search components

* dbeaver/pro#7023 feat: add history action to refresh search results on undo/redo

* dbeaver/pro#7023 feat: add batch cell update functionality to data grid

That functionality comes in handy when we want to use Undo and Replace All functions together:
Before that commit we used to perform N operations where N is a number of replaced cells. And history would record them as atomic operations, so pressing Undo would revert only the last replace.
setMany enables batch editing and batch reverting, so now we can Replace All and then revert it by using one Undo operation

* dbeaver/pro#7023 refactor: remove unnecessary eslint disable comment in useGridSearch

* dbeaver/pro#7023 feat: select affected cell on history change

* dbeaver/pro#7023 feat: add searchReadOnly prop to DataGridTable for conditional read-only state

* dbeaver/pro#7023 refactor: simplify isReadOnly logic in GridSearchPanel

* dbeaver/pro#7023 refactor: use constants for flags

* dbeaver/pro#7023 refactor: update focus and refresh methods in GridSearchPanel

* dbeaver/pro#7023 refactor: consolidate grid search state update logic

* dbeaver/pro#7023 refactor: update scrollToCell method signature and related usages

* dbeaver/pro#7023 refactor: update handleCellChangeBatch to use ICellChange type

* dbeaver/pro#7023 refactor: remove unwanted code

* dbeaver/pro#7023 feat: refactor search functionality to use standalone functions

* dbeaver/pro#7023 feat: implement useDataGridSearch hook to avoid bloating the  DataGrid component with the search-related logic

* dbeaver/pro#7023 feat: update search functionality to use onReplace for cell value updates

* dbeaver/pro#7023 feat: use Activity component for search panel visibility in DataGrid

* dbeaver/pro#7023 refactor: use common value transformation fn in set and setMany methods

* dbeaver/pro#7023 refactor: rename methods for "unnkeyed" cache

* dbeaver/pro#7023 refactor: unify cell edit history handling and remove batch edit references

* dbeaver/pro#7023 fix: update searchReadOnly logic

* dbeaver/pro#7023 refactor: replace rowsCountRef with useState (Error: Cannot access refs during render)

* dbeaver/pro#7023 refactor: expose getCellClassName via useImperativeHandle

Replaced onCellClassNameChange prop + useEffect with useImperativeHandle as suggested in code review. Using useState setter directly as callback ref either caused infinite re-renders (without deps) or lost context synchronization entirely (with stable deps). Settled on useCallback ref that sets both useRef (for method access) and useState (for context propagation).

* dbeaver/pro#7023 fix: Cmd+F not re-focusing the search panel when already open

* dbeaver/pro#7023 refactor:  cell edit history compression

* dbeaver/pro#7023 fix: refresh search state on revert

* dbeaver/pro#7023 refactor: move focus blocking during replace into grid component

Previously DataGridTable checked isReplacing() before syncing
focus/selection, leaking grid internals
Now the grid's selectCell blocks during replacing internally and
supports deferred mode (setTimeout + rAF) for post-render focus sync.

* dbeaver/pro#7023 refactor: restructure grid search logic with useReducer and improve state management

* dbeaver/pro#7023 refactor: improve grid search

moved ref synchronization out of render,
unified search+scroll into one shared path,
added a SET_MATCHES no-op guard to reduce unnecessary updates,
removed duplicate open=false persistence from useDataGridSearch (single owner now in useGridSearch).

* dbeaver/pro#7023 refactor: update search state types

* dbeaver/pro#7023 refactor: restructure grid search state interfaces for improved clarity

* dbeaver/pro#7023 refactor: optimize match retrieval logic after removal in grid search

* dbeaver/pro#7023 refactor: sync refs and computed props in one effect

* dbeaver/pro#7023 fix: Esc double handling between SearchPanel and CaptureView hotkeys

* dbeaver/pro#7023 refactor: fix review comments in useGridSearch

* dbeaver/pro#7023 refactor: unify hotkey stop propagation via shared EventContext/EventStopPropagationFlag

* dbeaver/pro#7023 refactor: pass reactive cellText to useGridSearch

* dbeaver/pro#7023 refactor: update grid search reducer actions and navigation logic

reuse SET_ACTIVE_MATCH and computeActiveMatchIdx for consistency

* dbeaver/pro#7023 refactor: improve equality check for cell matches

* dbeaver/pro#7023 feat: implement cell change tracking subscriptions and cleanup logic

* dbeaver/pro#7023 refactor: remove redundant clearTrackedCellSubscriptions

Cleanup implemented in useTrackedGridSearchCellSubscriptions hook

* dbeaver/pro#7023 fix: cancel stale deferred selectCell to prevent infinite focus loop

When multiple rows are deleted, competing deferred selectCell timeouts
cause focusSyncRef mismatch and endless focus-change cycle.

* dbeaver/pro#7023 fix: update keyboard shortcut for search to use 'KeyF'

* dbeaver/pro#7023 feat: make search disabled by default

* dbeaver/pro#7023 fix: layout

* dbeaver/pro#7023 fix: update scrollBox selection method in Table component

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Viktor Kirst <viktor.kirst@dbeaver.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-03-17 17:28:02 +01:00
2abdbce8ff dbeaver/pro#7566 feat: add settings functionality to the new tree (#4184)
* dbeaver/pro#7566 feat: add settings functionality to the new tree

Implemented settings as a part of Toolbar. The key reason was that the TreeToolbar sets different contexts for all other actions related to tree and easily extendible as a menu service. It might limit in some weird cases, but for now it looks like a valid decision

Settings are made as unknown intentionally to be polymorphic. We could use Record<string, unknown> instead, but I don't see it really improving anything.

* dbeaver/pro#7566 refactor: introduce ITreeSettings interface

add filter with settings connection via useTreeFilterState hook

* dbeaver/pro#7566 refactor: remove redundant hooks, update ITreeSettings

* dbeaver/pro#7566 refactor: move actions to cloudbeaver part

* dbeaver/pro#7566 feat: add universal hook that persists any tree settings via UserDataService as a flat key-value record

* dbeaver/pro#7566 refactor: remove unused childrenTransformers and stateTransformers from ITreeFilterState

* dbeaver/pro#7566 feat: enhance tree settings functionality with replace method and user data synchronization

* dbeaver/pro#7566 refactor: remove toolbarFilter from ITreeFilterState interface

* dbeaver/pro#7566 refactor: simplify useUserTreeSettings

* dbeaver/pro#7566 fix: lint rule and defaults inconsistency

* dbeaver/pro#7566 fix: defaults

* dbeaver/pro#7566 fix: update Russian translations for navigation tree settings

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-03-17 17:21:35 +01:00
Tsimafei Labanovich 051695451b dbeaver/pro#8267 Fix git comments (#4189) 2026-03-17 12:28:21 +00:00
sergeyteleshevandEvgenia 5b7a7fc3d6 [CB] Disable constraint on SQL generation for LOBs (#4215)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-17 12:30:28 +01:00
sergeyteleshev 0ba013e7ad [CB] fix high CVE-2026-32141 in flatted (#4217) 2026-03-17 12:27:11 +01:00
Dmitrii Barnukov 2d2f7476b4 dbeaver/pro#5562 removed auth password from props if not applicable (#4216) 2026-03-17 08:33:01 +01:00
23a0ad49b9 dbeaver/pro#8627 Redesign node extensions (#4212)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-16 19:58:57 +01:00
Dmitrii BarnukovandEvgenia d21a5aac7f dbeaver/pro#8637 enable search inside names by default (#4219)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-16 17:30:02 +01:00
f9cd51db0f [CB] add ability for data grid selection to apply for context menu (#4196)
* dbeaver/pro#8187 Adds right click for the cell menu

* supports multiple pin/unpin columns

* applies selection for the features: duplicate, delete, revert, set to NULL

* fixes pin column for 1 column selected

* renames to multiple pin/unpin columns

* reverts sandwich

* adds multiple add row action

* dbeaver/pro#8601 TE + Git fixes (#4193)

* dbeaver/pro#8601 Fix event handlers on RM server

* dbeaver/pro#8601 Fix RM node icons

* dbeaver/pro#8601 Fix RM project obtain

* dbeaver/pro#8592 support new headers (#4190)

* dbeaver/pro#7657 avoid blicking by debounce loading state (#4174)

* dbeaver/pro#7657 avoid blicking by debounce loading state

* dbeaver/pro#7657 use existing hooks

* dbeaver/pro#7657 keep the old behaviour

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>

* dbeaver/pro#8298 fix: preserve query param order, fix incorrect repla… (#4164)

* dbeaver/pro#8298 fix: preserve query param order, fix incorrect replacement, add tooltip

* dbeaver/pro#8298 refactor: rename ordered prop to sortByName and update sorting logic

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>

* fix: on selected cell menu click opens menu

* fix: duplicates on right click 1 row at time

* removes selection on cell click + removes menu sandwich for selected multiple cells

* value panel, upload, download features available only for 1 selected cell

* adds translations for the multiple rows/columns actions

---------

Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: Alexander Skoblikov <aleksandr.skoblikov@dbeaver.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-15 10:59:32 +01:00
GeorgiiandDiana 3687a645b6 dbeaver/pro#8591 clickhouse driver update (#4195)
Signed-off-by: Georgii Gvinepadze <fireshowel@gmail.com>
Co-authored-by: Diana <31996417+uslss@users.noreply.github.com>
2026-03-13 15:05:34 +01:00
c142996db0 dbeaver/pro#8497 added constraint attribute name (#4206)
* dbeaver/pro#8497 added constraint attribute name

* dbeaver/pro#8497 fix: deleteConstraint call

we can use ! here because we check for currentConstraint in the code above which we get by comparing constraint.attributePosition === attributePosition, so we can be sure the attributePosition prop is present

---------

Co-authored-by: Sychev Andrey <nukemoore@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2026-03-13 14:45:10 +01:00
Dmitrii BarnukovandAinur 36d2b46496 dbeaver/pro#8331 added sql generation for large objects (#4208)
* dbeaver/pro#8331 added sql generation for large objects

* dbeaver/pro#8331 check only relevant columns

---------

Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2026-03-13 12:46:43 +01:00
Ruslan Musaevandmr-anton-t cb8b1cfc52 dbeaver/pro#8467 fix npe read only status (#4183)
* dbeaver/pro#8467 fix npe read only status

* dbeaver/pro#8467 fix navigator model read write lock

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-13 12:13:24 +01:00
743afcaad7 dbeaver/pro#7565 feat: implement drag-and-drop functionality in the n… (#4179)
* dbeaver/pro#7565 feat: implement drag-and-drop functionality in the new tree component

* dbeaver/pro#7565 refactor: optimize node state handling in drag-and-drop functionality

this will avoid additional re-renders in some cases

* dbeaver/pro#7565 refactor: simplify drag-and-drop context handling in useNodeDnD

* dbeaver/pro#7565 fix: add extra check to avoid redundant calls

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
2026-03-12 23:38:15 +01:00
sergeyteleshevandDaria Marutkina 52c78e1aee [CB] Fix high CVE-2025-45141 in tar (#4211)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-12 23:25:16 +01:00
alexandmr-anton-t ff275c36b6 dbeaver/pro#8493 add fetch size tooltip (#4203)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-03-12 15:42:13 +01:00
9a94639167 [CB] Error in the console when opening the admin panel (#4194)
* dbeaver/pro#5495 removes formState hack in ServerConfig

* Revert "dbeaver/pro#5495 removes formState hack in ServerConfig"

This reverts commit 1635e60b38.

* respects formState if it is null

* cleanup

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Viktor Kirst <viktor.kirst@dbeaver.com>
2026-03-11 16:41:17 +01:00
alex 74d09ee4dc dbeaver/pro#8665 always show tabs for the right panel (#4210) 2026-03-11 16:37:31 +01:00
360aaed795 [CB] Bump NPM dependencies (#4186)
* dbeaver/pro#8307 updates yarn lock

* updates eslint

* updates graphql libs

* updates @types/whatwg-mimetype

* updates whatwg-mimetype

* covers evaluate with unit tests

* updates subscript

* removes Rollup CVE

* removes CVE for storybook

* removes CVE for koa

* removes CVE for minimatch

* removes CVE for ajv

* removes redundant types

* cleanup

* updates yarn.lock

* reverts eslint

* updates yarn.lock

* updates yarn lock

* reverts subscript version

* fixes immutable CVE

* fixes serialize-javascript CVE

* updates yarn lock

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Serge Rider <serge@jkiss.org>
2026-03-11 13:27:17 +01:00
912ca3fdcd dbeaver/pro#8394 fix ability to test ssh config for the existing conn… (#4166)
* dbeaver/pro#8394 fix ability to test ssh config for the existing connection

* dbeaver/pro#8394 pass the same config to test tunnel

* dbeaver/pro#8394 fix checking not empty for ssh password

* dbeaver/pro#8394 fix after review

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-11 12:33:04 +01:00
Ruslan MusaevandDaria Marutkina 32bbfc3a37 dbeaver/pro#8149 fix audit logging ip (#4182)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-11 12:32:38 +01:00
1e05ad9eea dbeaver/pro#8223 add new events for messages (#4110)
* dbeaver/pro#8223 add new events for messages

* dbeaver/pro#7138 add error alert variant

* dbeaver/pro#7138 add size for the icon

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-11 12:32:03 +01:00
Serge RiderandEvgenia 4b516c770d dbeaver/pro#8351 Disable devel mode by default (#4136)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-11 12:31:37 +01:00
Serge Rider 4e1fed8177 dbeaver/pro#8626 Refactor QM model init (#4200) 2026-03-10 18:59:22 +01:00
alexandEvgenia 4b30d1073b dbeaver/pro#8355 throttle hover and menu showing (#4176)
* dbeaver/pro#8355 throttle hover and menu showing

* Revert "dbeaver/pro#8355 throttle hover and menu showing"

This reverts commit a88f7aee8b.

* dbeaver/pro#8355 mark hover update as non urgent and non-blocking

* dbeaver/pro#8355 use useHover hook for data grid

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-10 15:48:48 +01:00
269128360d dbeaver/pro#8298 fix: preserve query param order, fix incorrect repla… (#4164)
* dbeaver/pro#8298 fix: preserve query param order, fix incorrect replacement, add tooltip

* dbeaver/pro#8298 refactor: rename ordered prop to sortByName and update sorting logic

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-03-09 17:28:02 +01:00
alexandEvgenia 07db19635e dbeaver/pro#7657 avoid blicking by debounce loading state (#4174)
* dbeaver/pro#7657 avoid blicking by debounce loading state

* dbeaver/pro#7657 use existing hooks

* dbeaver/pro#7657 keep the old behaviour

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-03-09 14:12:16 +01:00
Alexander Skoblikov 41d9ac4296 dbeaver/pro#8592 support new headers (#4190) 2026-03-05 17:57:52 +01:00
Serge Rider a536a93ea3 dbeaver/pro#8601 TE + Git fixes (#4193)
* dbeaver/pro#8601 Fix event handlers on RM server

* dbeaver/pro#8601 Fix RM node icons

* dbeaver/pro#8601 Fix RM project obtain
2026-03-05 17:54:46 +01:00
Alexander Skoblikov aefdca73fa dbeaver/pro#8557 log null host (#4187) 2026-03-04 12:18:26 +01:00
sergeyteleshevandDaria Marutkina 5d402d0c20 Confusing "something went wrong" in case of network issues (#4172)
* dbeaver/pro#8468 adds server is not available network error

* removes localization

* renames method for network state issues

* adds comment

* adds explanations for dev and prod mode errors

* supports multiple browser with network errors

* moves logic to a single method + reuses for events

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-03-03 10:41:04 +01:00
Alexander SkoblikovandSerge Rider fcd1924853 dbeaver/pro#8479 annotation for non executable options (#4170)
Co-authored-by: Serge Rider <serge@jkiss.org>
2026-03-03 09:47:29 +01:00
Malcolm-B-Breaks 05c0aafb53 [CB] Small edit in wording (#4154) 2026-03-02 13:54:14 +01:00
Daria Marutkina 159b1b8bd6 dbeaver/pro#8483 Update README file (#4181)
Updated changelog for version 26.0.0, detailing new features, improvements, and fixes since version 25.3.0.
2026-03-02 11:19:37 +01:00
Tsimafei Labanovich f03c08bde7 dbeaver/pro#8120 Fix kerbeors properrties (#4178) 2026-02-27 11:16:01 +00:00
serge-rider 1b07a66192 dbeaver/pro#8105 Cleanup Http constants 2026-02-27 07:20:39 +01:00
Serge Rider ce1686e0eb dbeaver/pro#8504 Deadlock fix (#4175)
* dbeaver/pro#8504 Deadlock fix

* dbeaver/pro#8504 Deadlock fix
2026-02-26 12:17:58 +01:00
091160a59a dbeaver/pro#8441 fix ssl parameters (#4162)
Co-authored-by: Serge Rider <serge@jkiss.org>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-25 16:12:00 +01:00
dbeaver-devops 47d2377b39 dbeaver/pro#8483 26.0.1 version bump 2026-02-24 12:27:49 +01:00
Dmitrii BarnukovandEvgenia 8a3f6455ec dbeaver/pro#8190 secured values masking (#4143)
* dbeaver/pro#8190 secured values masking

* dbeaver/pro#6688 added idp values masking

* dbeaver/pro#8190 fix masking for saved params

* dbeaver/pro#8190 revert showProtected, review improvements

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-23 18:28:56 +01:00
801139e3ff dbeaver/pro#8068 [CB] Fix: form reload on save and AI settings language (#4140)
* dbeaver/pro#8068 fix: FormPart reload after save: race condition between loaded=false and savingPromise

Problem:
FormPart.save() sets loaded=false after successful save, expecting
useAutoLoad to call load() and reload data from server. However,
load() is blocked by formState.savingPromise guard (added recently in dbeaver/pro#8151, 9c406a4e1e),
which is only cleared later in FormState.save() finally block.
By the time savingPromise is cleared, no observable changes trigger
a React re-render, so useAutoLoad never retries — loaded stays false
forever and isChanged always returns false, permanently disabling
the Save button.

Solution:
Make FormPart.save() self-contained by adding an inline post-save
reload. After saveChanges(), set loaded=false (so setInitialState
resets both initialState and state), call loader() directly, then
set loaded=true. This eliminates the dependency on the external
useAutoLoad mechanism for post-save reload, avoiding the race
condition with savingPromise entirely.

* dbeaver/pro#8068 fix(Combobox): preserve custom value on Enter key press

When allowCustomValue is enabled, pressing Enter was selecting
an auto-highlighted item from the dropdown instead of keeping
the user's typed value. Disable autoSelect and close the popover
via Ariakit store on Enter to match the blur behavior.

* dbeaver/pro#8068 fix: after saveChanges(), reset state synchronously via setInitialState

* dbeaver/pro#8068 fix: remove redundant state loading in FormPart class

* dbeaver/pro#8068 feat: improve loadable state handling in useAutoLoad and FormPart

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-23 17:18:50 +01:00
457909dd82 dbeaver/pro#8423 patch resolutions (#4157)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-23 17:18:16 +01:00
Sychev AndreyandEvgenia 9f7e086369 dbeaver/pro#8279 feat: rename CloudBeaver CE to CloudBeaver Community… (#4153)
* dbeaver/pro#8279 feat: rename CloudBeaver CE to CloudBeaver Community across multiple files

* dbeaver/pro#8279 ci: remove unused dependency

* dbeaver/pro#8279 feat: remove unnecessary description fields in plugin.xml and defaultServerConfig.ts

* dbeaver/pro#8279 feat: update copyright year and remove product description fields in multiple locale files

* dbeaver/pro#8279 feat: return product version back to Logo component

* dbeaver/pro#8279 fix: remove exclamation mark from easy config heading

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-23 17:17:31 +01:00
Serge Rider 6e8a09a49e dbeaver/pro#8462 session npe (#4161)
* dbeaver/pro#8462 Persistent session refactoring. Nullability annotations.

* dbeaver/pro#8462 Synchronize active projects
2026-02-23 17:16:42 +01:00
alexandDaria Marutkina 8d11f94ad6 dbeaver/pro#8372 add claude code snippet (#4156)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-23 13:51:57 +01:00
Dmitrii BarnukovandEvgenia 3b0cd66232 dbeaver/pro#8444 bring back error prefix (#4160)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-23 12:37:07 +01:00
alexandEvgenia a60b7d4496 dbeaver/pro#8028 use UsersResourceNewUsers alias (#4150)
* dbeaver/pro#8028 use UsersResourceNewUsers alias

* dbeaver/pro#8028 use executor to add update pagination

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-20 15:40:34 +01:00
Ruslan MusaevandEvgenia 07a3a069fb dbeaver/pro#8281 check inner database password (#4155)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-20 12:32:01 +01:00
mr-anton-t 4511208cad dbeaver/pro#8413 fix fr locale (#4152) 2026-02-20 12:29:34 +01:00
1ab8d15b00 dbeaver/pro#8082 send query parameters as list fix (#4149)
* dbeaver/pro#8082 send query parameters as list fix

* dbeaver/pro#8082 fix: change query parameters handling in QueryDataSource

---------

Co-authored-by: Sychev Andrey <nukemoore@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-20 11:20:11 +01:00
alexandEvgenia ae331effde dbeaver/pro#3822 make selection accountable (#4144)
* dbeaver/pro#3822 make selection accountable

* dbeaver/pro#3822 pass cursor to get queries in range

* dbeaver/pro#3822 keep range queries clamped in selection

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-19 21:58:11 +01:00
alexandkseniaguzeeva 6c88030106 dbeaver/pro#8382 pass originalQuery to gql (#4151)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-02-19 12:19:30 +01:00
alexandDaria Marutkina 5e53341ba1 dbeaver/pro#8317 show all users by default (#4142)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-19 10:04:04 +01:00
Dmitrii BarnukovandDaria Marutkina 9338539564 dbeaver/pro#8023 added nullable exception message handling (#4146)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-19 10:03:33 +01:00
Ruslan MusaevandDaria Marutkina af1aa83d8b dbeaver/pro#7628 fix session refresh on server config update (#4126)
* dbeaver/pro#7628 fix session refresh on server config update

* dbeaver/pro#7628 fix session refresh on server config update

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-19 06:06:21 +01:00
AinurandEvgenia 32671ff239 dbeaver/pro#8196 validate projects on copying connection (#4141)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-19 06:03:50 +01:00
AinurandDaria Marutkina f7c33a7f7e dbeaver/pro#8283 execute get request for gql endpoint in devel mode (#4145)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-18 16:14:01 +01:00
alexandEvgenia d70e4976e5 dbeaver/pro#7511 move readonly loaders to the component (#4137)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-18 12:29:24 +01:00
a0e6154a08 [CB] The generated Custom SSL certificate domain is not displayed (#4127)
* dbeaver/pro#8305 removes serverUrl

* removes unused helper

* adds getCurrentDomain helper

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-02-18 12:29:03 +01:00
Daria Marutkina 1b6e729506 dbeaver/pro#8330 Update README file (#4148)
Added changelog entries for version 25.3.5, detailing new features and improvements in Data Editor, Navigator, and General sections.
2026-02-18 10:47:31 +01:00
Serge Rider 3adb898f5a dbeaver/pro#8365 Cert storage init fix 2026-02-17 11:50:31 +01:00
c886ee7bcd dbeaver/pro#8078 fix: remove translate property from menu transition (#4125)
Initial translation of a menu led to a unwanted hover effect on a first item due to menu and trigger overlapping. Removing this property fixes the problem and the menu still has a nice animation effect, thanks to opacity and scale

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-16 12:54:32 +01:00
serge-rider a5a9700ab9 dbeaver/pro#7928 Code style, constants 2026-02-15 09:21:36 +01:00
2c38e75a99 dbeaver/pro#6761 listen to localhost with host network mode (#4003)
* dbeaver/pro#6761 listen to localhost with host network mode

* dbeaver/pro#6761 merge

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Alexander Skoblikov <aleksandr.skoblikov@dbeaver.com>
2026-02-13 12:46:05 +01:00
Dmitrii BarnukovandEvgenia 12bc5ba5dc dbeaver/pro#8325 fixed oracle generated selects (#4130)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-11 18:19:23 +01:00
sergeyteleshev 2719f53655 [CB] SQL Query generation: front-end part (#4132)
* dbeaver/pro#8048 pass commit message validation

* fixes json + translations

* fixes gis generation

* uses format action to get text value for sql generators
2026-02-11 18:16:38 +01:00
dbeaver-devops a2d336166b dbeaver/pro#8330 26.0.0 version bump 2026-02-11 12:17:47 +01:00
sergeyteleshevandmr-anton-t 06da0705d2 dbeaver/pro#8303 fixes axios CVE (#4123)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-11 11:14:47 +01:00
058a341815 dbeaver/pro#8197 add connection preferences plugin (#4111)
* dbeaver/pro#8197 add connection preferences plugin

* dbeaver/pro#8197 add preferences form

* dbeaver/pro#8197 wrap to observable

* dbeaver/pro#8197 use config from core package

* dbeaver/pro#8197 adjust naming

* dbeaver/pro#8197 add separate resource for preferences info

* dbeaver/pro#8197 change action naming

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-10 17:16:48 +01:00
Dmitrii BarnukovandDiana f5b0aae8bd dbeaver/pro#8047 exposed resultset SQL generators to CB (#4116)
* dbeaver/pro#8047 exposed resultset SQL generators to CB

* dbeaver/pro#8047 minor fix

* dbeaver/pro#7998 added sql generator tests

* dbeaver/pro#8047 moved data provider

* dbeaver/pro#8047 fixed imports after move

* dbeaver/pro#8047 extracted methods to DBDResultSetModel

---------

Co-authored-by: Diana <31996417+uslss@users.noreply.github.com>
2026-02-10 16:21:17 +01:00
Ainurandkseniaguzeeva 789760aa9e dbeaver/pro#8181 fix saving data source config on init (#4121)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-02-10 16:13:56 +01:00
Tsimafei LabanovichandMashaKorax fe77133226 dbeaver/pro#8119 Add CLI commands for project management (create, rename, delete, list) (#4106)
* dbeaver/pro#8119 Add CLI commands for project management (create, rename, delete, list)

* dbeaver/pro#8119 Fix git issues

---------

Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2026-02-10 16:07:06 +01:00
AinurandDaria Marutkina 8e237d1bbc dbeaver/pro#8142 remove public url param (#4119)
* dbeaver/pro#8142 remove public url param

* dbeaver/pro#8142 remove server url in all products

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-10 16:04:22 +01:00
0f3b8033df [CB] Ability to clone connections (#4107)
* dbeaver/pro#6998 adds clone connection in context menu

* adds copy to cloned connection

* dbeaver/pro#6998 fix copy navigator settings

* preload connections names to create correct name for cloned node

* pr fixes: adds translation + unique name among all connections

* adds "копия" locale

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
2026-02-10 12:16:56 +01:00
586d4154ab dbeaver/pro#4999 Data editor icons to see pk details (#4040)
* dbeaver/pro#4999 feat: add TableStatusIndicator component and update tooltips for key detection

* dbeaver/pro#4999 feat: update TableStatusIndicator to handle read-only connection state and improve tooltips

* dbeaver/pro#4999 feat: use new backend endpoint to provide info about identifier

* dbeaver/pro#4999 refactor: simplify TableStatusIndicator by removing props and using context directly

* dbeaver/pro#4999 refactor: move hasElementIdentifier to ResultSetDataSource

* pr fixes

* fixes indicator center position + removes duplicated translation

* fixes contrast for color-status (JSON edit row + keys indicator for tables)

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-10 09:41:39 +01:00
e0fe55c3bf dbeaver/pro#6306 api for original query (#4108)
* dbeaver/pro#6306 api for original query

* uses original query from backend for sql data source

* adds description for popup

* removes message

* fixes creating dataset from grouping panel

* adds new full query abstraction

* pr fixes

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-02-10 09:40:55 +01:00
9c406a4e1e dbeaver/pro#8151 fix: reset password and key fields on change detection (#4102)
* dbeaver/pro#8151 fix: reset password and key fields on change detection

* fixes ssh save form part flow

* removes race condition

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-09 12:12:37 +01:00
alexandDaria Marutkina 05bf79e59c dbeaver/pro#8218 add doc locale (#4113)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-09 12:11:03 +01:00
sergeyteleshevandEvgenia d013ece4f6 dbeaver/pro#8262 fixes CVE for @isaacs/brace-expansion (#4115)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-09 12:10:18 +01:00
Ruslan Musaev b0205e34c7 dbeaver/cloudbeaver#3833 remove mandatory backup for database community (#4118) 2026-02-06 16:29:46 +01:00
Ruslan MusaevandEvgenia 0b41ae89e8 dbeaver/pro#8211 update duck db version (#4114)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-05 15:30:14 +01:00
Ruslan Musaev b318c262b9 dbeaver/pro#7914 jetty server websocket version bump (#4079) 2026-02-05 11:55:08 +00:00
alex 547831d060 dbeaver/pro#8214 change naming (#4112) 2026-02-05 09:45:06 +01:00
sergeyteleshevandmr-anton-t 93af9a8719 [CB] Decrize the size of radio buttons (#4109)
* dbeaver/pro#8202 decreases border for radio buttons + matches border colors

* fixes checkboxes and radios space conflicts so it aligned on the left and has correct spacing with text also

* reverts margin top for checkboxes

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-04 16:25:34 +01:00
Alexander Skoblikov 19125fed5e dbeaver/pro#7888 interface for cli context (#4100) 2026-02-04 10:02:43 +00:00
sergeyteleshevandDaria Marutkina ea5fb47a56 [CB] remove shortcuts intersection for layouts (#4103)
* dbeaver/pro#8104 removes shortcuts intersection

* removes check of an active element (handled by the lib)

* build fix

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-02-03 17:30:59 +01:00
alexandEvgenia 89b4d1f970 dbeaver/pro#7357 use the same saved value indicator across the app (#4099)
* dbeaver/pro#7357 use the same saved value indicator across the app

* dbeaver/pro#7357 check id first

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-02-03 17:26:06 +01:00
Daria Marutkina 0bd89236d2 dbeaver/pro#8155 Update README file (#4105)
Added changelog for version 25.3.4, detailing new features and fixes in Editors, Navigator, and General sections.
2026-02-02 11:36:44 +01:00
Dmitrii Barnukovandmr-anton-t abe0f6fb7d dbeaver/pro#7975 removed vks from api and logic (#4098)
* dbeaver/pro#7975 removed vks from api and logic

* dbeaver/pro#7975 revert api changes

* dbeaver/pro#7975 revert api changes

* dbeaver/pro#7975 revert api changes

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-02-02 11:01:03 +01:00
sergeyteleshevandEvgenia 0b2b851772 dbeaver/pro#8156 filters menu bar with hidden actions (#4093)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-01-30 23:05:08 +08:00
alexandmr-anton-t dcb8e536b8 dbeaver/pro#8067 add tooltip (#4101)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-01-30 23:04:45 +08:00
sergeyteleshev 7881551821 dbeaver/pro#8175 removes tar and lodash CVEs (#4095) 2026-01-29 12:15:39 +01:00
alexandDaria Marutkina cdc94dce5f dbeaver/pro#8089 validate date before formatting (#4091)
* dbeaver/pro#8089 validate date before formatting

* dbeaver/pro#8089 treat date and time the same

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-01-28 12:30:09 +01:00
dbeaver-devops 70b67ce6f4 dbeaver/pro#8155 25.3.5 version bump 2026-01-27 19:55:11 +01:00
743 changed files with 13123 additions and 7536 deletions
+93 -51
View File
@@ -25,61 +25,103 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 25.3.3 2026-01-19
### 26.0.2 2026-04-06
- For security reasons, session expiration leads to a complete logout for a user and clears all data from the screen;
- Added "Use local formatting" setting to Data Editor. Users can choose how to display numbers and dates: using the OS locale, a custom locale, or keeping values unformatted. This formatting applies only to displayed values. Data in the database remains unchanged;
- Added the shortcut Ctrl/Cmd + . to cancel operations in Data Editor;
- CloudBeaver now supports long polling as a fallback when WebSockets are unavailable or blocked, ensuring reliable communication for metadata updates and SQL execution;
- Fixed a keyboard navigation issue for panels to keep the focus inside;
- Clickhouse: Spatial data support was added.
- UI improvements:
- Marked icons for non-saved (temporary) scripts with the hourglass symbol to add visual distinction from the saved ones.
- Added the tooltip about quoting rules for the Bind variables parameters dialog.
- Improved keyboard navigation for context menu and buttons for Data Editor, SQL Editor, and Navigator tree.
- Authentication:
- Fixed the bug with the authentication form. Now the same password is required for the password change operation.
- Fixed the display of authentication fields for SQL Server with Active Directory - MSI. The unnecessary Password field was removed.
- Databases and drivers:
- DuckDB: Fixed geometry type display for database versions > 1.5. (thanks @EastLord).
- H2 database:
- Added new embedded driver version 2.4.2.
- Updated server driver to the version 2.4.2.
- Security:
- Fixed the high vulnerability (CVE-2026-33671) in the picomatch library. The library was updated to version 2.3.2.
- Fixed the high vulnerability (CVE-2026-33943) in the happy-dom library. The library was updated to version 20.8.8.
- Fixed the high vulnerability (CVE-2026-33228) in the flatted library. The library was updated to version 3.4.2.
### 25.3.2 2026-01-05
### 26.0.1 2026-03-23
- The ability to pin columns has been added to the Data Editor, allowing users to keep key columns (e.g., IDs, names) visible while scrolling through wide tables;
- Added a new setting in the SQL Editor to highlight spaces, tabs, and other whitespace characters to help users read, debug, and maintain their scripts. It is turned off by default and can be configured in personal preferences;
- Redesigned the connection configuration page. Form fields and sections have been reorganized to provide more space for input and reduce visual clutter;
- Clickhouse: driver has been updated to version 0.9.5;
- Oracle: Added a new "Set Username to OS_USER" option in the Misc section of Oracle connection settings. When turned on, the tool automatically uses the current database username as the operating system user identifier in the session metadata.
### 25.3.1 2025-12-22
- Implemented an ability to add parameters and variables to queries in the SQL Editor. This feature allows for the reuse of queries by changing parameters at the execution stage. It is enabled by default and can be configured in personal preferences;
- Added support for mapping users to CloudBeaver teams based on LDAP memberOf group membership;
- Added support for previous browser versions, but not older than three years.
### 25.3.0 2025-12-01
### Changes since 25.2.0:
- Administration:
- Permission configuration for connections has been simplified, providing a clearer appearance and improved performance;
- LDAP authentication now supports case-insensitive Distinguished Names (DNs), allowing login with mixed-case DNS.
- Security:
- Fixed a security issue in the Spatial Viewer for the HTML content to prevent a third-party script execution;
- Fixed the critical vulnerability (CVE-2025-61927) in the Happy DOM library. The library was updated to version 20.0.2.
- SQL Editor:
- A confirmation prompt has been added to the SQL Editor to prevent accidental execution of potentially dangerous queries. You can customize this behavior in Preferences > SQL Editor;
- A search field has been added to the catalog/schema selector on the main toolbar for easy navigation;
- Alphabetical order has been added to the autocomplete values in the SQL Editor to simplify object search;
- Added support for temporary tables in semantic analysis.
- Data Editor:
- The ability to review the database query after applying filters and sorting in the Data Editor, Grouping panel, and Result sets has been added. The new Show SQL button is placed on the Filter panel;
- The value panel in the Data Editor has been redesigned to give users more space for viewing and editing data. Tab buttons for different data type visualizations are now aligned on a single level, creating a cleaner layout;
- Added visualization for column reordering in the Data Editor;
- Fixed an issue in the Data Editor where resizing one column in a wide table caused previously resized columns to revert to their original width. Users can resize multiple columns as intended.
- Added the Find and Replace functionality for the Data Editor with the ability to find data by matching case, whole word, or using regular expressions.
- Added support for adding, duplicating, deleting, or setting values to NULL in multiple highlighted rows via the Data Editor context menu.
- SQL query generation in the Data Editor is now available for columns containing large object data.
- Improved column drag-and-drop behavior in the Data Editor. Users could drag columns from any area within the header and drop them onto any part of the target column or table body.
- SQL Editor:
- Added fuzzy search to SQL Editor auto-completion for object names. The feature allowed users to find tables and containers even with typos or incorrect letter ordering by matching strings that start with, contain, or resemble the input.
- Fixed data editing for SQL scripts with variables.
- General:
- A reorder tabs option has been added to the application, allowing users to set editor tabs in custom positions;
- Keyboard navigation has been improved across the application. Additional shortcuts are available in the main info panel (top-right corner) to help discover them faster. Relevant keyboard shortcuts are also shown in the tooltips of Data Editor buttons;
- Boolean values have been replaced with intuitive checkboxes instead of the true/false text in the metadata editor, making editing easier and faster;
- The database navigator now automatically hides empty subfolders in shared projects, reducing visual clutter;
- Databases and drivers:
- ClickHouse: query canceling support has been added;
- DuckDB: fixed an issue where catalog selection was lost upon disconnection;
- PostgreSQL: fixed support for GIS data types;
- SQL Server: the driver has been updated to version 13.2.1;
- The "Show all databases" setting has been added on the connection page for MySQL and MariaDB.
## Contribution
As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests.
- Added a close button to the right tab when it is the only open tab (ex. Scripts). Users no longer needed to use the main menu to close a single tab.
- Replaced the generic "Something went wrong" message with a specific network error notification when the application cannot connect to the server.
- Null or empty values can now be assigned to the Driver properties for all drivers.
- Fixed an issue where re-selecting an existing SSH key file during connection edits caused problems.
- Fixed the ability to switch tabs in the Data editor using the keyboard.
- Fixed error notification spelling. Thanks to @Malcolm-B-Breaks.
- ClickHouse:
- Updated driver version to 0.9.7.
- Fixed IP address display in the Data Editor by removing the leading slash.
- PostgreSQL: Fixed an error when connecting via URL if the database name contains a hyphen.
- Security:
- Fixed the high vulnerability (CVE-2025-45141) in the tar library. The library was updated to version 7.5.11.
- Fixed the high vulnerability (CVE-2026-27148) in the storybook library. The library was updated to version 10.2.10.
- Fixed the high vulnerability (CVE-2026-27606) in the rollup library. The library was updated to version 4.59.0.
- Fixed the high vulnerability (CVE-2026-27904) in the minimatch library. The library was updated to version 3.1.3.
- Fixed the high vulnerability (CVE-2026-27959) in the koa library. The library was updated to version 2.16.4.
- Fixed the high vulnerability (CVE-2026-32141) in the flatted library. The library was updated to version 4.4.0.
### 26.0.0 2026-03-02
### Changes since 25.3.0:
- Security:
- Enforced complete logout and screen data clearance upon session expiration;
- Fixed the high vulnerability (CVE-2026-25639) in the axios library. The library was updated to version 1.13.5.
- Administration:
- Added support for mapping users to CloudBeaver teams based on LDAP memberOf group membership;
- Change the User list settings in the Administration part to show both active and inactive users by default.
- SQL Editor:
- Added support for parameters and variables in queries. This feature allows queries to be reused by changing parameters at execution time. Enabled by default and configurable in personal preferences;
- Added SQL preview to the Bind parameters/variables dialog to review queries with changed values on the fly;
- Enabled Tab key for autocompletion in the SQL Editor alongside the Enter key;
- Added a new setting in the SQL Editor to highlight spaces, tabs, and other whitespace characters to help users read, debug, and maintain their scripts. It is turned off by default and can be configured in personal preferences;
- Dangerous query confirmation is now shown for all DROP statements, not just for tables.
- Data Editor:
- Added ability to automatically generate INSERT, SELECT, DELETE, and UPDATE statements for the selected values;
- Added undo and redo functionality for cell edits, row operations, and other data modifications. Retains the last 50 actions across the Data Editor, result sets, and related panels;
- Added "Use local formatting" setting. Users can choose how to display numbers and dates: using the OS locale, a custom locale, or keeping values unformatted. This formatting applies only to displayed values. Data in the database remains unchanged;
- Added column pinning to keep key columns (e.g., IDs, names) visible while horizontal scrolling through wide tables;
- Added status indicator icon in the top-left corner with tooltips explaining table editability. Indicates presence of primary keys, read-only connection settings, or read-only columns;
- Added shortcut Ctrl/Cmd + . to cancel operations in Data Editor;
- Fixed application freeze in canceling fetch size requests for large tables.
- Navigator tree:
- Added the ability to duplicate connection configuration in the project navigation tree. The "Clone connection" feature is available in the context menu;
- Added the ability for users to configure the Simple or Advanced view in the Navigation tree for all connections or for each connection separately;
- Added the ability to show table objects, such as columns or keys, in the Navigation tree. The setting is disabled by default and can be turned on in the Navigator settings panel;
- Added the ability to rename connections via context menu in the Navigation Tree;
- Added Connection Info tab to display basic information about the current connection for all users.
- General:
- Added support for long polling as a fallback when WebSockets are unavailable or blocked. Ensures reliable communication for metadata updates and SQL execution;
- Extended browser support to versions up to three years old;
- Redesigned the connection configuration page. Reorganized form fields and sections to provide more input space and reduce visual clutter;
- Expanded pointer target areas for icons in the Navigator, editors, and tabs according to the accessibility standards;
- Fixed a keyboard navigation issue for panels to keep the focus inside;
- Renamed "Database Native" authentication type to "Username/password" in the connection dialog;
- Fixed issue where the missing pg_dump utility caused errors during initialization or deployment updates when PostgreSQL was selected as the inner database.
- Databases:
- ClickHouse:
- Updated driver to version 0.9.5
- Added spatial data support
- Fixed an issue with displaying arrays of UUID, IPv4/IPv6, and Map types
- Fixed JSON data type reading
- Resolved an issue with CTE expressions
- DuckDB:
- Updated driver to version 1.4.4.0;
- Added support for the dollar-quoted string syntax for the SQL Editor
- Oracle: Added a new "Set Username to OS_USER" option in the Misc section of Oracle connection settings. Automatically uses the current database username as the operating system user identifier in session metadata when enabled;
- PostgreSQL: Added DDL display support for PostgreSQL policies.
[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint.
+3 -3
View File
@@ -13,7 +13,7 @@ if [ "$(id -u)" -eq 0 ]; then
chown -R $DBEAVER_UID:$DBEAVER_GID $PWD/workspace
# Execute run-cloudbeaver-server.sh as the dbeaver user with the JAVA_HOME and PATH environment variables
exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-cloudbeaver-server.sh"
exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-cloudbeaver-server.sh $*"
else
exec ./run-cloudbeaver-server.sh
fi
exec ./run-cloudbeaver-server.sh "$@"
fi
+1 -1
View File
@@ -35,7 +35,7 @@ exec java ${JAVA_OPTS} \
--add-opens=java.sql/java.sql=ALL-UNNAMED \
-jar ${launcherJar} \
-product io.cloudbeaver.product.ce.product \
-web-config conf/cloudbeaver.conf \
"$@" \
-web-config conf/cloudbeaver.conf \
-nl en \
-registryMultiLanguage
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model.cli;singleton:=true
Bundle-Version: 1.0.3.qualifier
Bundle-Release-Date: 20260202
Bundle-Version: 1.0.8.qualifier
Bundle-Release-Date: 20260420
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.cli;visibility:=reexport
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model.cli</artifactId>
<version>1.0.3-SNAPSHOT</version>
<version>1.0.8-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,39 +18,33 @@ package io.cloudbeaver.model.cli;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.cli.*;
import org.jkiss.dbeaver.model.cli.model.CommandLineAuthenticator;
import org.jkiss.dbeaver.model.cli.ApplicationCommandLine;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.cli.CLIContextImpl;
import org.jkiss.dbeaver.model.cli.CLIRunMeta;
import picocli.CommandLine;
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
@Nullable
private final CommandLineAuthenticator authenticator;
private final CloudBeaverMixin mixin;
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
public CloudBeaverCommandLine() {
super();
this.authenticator = authenticator;
this.mixin = new CloudBeaverMixin();
}
public CloudBeaverCommandLine(
@Nullable CommandLineAuthenticator authenticator,
@NotNull CloudBeaverMixin mixin
) {
super();
this.authenticator = authenticator;
this.mixin = mixin;
}
@Override
protected CloudBeaverTopLevelCommand createTopLevelCommand(
@Nullable ApplicationInstanceController applicationInstanceController,
@NotNull CommandLineContext context,
@NotNull CLIContextImpl context,
@NotNull CLIRunMeta runMeta
) {
if (authenticator != null) {
context.setContextParameter(CLIConstants.CONTEXT_PARAM_AUTHENTICATOR, authenticator);
}
return new CloudBeaverTopLevelCommand(applicationInstanceController, context, runMeta);
}
@@ -58,7 +52,7 @@ public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationIn
@Override
protected CommandLine initCommandLine(
@Nullable ApplicationInstanceController applicationInstanceController,
@NotNull CommandLineContext context,
@NotNull CLIContextImpl context,
@NotNull CLIRunMeta runMeta
) {
var cmd = super.initCommandLine(applicationInstanceController, context, runMeta);
@@ -29,7 +29,7 @@ public class CloudBeaverInstanceServer extends ApplicationInstanceServer<Applica
public CloudBeaverInstanceServer() throws IOException {
super(ApplicationInstanceController.class);
this.commandLine = new CloudBeaverCommandLine(null);
this.commandLine = new CloudBeaverCommandLine();
}
public CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +16,14 @@
*/
package io.cloudbeaver.model.cli;
import org.jkiss.dbeaver.model.cli.model.NonExecutableOption;
import picocli.CommandLine;
/**
* CloudBeaver specific mixin options
*/
public class CloudBeaverMixin {
@NonExecutableOption
@CommandLine.Option(
names = {"-web-config"},
arity = "1",
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +19,15 @@ package io.cloudbeaver.model.cli;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
import org.jkiss.dbeaver.model.cli.CLIContextImpl;
import org.jkiss.dbeaver.model.cli.CLIRunMeta;
import org.jkiss.dbeaver.model.cli.CommandLineContext;
import org.jkiss.dbeaver.model.cli.command.AbstractTopLevelCommand;
import org.jkiss.dbeaver.model.cli.model.NonExecutableOption;
import picocli.CommandLine;
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands")
public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
@NonExecutableOption
@CommandLine.Option(
names = {NOSPASH_OPTION},
hidden = true,
@@ -35,7 +37,7 @@ public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
protected CloudBeaverTopLevelCommand(
@Nullable ApplicationInstanceController controller,
@NotNull CommandLineContext context,
@NotNull CLIContextImpl context,
@NotNull CLIRunMeta meta
) {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.94.qualifier
Bundle-Release-Date: 20260202
Bundle-Version: 1.0.99.qualifier
Bundle-Release-Date: 20260420
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
@@ -46,6 +46,8 @@
class="io.cloudbeaver.websocket.event.client.WSSessionTaskConfirmationEvent"/>
<clientEvent id="cb_client_session_task_with_parameters_confirmation"
class="io.cloudbeaver.websocket.event.client.WSSessionTaskWithParametersConfirmationEvent"/>
<clientEvent id="cb_client_cancel_action"
class="io.cloudbeaver.websocket.event.client.WSSessionCancelActionClientEvent"/>
</extension>
</plugin>
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.94-SNAPSHOT</version>
<version>1.0.99-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -146,6 +146,8 @@ public class DBWebException extends DBException implements GraphQLError {
} else {
return message;
}
} else if (cause.getMessage() == null && cause.getCause() != null) {
return message + ":\n" + cause.getCause().getClass().getSimpleName();
}
if (CommonUtils.isEmpty(message)) {
if (cause.getMessage() != null) {
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourcePreferenceStore;
import org.jkiss.utils.CommonUtils;
public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C extends DataSourceDescriptor> {
@@ -93,6 +94,9 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
input.isSharedCredentials()
);
dataSource.setConnectionReadOnly(input.isReadOnly());
DataSourcePreferenceStore preferenceStore = dataSource.getPreferenceStore();
preferenceStore.clear();
preferenceStore.setProperties(input.getDefaultUserPreferences());
}
@NotNull
@@ -218,6 +218,7 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
*
* @return connections from cache.
*/
@NotNull
public List<WebConnectionInfo> getConnections() {
if (!registryIsLoaded) {
addDataSourcesToCache();
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.meta.Property;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -64,6 +65,8 @@ public class WebConnectionConfig {
private Boolean defaultAutoCommit;
private String defaultCatalogName;
private String defaultSchemaName;
@NotNull
private Map<String, String> defaultUserPreferences = new LinkedHashMap<>();
public WebConnectionConfig() {
}
@@ -111,6 +114,8 @@ public class WebConnectionConfig {
String configType = JSONUtils.getString(params, "configurationType");
configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType);
Map<String, Object> stringObjectUserPrefMap = JSONUtils.getObject(params, "defaultUserPreferences");
stringObjectUserPrefMap.forEach((key, value) -> defaultUserPreferences.put(key, value.toString()));
networkHandlersConfig = new ArrayList<>();
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
@@ -367,4 +372,13 @@ public class WebConnectionConfig {
public void setDefaultSchemaName(String defaultSchemaName) {
this.defaultSchemaName = defaultSchemaName;
}
@NotNull
public Map<String, String> getDefaultUserPreferences() {
return defaultUserPreferences;
}
public void setDefaultUserPreferences(@NotNull Map<String, String> defaultUserPreferences) {
this.defaultUserPreferences = defaultUserPreferences;
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.app.BaseWebAppConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.service.sql.WebDataFormat;
import io.cloudbeaver.utils.CBModelConstants;
@@ -39,11 +40,13 @@ import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNative;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
import org.jkiss.dbeaver.model.rm.RMConstants;
import org.jkiss.dbeaver.model.rm.RMProjectPermission;
import org.jkiss.dbeaver.model.runtime.DBRRunnableParametrized;
import org.jkiss.dbeaver.registry.DataSourcePreferenceStore;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -59,7 +62,6 @@ import java.util.stream.Collectors;
public class WebConnectionInfo {
private static final Log log = Log.getLog(WebConnectionInfo.class);
public static final String SECURED_VALUE = "********";
private static final String FEATURE_HAS_TOOLS = "hasTools";
private static final String FEATURE_CONNECTED = "connected";
@@ -127,7 +129,7 @@ public class WebConnectionInfo {
if (canViewReadOnlyConnections()) {
return dataSourceContainer.getConnectionConfiguration().getHostName();
}
return SECURED_VALUE;
return CBConstants.SECURED_VALUE;
}
@Property
@@ -135,7 +137,7 @@ public class WebConnectionInfo {
if (canViewReadOnlyConnections()) {
return dataSourceContainer.getConnectionConfiguration().getHostPort();
}
return SECURED_VALUE;
return CBConstants.SECURED_VALUE;
}
@Property
@@ -148,7 +150,7 @@ public class WebConnectionInfo {
if (canViewReadOnlyConnections()) {
return dataSourceContainer.getConnectionConfiguration().getDatabaseName();
}
return SECURED_VALUE;
return CBConstants.SECURED_VALUE;
}
@Property
@@ -156,7 +158,7 @@ public class WebConnectionInfo {
if (canViewReadOnlyConnections()) {
return dataSourceContainer.getConnectionConfiguration().getUrl();
}
return SECURED_VALUE;
return CBConstants.SECURED_VALUE;
}
@Property
@@ -293,6 +295,16 @@ public class WebConnectionInfo {
return dataSourceContainer.getNavigatorSettings().getOriginalSettings();
}
@Property
@NotNull
public Map<String, String> getDefaultUserPreferences() {
DBPPreferenceStore preferenceStore = dataSourceContainer.getPreferenceStore();
if (preferenceStore instanceof DataSourcePreferenceStore dataSourcePreferenceStore) {
return dataSourcePreferenceStore.getProperties();
}
return Collections.emptyMap();
}
@Property
@NotNull
public List<WebDataFormat> getSupportedDataFormats() {
@@ -578,4 +590,5 @@ public class WebConnectionInfo {
public void setCredentialsSavedInSession(@Nullable Boolean credentialsSavedInSession) {
this.credentialsSavedInSession = credentialsSavedInSession;
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.model;
import io.cloudbeaver.server.CBConstants;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.net.DBWHandlerType;
@@ -88,7 +89,9 @@ public class WebNetworkHandlerConfig {
DBPPropertyDescriptor[] descriptor = configuration.getHandlerDescriptor().getHandlerProperties();
for (DBPPropertyDescriptor p : descriptor) {
if (p.hasFeature(RegistryConstants.ATTR_PASSWORD)) {
secureProperties.computeIfPresent(p.getId(), (k, v) -> CommonUtils.isEmpty(v) ? null : WebConnectionInfo.SECURED_VALUE);
secureProperties.computeIfPresent(p.getId(), (k, v) ->
CommonUtils.isEmpty(v) ? null : CBConstants.SECURED_VALUE
);
}
}
return secureProperties;
@@ -16,6 +16,8 @@
*/
package io.cloudbeaver.model;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.net.DBWHandlerType;
import org.jkiss.utils.CommonUtils;
@@ -28,12 +30,14 @@ import java.util.Map;
*/
public class WebNetworkHandlerConfigInput {
@NotNull
private final Map<String, Object> cfg;
public WebNetworkHandlerConfigInput(Map<String, Object> cfg) {
public WebNetworkHandlerConfigInput(@NotNull Map<String, Object> cfg) {
this.cfg = cfg;
}
@Nullable
public DBWHandlerType getType() {
return CommonUtils.valueOf(DBWHandlerType.class, JSONUtils.getString(cfg, "type"), null);
}
@@ -42,6 +46,7 @@ public class WebNetworkHandlerConfigInput {
return JSONUtils.getString(cfg, "id");
}
@Nullable
public Boolean isEnabled() {
if (cfg.containsKey("enabled")) {
return JSONUtils.getBoolean(cfg, "enabled");
@@ -62,11 +67,13 @@ public class WebNetworkHandlerConfigInput {
return JSONUtils.getString(cfg, "password");
}
@Deprecated // use secured properties
// Deprecated. Use secured properties
@Nullable
public String getKey() {
return JSONUtils.getString(cfg, "key");
}
@Nullable
public Boolean isSavePassword() {
if (cfg.containsKey("savePassword")) {
return JSONUtils.getBoolean(cfg, "savePassword");
@@ -75,10 +82,12 @@ public class WebNetworkHandlerConfigInput {
}
}
@Nullable
public Map<String, Object> getProperties() {
return JSONUtils.getObjectOrNull(cfg, "properties");
}
@Nullable
public Map<String, String> getSecureProperties() {
var secureProperties = JSONUtils.getObjectOrNull(cfg, "secureProperties");
if (secureProperties == null) {
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,11 +17,13 @@
package io.cloudbeaver.model;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBConstants;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType;
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
import org.jkiss.dbeaver.model.impl.ProviderPropertyDescriptor;
import org.jkiss.dbeaver.model.meta.IPropertyValueListProvider;
import org.jkiss.dbeaver.model.meta.Property;
@@ -30,6 +32,7 @@ import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
import org.jkiss.dbeaver.runtime.properties.ObjectPropertyDescriptor;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.Array;
@@ -136,13 +139,14 @@ public class WebPropertyInfo {
@Property
public Object getValue() throws DBException {
Object value = propertySource == null ? null : propertySource.getPropertyValue(session.getProgressMonitor(), property.getId());
if (property instanceof ObjectPropertyDescriptor) {
ObjectPropertyDescriptor opd = (ObjectPropertyDescriptor) property;
if (!showProtected && opd.isPassword() || opd.isHidden()) {
if (value == null || value.toString().isEmpty()) {
return "";
}
return "******";
if (property instanceof ObjectPropertyDescriptor opd &&
(!showProtected && opd.isPassword() || opd.isHidden())
) {
return maskValue(value);
} else if (property instanceof PropertyDescriptor pd) {
String[] features = pd.getFeatures();
if (features != null && ArrayUtils.contains(features, DBConstants.PROP_FEATURE_PASSWORD)) {
return maskValue(value);
}
}
return value == null ? null : makePropertyValue(value);
@@ -307,4 +311,13 @@ public class WebPropertyInfo {
READ_ONLY
}
}
@NotNull
private static String maskValue(@Nullable Object value) {
if (value == null || value.toString().isEmpty()) {
return "";
} else {
return CBConstants.SECURED_VALUE;
}
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,10 +25,13 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBFileController;
import org.jkiss.dbeaver.model.app.DBPLockManagerProvider;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.fs.lock.LockManager;
import org.jkiss.dbeaver.model.fs.lock.shared.SharedFileLockManager;
import org.jkiss.dbeaver.model.impl.app.ApplicationRegistry;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
import org.jkiss.dbeaver.model.impl.app.BaseWorkspaceImpl;
@@ -48,7 +51,7 @@ import java.util.Map;
/**
* Servlet application
*/
public abstract class BaseServletApplication extends BaseApplicationImpl implements ServletApplication {
public abstract class BaseServletApplication extends BaseApplicationImpl implements ServletApplication, DBPLockManagerProvider {
public static final String DEFAULT_CONFIG_FILE_PATH = "/etc/cloudbeaver.conf";
public static final String CUSTOM_CONFIG_FOLDER = "custom";
@@ -59,6 +62,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
private String instanceId;
@NotNull
@Override
public RMController createResourceController(
@NotNull SMCredentialsProvider credentialsProvider,
@@ -73,6 +77,18 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
throw new IllegalStateException("File controller is not supported by " + getClass().getSimpleName());
}
@NotNull
@Override
public LockManager createLockManager(@NotNull Path metadataFolder) throws DBException {
return new SharedFileLockManager(getApplicationInstanceId(), metadataFolder);
}
@NotNull
@Override
public LockManager createLockManager() throws DBException {
return new SharedFileLockManager(getApplicationInstanceId());
}
@Nullable
@Override
public Path getDefaultWorkingFolder() {
@@ -80,9 +80,6 @@ public interface ServletApplication extends DBPApplication {
@NotNull
DBFileController createFileController(@NotNull SMCredentialsProvider credentialsProvider);
@Nullable
String getServerURL();
@NotNull
default String getServicesURI() {
return "/";
@@ -31,8 +31,6 @@ import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMResource;
import org.jkiss.dbeaver.model.rm.RMResourceType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
@@ -44,7 +42,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
private final RMResource resource;
DBNResourceManagerResource(DBNNode parentNode, RMResource resource) {
DBNResourceManagerResource(@NotNull DBNNode parentNode, @NotNull RMResource resource) {
super(parentNode);
this.resource = resource;
}
@@ -71,6 +69,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
@Override
public DBPImage getNodeIcon() {
if (resource.isFolder()) {
/*
if (getParentNode() instanceof DBNResourceManagerResource) {
return DBIcon.TREE_FOLDER;
}
@@ -82,11 +81,12 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
if (folderResType != null) {
return folderResType.getFolderIcon();
}
*/
return DBIcon.TREE_FOLDER;
} else {
var fileExtension = IOUtils.getFileExtension(getNodeDisplayName());
if (!CommonUtils.isEmpty(fileExtension)) {
RMProject project = getProjectNode();
RMProject project = getRmProject();
if (project != null) {
for (RMResourceType rt : project.getResourceTypes()) {
if (ArrayUtils.contains(rt.getFileExtensions(), fileExtension)) {
@@ -99,7 +99,8 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
}
}
private RMProject getProjectNode() {
@Nullable
public RMProject getRmProject() {
for (DBNNode node = this; node != null; node = node.getParentNode()) {
if (node instanceof DBNResourceManagerProject) {
return ((DBNResourceManagerProject) node).getProject();
@@ -26,7 +26,9 @@ import org.jkiss.dbeaver.model.DBPDataSourceFolder;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.fs.lock.LockManager;
import org.jkiss.dbeaver.model.fs.lock.LockOptions;
import org.jkiss.dbeaver.model.fs.lock.LockTarget;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMEvent;
import org.jkiss.dbeaver.model.rm.RMEventManager;
@@ -53,11 +55,11 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull
protected final DBPWorkspace workspace;
@NotNull
protected final FileLockController lockController;
protected final LockManager lockController;
protected BaseLocalResourceController(
@NotNull DBPWorkspace workspace,
@NotNull FileLockController lockController
@NotNull LockManager lockController
) {
this.workspace = workspace;
this.lockController = lockController;
@@ -157,7 +159,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "updateProjectDataSources")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("updateProjectDataSources"))) {
DBPProject project = getWebProject(projectId, false);
return doFileWriteOperation(
projectId, project.getMetadataFolder(false),
@@ -188,7 +190,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String[] dataSourceIds
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "deleteDataSources")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("deleteDataSources"))) {
DBPProject project = getWebProject(projectId, false);
doFileWriteOperation(projectId, project.getMetadataFolder(false), () -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
@@ -213,7 +215,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String folderPath
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("createDatasourceFolder"))) {
DBPProject project = getWebProject(projectId, false);
log.debug("Creating data source folder '" + folderPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
@@ -241,7 +243,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String[] folderPaths,
boolean dropContents
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("createDatasourceFolder"))) {
DBPProject project = getWebProject(projectId, false);
doFileWriteOperation(projectId, project.getMetadataFolder(false),
() -> {
@@ -267,7 +269,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String oldPath,
@NotNull String newPath
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "createDatasourceFolder")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("createDatasourceFolder"))) {
DBPProject project = getWebProject(projectId, false);
log.debug("Moving data source folder from '" + oldPath + "' to '" + newPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,9 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentials;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.fs.lock.LockManager;
import org.jkiss.dbeaver.model.fs.lock.LockOptions;
import org.jkiss.dbeaver.model.fs.lock.LockTarget;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.navigator.DBNLocalFolder;
@@ -90,9 +92,10 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull Path rootPath,
@NotNull Path userProjectsPath,
@NotNull Path sharedProjectsPath,
@NotNull Supplier<SMAdminController> smControllerSupplier
@NotNull Supplier<SMAdminController> smControllerSupplier,
@NotNull LockManager lockController
) throws DBException {
super(workspace, new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId()));
super(workspace, lockController);
this.credentialsProvider = credentialsProvider;
this.rootPath = rootPath;
this.userProjectsPath = userProjectsPath;
@@ -101,7 +104,7 @@ public class LocalResourceController extends BaseLocalResourceController {
this.globalProjectName = DBWorkbench.getPlatform().getApplication().getDefaultProjectName();
this.fileHandlers = RMFileOperationHandlersRegistry.getInstance().getFileHandlers();
this.sharedProjectsMetadataInfo = new ProjectsMetadataInfo(sharedProjectsPath);
this.sharedProjectsMetadataInfo = new ProjectsMetadataInfo(sharedProjectsPath, lockController);
}
@NotNull
@@ -303,7 +306,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public RMProject updateProject(@NotNull String projectId, @NotNull RMProjectInfo projectInfo) throws DBException {
validateProjectName(projectId, projectInfo.getName());
try (var ignoredLock = lockController.lock(projectId, "updateProject")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("updateProject"))) {
RMLocalProject project = getWebProject(projectId, false);
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
@@ -330,7 +333,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public void deleteProject(@NotNull String projectId) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "deleteProject")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("deleteProject"))) {
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
log.error(MessageFormat.format("Project folder ''{0}'' is not found", projectId));
@@ -699,7 +702,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String resourcePath,
boolean isFolder
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "createResource")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("createResource"))) {
validateResourcePath(resourcePath);
Path targetPath = getTargetPath(projectId, resourcePath);
if (Files.exists(targetPath)) {
@@ -731,7 +734,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String oldResourcePath,
@NotNull String newResourcePath
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "moveResource")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("moveResource"))) {
var normalizedOldResourcePath = CommonUtils.normalizeResourcePath(oldResourcePath);
var normalizedNewResourcePath = CommonUtils.normalizeResourcePath(newResourcePath);
if (log.isDebugEnabled()) {
@@ -803,7 +806,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public void deleteResource(@NotNull String projectId, @NotNull String resourcePath, boolean recursive) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "deleteResource")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("deleteResource"))) {
if (log.isDebugEnabled()) {
log.debug("Removing resource from '" + resourcePath + "' in project '" + projectId + "'" + (recursive ? " recursive" : ""));
}
@@ -891,7 +894,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull byte[] data,
boolean forceOverwrite
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "setResourceContents")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("setResourceContents"))) {
validateResourcePath(resourcePath);
Number fileSizeLimit = ServletAppUtils.getServletApplication()
.getAppConfiguration()
@@ -936,7 +939,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String propertyName,
@Nullable Object propertyValue
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("resourcePropertyUpdate"))) {
validateResourcePath(resourcePath);
RMLocalProject webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
@@ -957,7 +960,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String resourcePath,
@NotNull Map<String, Object> properties
) throws DBException {
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
try (var ignoredLock = lockController.lock(LockTarget.of(projectId), LockOptions.of("resourcePropertyUpdate"))) {
validateResourcePath(resourcePath);
RMLocalProject webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
@@ -1204,12 +1207,14 @@ public class LocalResourceController extends BaseLocalResourceController {
return getProjectPath(projectId).toAbsolutePath().relativize(path).toString().replace('\\', IPath.SEPARATOR);
}
@NotNull
public static Builder builder(
SMCredentialsProvider credentialsProvider,
DBPWorkspace workspace,
Supplier<SMAdminController> smControllerSupplier
@NotNull SMCredentialsProvider credentialsProvider,
@NotNull DBPWorkspace workspace,
@NotNull LockManager lockController,
@NotNull Supplier<SMAdminController> smControllerSupplier
) {
return new Builder(workspace, credentialsProvider, smControllerSupplier);
return new Builder(workspace, credentialsProvider, lockController, smControllerSupplier);
}
@Override
@@ -1225,10 +1230,13 @@ public class LocalResourceController extends BaseLocalResourceController {
protected Path rootPath;
protected Path userProjectsPath;
protected Path sharedProjectsPath;
protected LockManager lockController;
protected Builder(
DBPWorkspace workspace, SMCredentialsProvider credentialsProvider,
Supplier<SMAdminController> smControllerSupplier
@NotNull DBPWorkspace workspace,
@NotNull SMCredentialsProvider credentialsProvider,
@NotNull LockManager lockController,
@NotNull Supplier<SMAdminController> smControllerSupplier
) {
this.workspace = workspace;
this.credentialsProvider = credentialsProvider;
@@ -1236,25 +1244,38 @@ public class LocalResourceController extends BaseLocalResourceController {
this.rootPath = RMUtils.getRootPath();
this.userProjectsPath = RMUtils.getUserProjectsPath();
this.sharedProjectsPath = RMUtils.getSharedProjectsPath();
this.lockController = lockController;
}
public Builder setRootPath(Path rootPath) {
@NotNull
public Builder setRootPath(@NotNull Path rootPath) {
this.rootPath = rootPath;
return this;
}
public Builder setUserProjectsPath(Path userProjectsPath) {
@NotNull
public Builder setUserProjectsPath(@NotNull Path userProjectsPath) {
this.userProjectsPath = userProjectsPath;
return this;
}
public Builder setSharedProjectsPath(Path sharedProjectsPath) {
@NotNull
public Builder setSharedProjectsPath(@NotNull Path sharedProjectsPath) {
this.sharedProjectsPath = sharedProjectsPath;
return this;
}
@NotNull
public LocalResourceController build() throws DBException {
return new LocalResourceController(workspace, credentialsProvider, rootPath, userProjectsPath, sharedProjectsPath, smController);
return new LocalResourceController(
workspace,
credentialsProvider,
rootPath,
userProjectsPath,
sharedProjectsPath,
smController,
lockController
);
}
}
@@ -17,14 +17,15 @@
package io.cloudbeaver.model.rm.local;
import com.google.gson.reflect.TypeToken;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.fs.lock.LockManager;
import org.jkiss.dbeaver.model.fs.lock.LockOptions;
import org.jkiss.dbeaver.model.fs.lock.LockTarget;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.rm.RMProjectInfo;
import org.jkiss.dbeaver.model.rm.RMProjectType;
@@ -45,12 +46,12 @@ public class ProjectsMetadataInfo {
private final Map<String, RMProjectInfo> projectsInfo = new LinkedHashMap<>();
private final Path projectsPath;
private final FileLockController lockController;
private final LockManager lockController;
public ProjectsMetadataInfo(@NotNull Path projectsPath) throws DBException {
public ProjectsMetadataInfo(@NotNull Path projectsPath, @NotNull LockManager lockController) throws DBException {
this.projectsPath = projectsPath;
this.lockController = new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId());
this.lockController = lockController;
readProjectInfos(projectsPath);
}
@@ -139,7 +140,7 @@ public class ProjectsMetadataInfo {
}
private void saveProjectsInfo() {
try (var lock = lockController.lock(PROJECTS_INFO_FILE_NAME, "saveProjectsInfo")) {
try (var lock = lockController.lock(LockTarget.of(PROJECTS_INFO_FILE_NAME), LockOptions.of("saveProjectsInfo"))) {
log.info("Saving project information");
Files.writeString(projectsPath.resolve(PROJECTS_INFO_FILE_NAME), JSONUtils.GSON.toJson(projectsInfo));
} catch (IOException e) {
@@ -148,4 +149,4 @@ public class ProjectsMetadataInfo {
log.error("Error locking file " + PROJECTS_INFO_FILE_NAME + ": " + e.getMessage());
}
}
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.app.ServletAuthApplication;
import io.cloudbeaver.websocket.CBWebSessionEventHandler;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
@@ -103,7 +102,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
}
}
public abstract void addSessionError(Throwable exception);
public abstract void addSessionError(@NotNull Throwable exception);
public void addEventHandler(@NotNull CBWebSessionEventHandler handler) {
synchronized (sessionEventHandlers) {
@@ -117,11 +116,11 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
}
}
public synchronized boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
return userContext.refresh(smAuthInfo);
}
public synchronized void refreshUserData() {
public void refreshUserData() {
try {
userContext.refreshPermissions();
if (userContext.isAuthorizedInSecurityManager()) {
@@ -151,8 +150,8 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
return workspace.getAuthContext();
}
protected void clearSessionContext() {
this.workspace.getAuthContext().clear();
protected synchronized void clearSessionContext() {
this.workspace.getAuthContext().clearContext(false);
this.workspace.getAuthContext().addSession(this);
}
@@ -177,23 +176,24 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
return lastAccessTime;
}
public synchronized void touchSession() {
public void touchSession() {
this.lastAccessTime = System.currentTimeMillis();
}
@NotNull
public synchronized WebUserContext getUserContext() {
public WebUserContext getUserContext() {
return userContext;
}
@Override
public void close() {
super.close();
cleanUpSession(true);
super.close();
}
public void close(boolean clearTokens, boolean sendSessionExpiredEvent) {
cleanUpSession(sendSessionExpiredEvent);
super.close();
}
private void cleanUpSession(boolean sendSessionExpiredEvent) {
@@ -227,7 +227,7 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
this.eventsFilter = eventsFilter;
}
public boolean isProjectAccessible(String projectId) {
public boolean isProjectAccessible(@NotNull String projectId) {
return userContext.getAccessibleProjectIds().contains(projectId);
}
@@ -240,11 +240,11 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
}
public void removeSessionProject(@Nullable String projectId) throws DBException {
public void removeSessionProject(@NotNull String projectId) throws DBException {
userContext.getAccessibleProjectIds().remove(projectId);
}
public abstract void addSessionMessage(WebServerMessage message);
public abstract void addSessionMessage(@NotNull WebServerMessage message);
@Property
public boolean isValid() {
@@ -35,12 +35,12 @@ public class WebHeadlessSession extends BaseWebSession {
}
@Override
public void addSessionError(Throwable exception) {
public void addSessionError(@NotNull Throwable exception) {
}
@Override
public void addSessionMessage(WebServerMessage message) {
public void addSessionMessage(@NotNull WebServerMessage message) {
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,8 +57,6 @@ import org.jkiss.dbeaver.model.websocket.event.MessageType;
import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.function.ThrowableConsumer;
import org.jkiss.utils.function.ThrowableFunction;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
@@ -66,6 +64,8 @@ import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
/**
* Web session.
@@ -96,12 +96,12 @@ public class WebSession extends BaseWebSession
private final List<WebServerMessage> sessionMessages = new ArrayList<>();
private final Map<String, WebAsyncTaskInfo> asyncTasks = new HashMap<>();
private final Map<String, ThrowableConsumer<Object, Exception>> attributeDisposers = new HashMap<>();
// Map of auth tokens. Key is authentication provider
private final List<WebAuthInfo> authTokens = new ArrayList<>();
private DBNModel navigatorModel;
private final ReadWriteLock navigatorModelLock = new ReentrantReadWriteLock(true);
private final DBRProgressMonitor progressMonitor = new SessionProgressMonitor();
private final Map<String, DBWSessionHandler<WebSession>> sessionHandlers;
private final WebDataSourceConnectEventListener connectListener = new WebDataSourceConnectEventListener(this);
@@ -188,11 +188,12 @@ public class WebSession extends BaseWebSession
this.cacheExpired = cacheExpired;
}
public synchronized WebUser getUser() {
public WebUser getUser() {
return this.userContext.getUser();
}
public synchronized Map<String, String> getUserMetaParameters() {
@NotNull
public Map<String, String> getUserMetaParameters() {
var user = getUser();
if (user == null) {
return Map.of();
@@ -204,20 +205,20 @@ public class WebSession extends BaseWebSession
return allMetaParams;
}
public synchronized String getUserId() {
public String getUserId() {
return userContext.getUserId();
}
public synchronized boolean hasPermission(String perm) {
return getSessionPermissions().contains(DBWConstants.PERMISSION_ADMIN) ||
getSessionPermissions().contains(perm);
Set<String> sessionPermissions = getSessionPermissions();
return sessionPermissions.contains(DBWConstants.PERMISSION_ADMIN) || sessionPermissions.contains(perm);
}
public synchronized boolean isAuthorizedInSecurityManager() {
public boolean isAuthorizedInSecurityManager() {
return userContext.isAuthorizedInSecurityManager();
}
public synchronized Set<String> getSessionPermissions() {
public Set<String> getSessionPermissions() {
if (userContext.getUserPermissions() == null) {
refreshSessionAuth();
}
@@ -225,28 +226,30 @@ public class WebSession extends BaseWebSession
}
@NotNull
public synchronized SMController getSecurityController() {
public SMController getSecurityController() {
return userContext.getSecurityController();
}
@NotNull
public synchronized SMAdminController getAdminSecurityController() throws DBException {
public SMAdminController getAdminSecurityController() throws DBException {
if (!hasPermission(DBWConstants.PERMISSION_ADMIN)) {
throw new DBException("Admin permissions required");
}
return userContext.getAdminSecurityController();
}
public synchronized RMController getRmController() {
@NotNull
public RMController getRmController() {
return userContext.getRmController();
}
public synchronized DBFileController getFileController() {
@NotNull
public DBFileController getFileController() {
return userContext.getFileController();
}
@Override
public synchronized void refreshUserData() {
public void refreshUserData() {
super.refreshUserData();
refreshSessionAuth();
@@ -254,7 +257,7 @@ public class WebSession extends BaseWebSession
}
// Note: for admin use only
public synchronized void resetUserState(boolean needResetUserCache) throws DBException {
public void resetUserState(boolean needResetUserCache) throws DBException {
clearAuthTokens();
if (needResetUserCache) {
try {
@@ -268,7 +271,7 @@ public class WebSession extends BaseWebSession
clearSessionContext();
}
public synchronized void resetUserState() throws DBException {
public void resetUserState() throws DBException {
resetUserState(true);
}
@@ -277,26 +280,35 @@ public class WebSession extends BaseWebSession
return connectListener;
}
@NotNull
public String getClientOrigin() {
return clientOrigin;
}
public void updateClientOrigin(@NotNull String originFromRequest) {
this.clientOrigin = originFromRequest;
}
private void initNavigatorModel() {
navigatorModelLock.writeLock().lock();
try {
// Cleanup current data
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
this.navigatorModel = null;
}
this.globalProject = null;
// Cleanup current data
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
this.navigatorModel = null;
loadProjects();
this.navigatorModel = new DBNModel(DBWorkbench.getPlatform(), getWorkspace().getProjects());
this.navigatorModel.setModelAuthContext(getWorkspace().getAuthContext());
this.navigatorModel.initialize();
this.locale = Locale.getDefault().getLanguage();
} finally {
navigatorModelLock.writeLock().unlock();
}
this.globalProject = null;
loadProjects();
this.navigatorModel = new DBNModel(DBWorkbench.getPlatform(), getWorkspace().getProjects());
this.navigatorModel.setModelAuthContext(getWorkspace().getAuthContext());
this.navigatorModel.initialize();
this.locale = Locale.getDefault().getLanguage();
}
private void loadProjects() {
@@ -327,7 +339,8 @@ public class WebSession extends BaseWebSession
}
}
private WebSessionProjectImpl createWebProject(RMProject project) throws DBException {
@NotNull
private WebSessionProjectImpl createWebProject(@NotNull RMProject project) throws DBException {
WebSessionProjectImpl sessionProject;
if (project.isGlobal()) {
sessionProject = createGlobalProject(project);
@@ -345,6 +358,7 @@ public class WebSession extends BaseWebSession
return sessionProject;
}
@NotNull
protected WebSessionProjectImpl createSessionProject(@NotNull RMProject project) throws DBException {
return new WebSessionProjectImpl(this, project, getProjectPath(project));
}
@@ -354,37 +368,24 @@ public class WebSession extends BaseWebSession
return RMUtils.getProjectPath(project);
}
@NotNull
protected WebSessionProjectImpl createGlobalProject(RMProject project) {
globalProject = new WebSessionGlobalProjectImpl(this, project);
globalProject.refreshAccessibleConnectionIds();
return globalProject;
}
private void resetSessionCache() throws DBCException {
// Clear attributes
synchronized (attributes) {
for (Map.Entry<String, ThrowableConsumer<Object, Exception>> attrDisposer : attributeDisposers.entrySet()) {
Object attrValue = attributes.get(attrDisposer.getKey());
try {
attrDisposer.getValue().accept(attrValue);
} catch (Exception e) {
log.error("Error disposing attribute '" + attrDisposer.getKey() + "'", e);
}
}
attributeDisposers.clear();
// Remove all non-persistent attributes
attributes.entrySet().removeIf(
entry -> !(entry.getValue() instanceof PersistentAttribute));
}
}
private void resetNavigationModel() {
getWorkspace().getProjects().forEach(WebSessionProjectImpl::dispose);
navigatorModelLock.writeLock().lock();
try {
getWorkspace().getProjects().forEach(WebSessionProjectImpl::dispose);
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
this.navigatorModel = null;
if (this.navigatorModel != null) {
this.navigatorModel.dispose();
this.navigatorModel = null;
}
} finally {
navigatorModelLock.writeLock().unlock();
}
}
@@ -427,15 +428,25 @@ public class WebSession extends BaseWebSession
@Nullable
public DBNModel getNavigatorModel() {
return navigatorModel;
navigatorModelLock.readLock().lock();
try {
return navigatorModel;
} finally {
navigatorModelLock.readLock().unlock();
}
}
@NotNull
public DBNModel getNavigatorModelOrThrow() throws DBWebException {
if (navigatorModel != null) {
return navigatorModel;
navigatorModelLock.readLock().lock();
try {
if (navigatorModel != null) {
return navigatorModel;
}
throw new DBWebException("Navigator model is not found in session");
} finally {
navigatorModelLock.readLock().unlock();
}
throw new DBWebException("Navigator model is not found in session");
}
/**
* Returns and clears progress messages
@@ -449,7 +460,7 @@ public class WebSession extends BaseWebSession
}
}
public synchronized void updateInfo(boolean isOldHttpSessionUsed) {
public void updateInfo(boolean isOldHttpSessionUsed) {
log.trace("Update session lifetime " + getSessionId() + " for user " + getUserId());
touchSession();
if (isOldHttpSessionUsed) {
@@ -481,7 +492,6 @@ public class WebSession extends BaseWebSession
public void close() {
try {
resetNavigationModel();
resetSessionCache();
} catch (Throwable e) {
log.error(e);
}
@@ -498,7 +508,6 @@ public class WebSession extends BaseWebSession
public void close(boolean clearTokens, boolean sendSessionExpiredEvent) {
try {
resetNavigationModel();
resetSessionCache();
} catch (Throwable e) {
log.error(e);
}
@@ -513,6 +522,7 @@ public class WebSession extends BaseWebSession
super.close(clearTokens, sendSessionExpiredEvent);
}
@NotNull
private List<WebAuthInfo> clearAuthTokens() throws DBException {
ArrayList<WebAuthInfo> tokensCopy;
synchronized (authTokens) {
@@ -597,11 +607,13 @@ public class WebSession extends BaseWebSession
}
}
@NotNull
public WebAsyncTaskInfo createAndRunAsyncTask(@NotNull String taskName, @NotNull WebAsyncTaskProcessor<?> runnable) {
WebAsyncTaskInfo asyncTask = createAsyncTask(taskName);
return runAsyncTask(asyncTask, runnable);
}
@NotNull
public WebAsyncTaskInfo runAsyncTask(@NotNull WebAsyncTaskInfo asyncTask, @NotNull WebAsyncTaskProcessor<?> runnable) {
AbstractJob job = new AbstractCancelableJob(asyncTask.getName()) {
@NotNull
@@ -642,11 +654,11 @@ public class WebSession extends BaseWebSession
return asyncTask;
}
public void addSessionError(Throwable exception) {
public void addSessionError(@NotNull Throwable exception) {
addSessionMessage(new WebServerMessage(exception));
}
public void addSessionMessage(WebServerMessage message) {
public void addSessionMessage(@NotNull WebServerMessage message) {
synchronized (sessionMessages) {
sessionMessages.add(message);
}
@@ -657,14 +669,15 @@ public class WebSession extends BaseWebSession
message.getMessage()));
}
public void addInfoMessage(String message) {
public void addInfoMessage(@NotNull String message) {
addSessionMessage(new WebServerMessage(MessageType.INFO, message));
}
public void addWarningMessage(String message) {
public void addWarningMessage(@NotNull String message) {
addSessionMessage(new WebServerMessage(MessageType.WARNING, message));
}
@NotNull
public List<WebServerMessage> readLog(Integer maxEntries, Boolean clearLog) {
synchronized (sessionMessages) {
List<WebServerMessage> messages = new ArrayList<>();
@@ -684,46 +697,6 @@ public class WebSession extends BaseWebSession
}
}
@Override
public <T> T getAttribute(String name) {
synchronized (attributes) {
Object value = attributes.get(name);
if (value instanceof PersistentAttribute persistentAttribute) {
value = persistentAttribute.value();
}
return (T) value;
}
}
public void setAttribute(String name, Object value, boolean persistent) {
synchronized (attributes) {
attributes.put(name, persistent ? new PersistentAttribute(value) : value);
}
}
public <T, E extends Exception> T getAttribute(
String name,
ThrowableFunction<String, T, E> creator,
ThrowableConsumer<T, E> disposer
) throws E {
synchronized (attributes) {
Object value = attributes.get(name);
if (value instanceof PersistentAttribute persistentAttribute) {
value = persistentAttribute.value();
}
if (value == null) {
value = creator.apply(null);
if (value != null) {
attributes.put(name, value);
if (disposer != null) {
attributeDisposers.put(name, (ThrowableConsumer<Object, Exception>) disposer);
}
}
}
return (T) value;
}
}
@Property
public Map<String, Object> getActionParameters() {
WebActionParameters action = WebActionParameters.fromSession(this, true);
@@ -752,7 +725,7 @@ public class WebSession extends BaseWebSession
}
}
@NotNull
public List<WebAuthInfo> getAllAuthInfo() {
synchronized (authTokens) {
return new ArrayList<>(authTokens);
@@ -804,7 +777,7 @@ public class WebSession extends BaseWebSession
});
}
private void removeAuthInfo(WebAuthInfo oldAuthInfo) {
private void removeAuthInfo(@NotNull WebAuthInfo oldAuthInfo) {
oldAuthInfo.closeAuth();
synchronized (authTokens) {
authTokens.remove(oldAuthInfo);
@@ -830,6 +803,7 @@ public class WebSession extends BaseWebSession
return oldInfo;
}
@Nullable
public List<DBACredentialsProvider> getContextCredentialsProviders() {
return getAdapters(DBACredentialsProvider.class);
}
@@ -917,11 +891,11 @@ public class WebSession extends BaseWebSession
return parameters;
}
public synchronized void resetAuthToken() throws DBException {
public void resetAuthToken() throws DBException {
this.userContext.reset();
}
public synchronized boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
public boolean updateSMSession(SMAuthInfo smAuthInfo) throws DBException {
boolean contextChanged = super.updateSMSession(smAuthInfo);
if (contextChanged) {
refreshUserData();
@@ -940,7 +914,7 @@ public class WebSession extends BaseWebSession
}
@Nullable
public WebSessionProjectImpl getProjectById(@Nullable String projectId) {
public WebSessionProjectImpl getProjectById(@NotNull String projectId) {
return getWorkspace().getProjectById(projectId);
}
@@ -949,6 +923,7 @@ public class WebSession extends BaseWebSession
*
* @throws DBWebException if project with provided id is not found.
*/
@NotNull
public WebSessionProjectImpl getAccessibleProjectById(@Nullable String projectId) throws DBWebException {
WebSessionProjectImpl project = null;
if (projectId != null) {
@@ -960,6 +935,7 @@ public class WebSession extends BaseWebSession
return project;
}
@NotNull
public List<WebSessionProjectImpl> getAccessibleProjects() {
return getWorkspace().getProjects();
}
@@ -969,26 +945,35 @@ public class WebSession extends BaseWebSession
*/
public void addSessionProject(@NotNull WebSessionProjectImpl project) {
getWorkspace().addProject(project);
if (navigatorModel != null) {
navigatorModel.getRoot().addProject(project, false);
navigatorModelLock.readLock().lock();
try {
if (navigatorModel != null) {
navigatorModel.getRoot().addProject(project, false);
}
} finally {
navigatorModelLock.readLock().unlock();
}
}
/**
* Removes project from session cache and navigator tree.
*/
public void deleteSessionProject(@Nullable WebSessionProjectImpl project) {
if (project != null) {
RMProject rmProject = project.getRMProject();
log.info(String.format(
"Project deleted: [ID=%s, Name=%s, Type=%s, Creator=%s]",
rmProject.getId(), rmProject.getName(), rmProject.getType(), rmProject.getCreator()
));
project.dispose();
}
public void deleteSessionProject(@NotNull WebSessionProjectImpl project) {
RMProject rmProject = project.getRMProject();
log.info(String.format(
"Project deleted: [ID=%s, Name=%s, Type=%s, Creator=%s]",
rmProject.getId(), rmProject.getName(), rmProject.getType(), rmProject.getCreator()
));
project.dispose();
getWorkspace().removeProject(project);
if (navigatorModel != null) {
navigatorModel.getRoot().removeProject(project);
navigatorModelLock.readLock().lock();
try {
if (navigatorModel != null) {
navigatorModel.getRoot().removeProject(project);
}
} finally {
navigatorModelLock.readLock().unlock();
}
}
@@ -996,6 +981,10 @@ public class WebSession extends BaseWebSession
public void addSessionProject(@NotNull String projectId) throws DBException {
super.addSessionProject(projectId);
var rmProject = getRmController().getProject(projectId, false, false);
if (rmProject == null) {
log.error("RM project '" + projectId + "' not found");
return;
}
createWebProject(rmProject);
}
@@ -1010,7 +999,7 @@ public class WebSession extends BaseWebSession
}
@Override
public void removeSessionProject(@Nullable String projectId) throws DBException {
public void removeSessionProject(@NotNull String projectId) throws DBException {
super.removeSessionProject(projectId);
var project = getProjectById(projectId);
if (project == null) {
@@ -1048,7 +1037,6 @@ public class WebSession extends BaseWebSession
}
}
public void handleTaskConfirmationWithParameters(@NotNull String taskId, @NotNull Map<String, Object> parameters) {
String attributeName = getTaskConfirmationAttributeName(taskId);
CompletableFuture<Map<String, Object>> confirmationFuture = getAttribute(attributeName);
@@ -1060,6 +1048,16 @@ public class WebSession extends BaseWebSession
}
}
public void handleActionCancelledEvent(@NotNull String actionId) {
Object attributeValue = getAttribute(actionId);
if (!(attributeValue instanceof CompletableFuture<?> future)) {
log.info("Action to be cancelled not found, skipping: " + actionId);
return;
}
future.completeExceptionally(new DBWebException("Action cancelled by user: " + actionId));
removeAttribute(actionId);
}
@NotNull
private String getTaskConfirmationAttributeName(@NotNull String taskId) {
return WebSQLConstants.TASK_CONFIRMATION_ATTR_PREFIX + taskId;
@@ -1081,6 +1079,4 @@ public class WebSession extends BaseWebSession
return true;
}
private record PersistentAttribute(Object value) {
}
}
@@ -19,9 +19,12 @@ package io.cloudbeaver.model.session;
import io.cloudbeaver.WebSessionProjectImpl;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPAdaptable;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.RMUtils;
@@ -36,6 +39,7 @@ import java.util.List;
* Web workspace
*/
public class WebSessionWorkspace implements DBPWorkspace {
private static final Log log = Log.getLog(WebSessionWorkspace.class);
private final BaseWebSession session;
private final SessionContextImpl workspaceAuthContext;
@@ -87,7 +91,9 @@ public class WebSessionWorkspace implements DBPWorkspace {
@NotNull
@Override
public List<WebSessionProjectImpl> getProjects() {
return accessibleProjects;
synchronized (accessibleProjects) {
return new ArrayList<>(accessibleProjects);
}
}
@Nullable
@@ -99,26 +105,30 @@ public class WebSessionWorkspace implements DBPWorkspace {
@Nullable
@Override
public WebSessionProjectImpl getProject(@NotNull String projectName) {
for (WebSessionProjectImpl project : accessibleProjects) {
if (project.getName().equals(projectName)) {
return project;
synchronized (accessibleProjects) {
for (WebSessionProjectImpl project : accessibleProjects) {
if (project.getName().equals(projectName)) {
return project;
}
}
}
log.error("Project name '" + projectName + "' not found in session workspace");
return null;
}
@Nullable
@Override
public WebSessionProjectImpl getProjectById(@Nullable String projectId) {
if (projectId == null) {
return activeProject;
}
for (WebSessionProjectImpl project : accessibleProjects) {
if (project.getId().equals(projectId)) {
return project;
public WebSessionProjectImpl getProjectById(@NotNull String projectId) {
synchronized (accessibleProjects) {
for (WebSessionProjectImpl project : accessibleProjects) {
if (project.getId().equals(projectId)) {
return project;
}
}
}
return null;
log.error("Project ID '" + projectId + "' not found in session workspace");
// FIXME: return null here
return activeProject;
}
@NotNull
@@ -143,25 +153,47 @@ public class WebSessionWorkspace implements DBPWorkspace {
return null;
}
public void setActiveProject(WebSessionProjectImpl activeProject) {
@NotNull
@Override
public WebSessionProjectImpl createProject(@NotNull String name, @Nullable String description) throws DBException {
throw new DBException("Project creation is not supported in web session workspace");
}
@Override
public void deleteProject(@NotNull DBPProject project) throws DBException {
throw new DBException("Project removing is not supported in web session workspace");
}
@Override
public void renameProject(@NotNull DBPProject project, @NotNull String newName) throws DBException {
throw new DBException("Project renaming is not supported in web session workspace");
}
public void setActiveProject(@NotNull WebSessionProjectImpl activeProject) {
this.activeProject = activeProject;
}
void addProject(WebSessionProjectImpl project) {
accessibleProjects.add(project);
void addProject(@NotNull WebSessionProjectImpl project) {
synchronized (accessibleProjects) {
accessibleProjects.add(project);
}
}
void removeProject(WebSessionProjectImpl project) {
accessibleProjects.remove(project);
void removeProject(@NotNull WebSessionProjectImpl project) {
synchronized (accessibleProjects) {
accessibleProjects.remove(project);
}
}
void clearProjects() {
if (!this.accessibleProjects.isEmpty()) {
for (WebSessionProjectImpl project : accessibleProjects) {
project.dispose();
synchronized (accessibleProjects) {
if (!this.accessibleProjects.isEmpty()) {
for (WebSessionProjectImpl project : accessibleProjects) {
project.dispose();
}
this.activeProject = null;
this.accessibleProjects.clear();
}
this.activeProject = null;
this.accessibleProjects.clear();
}
}
@@ -53,9 +53,9 @@ public class WebUserContext implements SMCredentialsProvider {
private final ServletApplication application;
private final DBPWorkspace workspace;
private WebUser user;
private Set<String> userPermissions;
private SMCredentials smCredentials = null;
private volatile WebUser user;
private volatile Set<String> userPermissions;
private volatile SMCredentials smCredentials = null;
private String smSessionId;
private String refreshToken;
@@ -87,7 +87,7 @@ public class WebUserContext implements SMCredentialsProvider {
* @return - true if context changed
* @throws DBException - if user already authorized and new token come from another user
*/
public synchronized boolean refresh(@NotNull SMAuthInfo smAuthInfo) throws DBException {
public boolean refresh(@NotNull SMAuthInfo smAuthInfo) throws DBException {
if (smAuthInfo.getAuthPermissions() == null && !isAuthorizedInSecurityManager()) {
throw new DBCException("Required information about session permissions is missing");
}
@@ -175,53 +175,56 @@ public class WebUserContext implements SMCredentialsProvider {
}
@NotNull
public synchronized SMController getSecurityController() {
public SMController getSecurityController() {
return securityController;
}
@Nullable
@Override
public synchronized SMCredentials getActiveUserCredentials() {
public SMCredentials getActiveUserCredentials() {
return smCredentials;
}
public synchronized boolean isAuthorizedInSecurityManager() {
public boolean isAuthorizedInSecurityManager() {
return smCredentials != null;
}
public synchronized boolean isNonAnonymousUserAuthorizedInSM() {
public boolean isNonAnonymousUserAuthorizedInSM() {
return isAuthorizedInSecurityManager() && getUser() != null;
}
public synchronized boolean isAnonymousUserAuthorizedInSM() {
public boolean isAnonymousUserAuthorizedInSM() {
return isAuthorizedInSecurityManager() && getUser() == null;
}
@Nullable
public synchronized WebUser getUser() {
public WebUser getUser() {
return user;
}
public synchronized String getUserId() {
@Nullable
public String getUserId() {
return user == null ? null : user.getUserId();
}
protected synchronized void setUser(@Nullable WebUser user) {
protected void setUser(@Nullable WebUser user) {
this.user = user;
}
public synchronized SMAdminController getAdminSecurityController() {
@NotNull
public SMAdminController getAdminSecurityController() {
return adminSecurityController;
}
public synchronized Set<String> getUserPermissions() {
@Nullable
public Set<String> getUserPermissions() {
return userPermissions;
}
/**
* reread the current user's permissions
*/
public synchronized void refreshPermissions() throws DBException {
public void refreshPermissions() throws DBException {
if (isAuthorizedInSecurityManager()) {
log.debug("refresh permissions " + getUserId() + " " + getSmSessionId());
setUserPermissions(securityController.getTokenPermissions().getPermissions());
@@ -234,6 +237,7 @@ public class WebUserContext implements SMCredentialsProvider {
this.userPermissions = permissions;
}
@NotNull
public DBSSecretController getSecretController() throws DBException {
if (this.securityController == null) {
this.secretController = application.getSecretController(this, workspace.getAuthContext());
@@ -241,7 +245,8 @@ public class WebUserContext implements SMCredentialsProvider {
return secretController;
}
public synchronized String getSmSessionId() {
@NotNull
public String getSmSessionId() {
return smSessionId;
}
@@ -255,6 +260,7 @@ public class WebUserContext implements SMCredentialsProvider {
return rmController;
}
@NotNull
public DBFileController getFileController() {
return fileController;
}
@@ -268,6 +274,7 @@ public class WebUserContext implements SMCredentialsProvider {
this.refreshToken = refreshToken;
}
@NotNull
public WebSessionPreferenceStore getPreferenceStore() {
return preferenceStore;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,50 +28,34 @@ public class CBConstants {
public static final String RUNTIME_PRODUCT_CONFIG_FILE_NAME = ".product.runtime.conf";
public static final String AUTO_CONFIG_FILE_NAME = ".cloudbeaver.auto.conf";
public static final String PARAM_SERVER_CONFIGURATION = "server";
public static final String PARAM_SERVER_PORT = "serverPort";
public static final String PARAM_SERVER_HOST = "serverHost";
public static final String PARAM_SERVER_NAME = "serverName";
public static final String PARAM_FORCE_HTTPS = "forceHttps";
public static final String PARAM_BIND_SESSION_TO_IP = "bindSessionToIp";
public static final String PARAM_SUPPORTED_HOSTS = "supportedHosts";
public static final String PARAM_SSL_CONFIGURATION_PATH = "sslConfigurationPath";
public static final String PARAM_CONTENT_ROOT = "contentRoot";
public static final String PARAM_SERVER_URL = "serverURL";
public static final String PARAM_ROOT_URI = "rootURI";
public static final String PARAM_SERVICES_URI = "serviceURI";
public static final String PARAM_DRIVERS_LOCATION = "driversLocation";
public static final String PARAM_WORKSPACE_LOCATION = "workspaceLocation";
//legacy path to a product.conf file
@Deprecated
public static final String PARAM_PRODUCT_CONFIGURATION = "productConfiguration";
public static final String PARAM_PRODUCT_SETTINGS = "productSettings";
public static final String PARAM_EXTERNAL_PROPERTIES = "externalProperties";
public static final String PARAM_STATIC_CONTENT = "staticContent";
public static final String PARAM_RESOURCE_QUOTAS = "resourceQuotas";
public static final String PARAM_RESOURCE_MANAGER_ENABLED = "resourceManagerEnabled";
public static final String PARAM_SECRET_MANAGER_ENABLED = "secretManagerEnabled";
public static final String PARAM_SHOW_READ_ONLY_CONN_INFO = "showReadOnlyConnectionInfo";
public static final String PARAM_CONN_GRANT_ANON_ACCESS = "grantConnectionsAccessToAnonymousTeam";
public static final String PARAM_AUTH_PROVIDERS = "authConfiguration";
public static final String PARAM_DB_CONFIGURATION = "database";
public static final String PARAM_SESSION_EXPIRE_PERIOD = "expireSessionAfterPeriod";
public static final String PARAM_DEVEL_MODE = "develMode";
public static final String PARAM_SECURITY_MANAGER = "enableSecurityManager";
public static final String PARAM_SM_CONFIGURATION = "sm";
public static final String PARAM_PASSWORD_POLICY_CONFIGURATION = "passwordPolicy";
public static final int DEFAULT_SERVER_PORT = 8080;
//public static final String DEFAULT_SERVER_NAME = "CloudBeaver Web Server";
public static final String DEFAULT_CONTENT_ROOT = "/var/www/cloudbeaver";
public static final String DEFAULT_ROOT_URI = "/";
public static final String DEFAULT_SERVICES_URI = "/api/";
public static final String DEFAULT_DEPLOY_LOCATION = "/opt/cloudbeaver";
public static final String DEFAULT_DRIVERS_LOCATION = DEFAULT_DEPLOY_LOCATION + "/drivers";
public static final String DEFAULT_WORKSPACE_LOCATION = DEFAULT_DEPLOY_LOCATION + "/workspace";
public static final String DEFAULT_PRODUCT_CONFIGURATION = "conf/product.conf";
public static final String DEFAULT_DATASOURCE_PERMISSIONS_CONFIGURATION = "data-sources-permissions.json";
public static final String DEFAULT_ADMIN_NAME = "cbadmin";
@@ -84,14 +68,11 @@ public class CBConstants {
public static final String VAR_HOST_DOCKER_INTERNAL = "host.docker.internal";
public static final String VAR_AUTO_CB_SERVER_NAME = "CB_SERVER_NAME";
public static final String VAR_AUTO_CB_SERVER_URL = "CB_SERVER_URL";
public static final String VAR_AUTO_CB_ADMIN_NAME = "CB_ADMIN_NAME";
public static final String VAR_AUTO_CB_ADMIN_PASSWORD = "CB_ADMIN_PASSWORD";
public static final String CB_SESSION_COOKIE_NAME = "cb-session-id";
public static final String APPLICATION_JSON = "application/json";
public static final String ADMIN_AUTO_GRANT = "auto-grant";
public static final String HOST_LOCALHOST = "localhost";
public static final String HOST_127_0_0_1 = "127.0.0.1";
@@ -99,4 +80,6 @@ public class CBConstants {
public static final String BIND_SESSION_ENABLE = "enable";
public static final String BIND_SESSION_DISABLE = "disable";
public static final String DEFAULT_CLOUD_PROJECT_NAME = "GlobalConfiguration";
public static final String SECURED_VALUE = "******";
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.model.app.ServletApplication;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.app.DBPProject;
@@ -56,7 +57,7 @@ public class ServerGlobalWorkspace extends BaseWorkspaceImpl {
}
@Override
public void initializeProjects() {
public void initializeProjects() throws DBException {
initializeWorkspaceSession();
// Load global project
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.service.sql;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.data.DBDAttributeConstraint;
import org.jkiss.dbeaver.model.gis.DBGeometry;
import org.jkiss.utils.CommonUtils;
@@ -27,6 +28,7 @@ import java.util.Map;
*/
public class WebSQLDataFilterConstraint {
private String attributeName;
private Integer attributePosition;
private Integer orderPosition;
private Boolean orderAsc;
@@ -38,8 +40,9 @@ public class WebSQLDataFilterConstraint {
private WebSQLDataFilterConstraint() {
}
public WebSQLDataFilterConstraint(Map<String, Object> map) {
this.attributePosition = CommonUtils.toInt(map.get("attributePosition"));
public WebSQLDataFilterConstraint(@NotNull Map<String, Object> map) {
this.attributeName = CommonUtils.toString(map.get("attributeName"));
this.attributePosition = CommonUtils.toInteger(map.get("attributePosition"), null);
this.orderPosition = map.containsKey("orderPosition") ?
CommonUtils.toInt(map.get("orderPosition")) + 1 : // Use position + 1 because 0 means no ordering (because of legacy compatibility)
null;
@@ -64,8 +67,10 @@ public class WebSQLDataFilterConstraint {
}
public static WebSQLDataFilterConstraint from(DBDAttributeConstraint constraint) {
@NotNull
public static WebSQLDataFilterConstraint from(@NotNull DBDAttributeConstraint constraint) {
var webConstraint = new WebSQLDataFilterConstraint();
webConstraint.attributeName = constraint.getAttributeName();
webConstraint.orderPosition = constraint.getOrderPosition();
webConstraint.orderAsc = !constraint.isOrderDescending();
webConstraint.criteria = constraint.getCriteria();
@@ -76,6 +81,10 @@ public class WebSQLDataFilterConstraint {
return webConstraint;
}
public String getAttributeName() {
return attributeName;
}
public Integer getAttributePosition() {
return attributePosition;
}
@@ -0,0 +1,23 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.service.sql;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
public record WebSQLQueryParameterRecord(@NotNull String name, @Nullable String value) {
}
@@ -24,6 +24,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.HttpConstants;
import java.net.CookieManager;
import java.net.URI;
@@ -112,12 +113,22 @@ public class WebGQLClient {
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull Map<String, String> requestHeaders
) throws Exception {
return executeGQLRequest(query, variables, requestHeaders, "result");
}
@NotNull
public Map<String, Object> executeGQLRequest(
@NotNull String query,
@Nullable Map<String, Object> variables,
@NotNull Map<String, String> requestHeaders,
@NotNull String resultName
) throws Exception {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.POST(HttpRequest.BodyPublishers.ofString(makeGQLRequest(query, variables)))
.setHeader("TE-Client-Version", GeneralUtils.getMajorVersion())
.header("Content-Type", "application/json");
.header(HttpConstants.HEADER_CONTENT_TYPE, HttpConstants.CONTENT_TYPE_JSON);
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach(requestBuilder::header);
}
@@ -132,7 +143,7 @@ public class WebGQLClient {
throw new DBException(message);
}
Map<String, Object> parsed = new LinkedHashMap<>();
parsed.put("data", JSONUtils.getObject(body, "data").get("result"));
parsed.put("data", JSONUtils.getObject(body, "data").get(resultName));
parsed.put("headers", response.headers().map());
return parsed;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,6 +47,8 @@ public class ServletAppUtils {
private static final String HEADER_REFERER = "Referer";
private static final String HEADER_FORWARDED_SCHEME = "X-Forwarded-Scheme";
private static final String HEADER_FORWARDED_PROTO = "X-Forwarded-Proto";
private static final String HEADER_FORWARDED_PORT = "X-Forwarded-Port";
private static final String HEADER_FORWARDED_HOST = "X-Forwarded-Host";
private static final Set<Integer> DEFAULT_PORTS = Set.of(
80,
@@ -191,8 +193,12 @@ public class ServletAppUtils {
@NotNull String origin
) throws DBException {
String finalOrigin = webAuthApplication.modifyOrigin(origin);
StringBuilder authUriBuilder = new StringBuilder(removeSideSlashes(finalOrigin));
String serviceUriSegment = removeSideSlashes(webAuthApplication.getAuthServiceUriSegment());
String rootUri = removeSideSlashes(getServletApplication().getRootURI());
if (finalOrigin.endsWith("/" + rootUri) && serviceUriSegment.startsWith(rootUri + "/")) {
finalOrigin = finalOrigin.substring(0, finalOrigin.length() - rootUri.length());
}
StringBuilder authUriBuilder = new StringBuilder(removeSideSlashes(finalOrigin));
if (CommonUtils.isNotEmpty(serviceUriSegment)) {
authUriBuilder.append("/").append(serviceUriSegment);
}
@@ -294,19 +300,37 @@ public class ServletAppUtils {
@NotNull
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
String origin = request.getHeader(HEADER_ORIGIN);
if (log.isTraceEnabled()) {
log.trace("Origin header: " + origin);
}
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_X_ORIGIN);
if (log.isTraceEnabled()) {
log.trace("X-Origin header: " + origin);
}
}
if (CommonUtils.isEmpty(origin)) {
origin = request.getHeader(HEADER_REFERER);
if (log.isTraceEnabled()) {
log.trace("Referer header: " + origin);
}
}
String forwardedScheme = request.getHeader(HEADER_FORWARDED_SCHEME);
if (CommonUtils.isEmpty(forwardedScheme)) {
forwardedScheme = request.getHeader(HEADER_FORWARDED_PROTO);
}
String forwardedHost = request.getHeader(HEADER_FORWARDED_HOST);
if (CommonUtils.isNotEmpty(forwardedScheme) && CommonUtils.isNotEmpty(forwardedHost)) {
origin = forwardedScheme + "://" + forwardedHost;
if (log.isTraceEnabled()) {
log.trace("forwarded origin: " + origin);
}
}
if (CommonUtils.isEmpty(origin)) {
URI requestUrl = URI.create(request.getRequestURL().toString());
if (log.isTraceEnabled()) {
log.trace("Request URL: " + requestUrl);
}
origin = getRootUrlFromUri(requestUrl) + "/";
}
origin = removeSideSlashes(origin);
@@ -318,7 +342,15 @@ public class ServletAppUtils {
origin = removeSideSlashes(origin);
URI uri = URI.create(origin);
if (DEFAULT_PORTS.contains(uri.getPort())) {
int port = uri.getPort();
if (CommonUtils.isNotEmpty(request.getHeader(HEADER_FORWARDED_PORT))) {
try {
port = Integer.parseInt(request.getHeader(HEADER_FORWARDED_PORT));
} catch (NumberFormatException e) {
log.error("Failed to parse port from header: " + request.getHeader(HEADER_FORWARDED_PORT), e);
}
}
if (DEFAULT_PORTS.contains(port)) {
try {
origin = new URI(
uri.getScheme(),
@@ -333,6 +365,9 @@ public class ServletAppUtils {
log.error("Failed to create URI without port", e);
}
}
if (log.isTraceEnabled()) {
log.trace("Origin URI: " + origin);
}
return origin;
}
@@ -0,0 +1,40 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.websocket.event.client;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
public class WSSessionCancelActionClientEvent extends WSClientEvent {
public static final String ID = "cb_client_cancel_action";
@NotNull
private final String actionId;
public WSSessionCancelActionClientEvent(
@NotNull String actionId
) {
super(ID, WSConstants.TOPIC_SESSION_ACTION);
this.actionId = actionId;
}
@NotNull
public String getActionId() {
return actionId;
}
}
@@ -16,23 +16,24 @@
*/
package io.cloudbeaver.websocket.event.task;
import io.cloudbeaver.service.sql.WebSQLQueryParameterRecord;
import org.jkiss.code.NotNull;
import java.util.Map;
import java.util.List;
public class WSSessionTaskQueryParamsConfirmationEvent extends WSAbstractSessionTaskEvent {
private static final String ID = "cb_session_task_query_params_confirmation_request";
@NotNull
private final String query;
@NotNull
private final Map<String, Object> parameters;
private final List<WebSQLQueryParameterRecord> parameters;
public WSSessionTaskQueryParamsConfirmationEvent(
@NotNull String taskId,
@NotNull String title,
@NotNull String message,
@NotNull String query,
@NotNull Map<String, Object> parameters
@NotNull List<WebSQLQueryParameterRecord> parameters
) {
super(ID, taskId, title, message);
this.query = query;
@@ -45,7 +46,7 @@ public class WSSessionTaskQueryParamsConfirmationEvent extends WSAbstractSession
}
@NotNull
public Map<String, Object> getParameters() {
public List<WebSQLQueryParameterRecord> getParameters() {
return parameters;
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 25.3.4.qualifier
Bundle-Release-Date: 20260202
Bundle-Version: 26.0.3.qualifier
Bundle-Release-Date: 20260420
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,9 +10,8 @@
</extension>
<extension point="org.eclipse.core.runtime.products" id="product">
<product application="io.cloudbeaver.product.ce.application" description="CloudBeaver CE Web UI Application"
name="CloudBeaver CE Server">
<property name="appName" value="CloudBeaver CE Server"/>
<product application="io.cloudbeaver.product.ce.application" name="CloudBeaver Community">
<property name="appName" value="CloudBeaver Community"/>
<property name="versionUpdateURL" value="https://dbeaver.com/product/cloudbeaver-ce-version.json"/>
</product>
</extension>
@@ -21,8 +20,7 @@
<application
id="cloudbeaver-ce"
family="CB"
name="CloudBeaver CE Server"
description="CloudBeaver CE Web UI"
name="CloudBeaver Community"
server="true"/>
</extension>
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>25.3.4-SNAPSHOT</version>
<version>26.0.3-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.139.qualifier
Bundle-Release-Date: 20260202
Bundle-Version: 1.0.144.qualifier
Bundle-Release-Date: 20260420
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -14,6 +14,7 @@
<resource name="drivers/jaybird"/>
<resource name="drivers/h2"/>
<resource name="drivers/h2_v2"/>
<resource name="drivers/h2_v3"/>
<resource name="drivers/sqlite/xerial"/>
<resource name="drivers/mssql/new"/>
<resource name="drivers/trino"/>
@@ -35,6 +36,7 @@
<bundle id="drivers.firebird" label="Firebird drivers"/>
<bundle id="drivers.h2" label="H2 drivers"/>
<bundle id="drivers.h2_v2" label="H2 v2 drivers"/>
<bundle id="drivers.h2_v3" label="H2 v3 drivers"/>
<bundle id="drivers.sqlite.xerial" label="SQLite drivers"/>
<bundle id="drivers.mssql.new" label="SQL Server drivers"/>
<bundle id="drivers.trino" label="Trino drivers"/>
@@ -55,6 +57,7 @@
<driver id="clickhouse:com_clickhouse"/>
<driver id="h2:h2_embedded"/>
<driver id="h2:h2_embedded_v2"/>
<driver id="h2:h2_embedded_v3"/>
<driver id="sqlite:sqlite_jdbc"/>
<driver id="sqlite:libsql_jdbc"/>
<driver id="sqlserver:microsoft"/>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.139-SNAPSHOT</version>
<version>1.0.144-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,10 +1,10 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver CE Server
Bundle-Name: CloudBeaver Community
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 25.3.4.qualifier
Bundle-Release-Date: 20260202
Bundle-Version: 26.0.3.qualifier
Bundle-Release-Date: 20260420
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.ce</artifactId>
<version>25.3.4-SNAPSHOT</version>
<version>26.0.3-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -34,7 +34,7 @@ public class CBWebServerConfig extends WebServerConfig {
@Property
public String getServerURL() {
return CommonUtils.notEmpty(cbApp.getServerConfiguration().getServerURL());
return ""; // backward compatibility
}
@Property
@@ -31,7 +31,6 @@ public class CBServerConfig implements WebServerConfiguration {
private static final Log log = Log.getLog(CBServerConfig.class);
protected String serverURL;
@NotNull
protected List<String> supportedHosts = new ArrayList<>();
protected boolean forceHttps;
@@ -62,10 +61,6 @@ public class CBServerConfig implements WebServerConfiguration {
this.securityManagerConfiguration = createSecurityManagerConfiguration();
}
public String getServerURL() {
return serverURL;
}
public int getServerPort() {
return serverPort;
}
@@ -108,10 +103,6 @@ public class CBServerConfig implements WebServerConfiguration {
return staticContent;
}
public void setServerURL(String serverURL) {
this.serverURL = serverURL;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
@@ -119,11 +119,6 @@ public abstract class CBApplication<T extends CBServerConfig>
this.homeDirectory = new File(initHomeFolder());
}
@Nullable
public String getServerURL() {
return getServerConfiguration().getServerURL();
}
// Port this server listens on. If set the 0 a random port is assigned which may be obtained with getLocalPort()
@Override
public int getServerPort() {
@@ -259,9 +254,6 @@ public abstract class CBApplication<T extends CBServerConfig>
//log.debug("\tProduct details: " + application.getInfoDetails());
log.debug("\tListen port: " + getServerPort() + (CommonUtils.isEmpty(getServerHost()) ? " on all interfaces" : " on " + getServerHost()));
log.debug("\tBase URI: " + getServicesURI());
if (!isConfigurationMode()) {
log.debug("\tGlobal access server URL: " + getServerConfiguration().getServerURL());
}
if (isDevelMode()) {
log.debug("\tDevelopment mode");
} else {
@@ -356,7 +348,6 @@ public abstract class CBApplication<T extends CBServerConfig>
*/
protected void performAutoConfiguration(Path configPath) {
String autoServerName = System.getenv(CBConstants.VAR_AUTO_CB_SERVER_NAME);
String autoServerURL = System.getenv(CBConstants.VAR_AUTO_CB_SERVER_URL);
String autoAdminName = System.getenv(CBConstants.VAR_AUTO_CB_ADMIN_NAME);
String autoAdminPassword = System.getenv(CBConstants.VAR_AUTO_CB_ADMIN_PASSWORD);
@@ -371,7 +362,6 @@ public abstract class CBApplication<T extends CBServerConfig>
autoProps.load(is);
autoServerName = autoProps.getProperty(CBConstants.VAR_AUTO_CB_SERVER_NAME);
autoServerURL = autoProps.getProperty(CBConstants.VAR_AUTO_CB_SERVER_URL);
autoAdminName = autoProps.getProperty(CBConstants.VAR_AUTO_CB_ADMIN_NAME);
autoAdminPassword = autoProps.getProperty(CBConstants.VAR_AUTO_CB_ADMIN_PASSWORD);
} catch (IOException e) {
@@ -389,7 +379,6 @@ public abstract class CBApplication<T extends CBServerConfig>
}
CBServerConfig serverConfig = new CBServerConfig();
serverConfig.setServerName(autoServerName);
serverConfig.setServerURL(autoServerURL);
serverConfig.setMaxSessionIdleTime(getMaxSessionIdleTime());
try {
finishConfiguration(
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,13 +75,16 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
);
}
@NotNull
@Override
public RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider,
@NotNull DBPWorkspace workspace) throws DBException {
return LocalResourceController.builder(credentialsProvider, workspace, this::getSecurityController).build();
public RMController createResourceController(
@NotNull SMCredentialsProvider credentialsProvider,
@NotNull DBPWorkspace workspace
) throws DBException {
var lockManager = createLockManager();
return LocalResourceController
.builder(credentialsProvider, workspace, lockManager, this::getSecurityController)
.build();
}
@NotNull
@@ -104,4 +107,9 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
embeddedSecurityController.finishConfiguration(adminName, adminPassword, authInfoList);
}
}
@Override
public boolean isCommunity() {
return true;
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import io.cloudbeaver.server.jobs.SessionStateJob;
import io.cloudbeaver.server.jobs.WebDataSourceMonitorJob;
import io.cloudbeaver.server.jobs.WebSessionMonitorJob;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPRegistryListener;
import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
@@ -51,7 +52,7 @@ public class CBPlatform extends BaseWebPlatform {
}
@Override
protected synchronized void initialize() {
protected synchronized void initialize() throws DBException {
long startTime = System.currentTimeMillis();
log.info("Initialize web platform " + getClass().getSimpleName());
this.preferenceStore = new WebServerPreferenceStore(WebPlatformActivator.getInstance().getPreferences());
@@ -56,6 +56,9 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
private static final Log log = Log.getLog(CBServerConfigurationController.class);
private static final String NETWORK_MODE_ENV = "NETWORK_MODE";
private static final String NETWORK_MODE_VALUE_HOST = "host";
// Configurations
@NotNull
private final T serverConfiguration;
@@ -167,12 +170,9 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
public T parseServerConfiguration() {
var config = getServerConfiguration();
if (config.getServerURL() == null) {
String hostName = config.getServerHost();
if (CommonUtils.isEmpty(hostName)) {
hostName = getLocalHostAddress();
}
config.setServerURL("http://" + hostName + ":" + config.getServerPort());
if (NETWORK_MODE_VALUE_HOST.equalsIgnoreCase(System.getenv(NETWORK_MODE_ENV))) {
String hostName = getLocalHostAddress();
config.setServerHost(hostName);
}
config.setContentRoot(ServletAppUtils.getRelativePath(config.getContentRoot(), homeDirectory));
@@ -383,12 +383,6 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
}
}
public synchronized void updateServerUrl(@NotNull SMCredentialsProvider credentialsProvider,
@Nullable String newPublicUrl) throws DBException {
getServerConfiguration().setServerURL(newPublicUrl);
}
protected Map<String, Object> collectConfigurationProperties(
@NotNull CBServerConfig serverConfig,
@NotNull CBAppConfig appConfig
@@ -523,10 +517,6 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
CBConstants.PARAM_SERVER_NAME,
serverConfig.getServerName());
}
if (!CommonUtils.isEmpty(serverConfig.getServerURL())) {
copyConfigValue(
originServerConfig, serverConfigProperties, CBConstants.PARAM_SERVER_URL, serverConfig.getServerURL());
}
if (serverConfig.getMaxSessionIdleTime() > 0) {
copyConfigValue(
originServerConfig,
@@ -32,8 +32,8 @@ import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator;
import io.cloudbeaver.service.DBWServiceBindingServlet;
import io.cloudbeaver.service.DBWServiceBindingWebSocket;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.ee10.servlet.*;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.ee11.servlet.*;
import org.eclipse.jetty.ee11.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.xml.XmlConfiguration;
@@ -39,7 +39,6 @@ public class CBServerContextListener implements ServletContextListener {
//scf.setDomain(domain);
//scf.setMaxAge(CB_SESSION_LIFE_TIME);
cookieConfig.setPath(CBApplication.getInstance().getRootURI());
// cookieConfig.setSecure(application.getServerURL().startsWith("https"));
cookieConfig.setHttpOnly(true);
cookieConfig.setName(CBConstants.CB_SESSION_COOKIE_NAME);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class RequestHostFilter implements Filter {
private static final Log log = Log.getLog(RequestHostFilter.class);
@@ -74,9 +75,19 @@ public class RequestHostFilter implements Filter {
chain.doFilter(request, response);
return;
}
boolean isIpAddress = InetAddresses.isInetAddress(originUri.getHost());
if (CommonUtils.isNotEmpty(originUri.getHost())) {
requestAllowed = InetAddresses.isInetAddress(originUri.getHost());
} else {
log.debug(
"Request origin host is null, request URI - " + originUri +
", request path - " + httpRequest.getServletPath() +
", request url - " + httpRequest.getRequestURL()
);
}
String servletPath = httpRequest.getServletPath();
requestAllowed = isIpAddress;
if (!requestAllowed) {
if (CommonUtils.isNotEmpty(servletPath)) {
for (String excludedPath : excludedPaths) {
@@ -34,7 +34,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee11.servlet.DefaultServlet;
import org.eclipse.jetty.http.HttpHeader;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
@@ -379,6 +379,63 @@ public class CBSessionManager implements WebAppSessionManager {
}
}
@Nullable
public WebSession getWebSession(
@Nullable String smAccessToken,
@NotNull WebHttpRequestInfo requestInfo,
boolean create
) throws DBException {
if (CommonUtils.isEmpty(smAccessToken)) {
return null;
}
synchronized (sessionMap) {
var tempCredProvider = new SMTokenCredentialProvider(smAccessToken);
SMAuthPermissions authPermissions = application.createSecurityController(tempCredProvider).getTokenPermissions();
var sessionId = requestInfo.getId() != null ? requestInfo.getId()
: authPermissions.getSessionId();
var existSession = sessionMap.get(sessionId);
if (existSession instanceof WebSession webSession) {
var creds = webSession.getUserContext().getActiveUserCredentials();
if (creds == null || !smAccessToken.equals(creds.getSmAccessToken())) {
if (webSession.getUserContext().refresh(
smAccessToken,
null,
authPermissions
)) {
webSession.refreshUserData();
}
}
return webSession;
}
if (existSession != null) {
//session exist but it not web session
return null;
}
if (!create) {
return null;
}
if (requestInfo.getId() == null) {
requestInfo = new WebHttpRequestInfo(
sessionId,
requestInfo.getLocale(),
requestInfo.getLastRemoteAddress(),
requestInfo.getLastRemoteUserAgent()
);
}
var webSession = createWebSessionImpl(requestInfo);
webSession.getUserContext().refresh(
smAccessToken,
null,
authPermissions
);
webSession.refreshUserData();
sessionMap.put(sessionId, webSession);
return webSession;
}
}
/**
* Send session state with remaining alive time to all cached session
*/
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 25.3.4.qualifier
Bundle-Release-Date: 20260202
Bundle-Version: 26.0.3.qualifier
Bundle-Release-Date: 20260420
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
@@ -39,7 +39,7 @@
</extension>
<extension point="org.jkiss.dbeaver.ws.event.handler">
<eventHandler class="io.cloudbeaver.server.events.WSDefaultEventHandler">
<eventHandler class="io.cloudbeaver.server.events.WSServerConfigurationChangedEventHandler">
<topic id="cb_config"/>
</eventHandler>
<eventHandler class="io.cloudbeaver.server.events.WSDataSourceUpdatedEventHandlerImpl">
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>25.3.4-SNAPSHOT</version>
<version>26.0.3-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -565,6 +565,8 @@ type ConnectionInfo {
requiredAuth: String
defaultCatalogName: String @since(version: "25.0.5")
defaultSchemaName: String @since(version: "25.0.5")
"Default user settings for connection"
defaultUserPreferences: Object @since(version: "26.0.2")
"List of tools that can be used with this connection. Returns empty list if no tools are available"
tools: [String!]! @since(version: "24.1.3")
@@ -741,6 +743,8 @@ input ConnectionConfig {
defaultCatalogName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Sets schema name for the connection"
defaultSchemaName: String @since(version: "25.0.5") @deprecated(reason: "25.2.1 use expertPropertyValues instead")
"Default user settings for connection"
defaultUserPreferences: Object @since(version: "26.0.2")
}
####################################################
@@ -825,7 +829,7 @@ extend type Mutation {
testConnection( config: ConnectionConfig!, projectId: ID): ConnectionInfo!
"Test network handler connectivity"
testNetworkHandler( config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!
testNetworkHandler(projectId: ID, connectionId: ID, config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!
"Initiate database connection. User credentials may be (optionally) override default settings."
initConnection(
@@ -846,6 +850,7 @@ extend type Mutation {
"Sets to default navigator settings for connection. Resets all user navigator settings for this connection."
clearConnectionNavigatorSettings(projectId: ID!, id: ID!): ConnectionInfo! @since(version: "25.3.2")
setObjectSettingsForDatasource(id: ID!, projectId:ID, settings: Object!): Object! @since(version: "25.3.5")
#### Generic async functions
"Cancel async task by ID"
@@ -0,0 +1,62 @@
####################################################
extend enum CBServerEventId {
"Datasource created"
cb_datasource_created,
"Datasource updated"
cb_datasource_updated,
"Datasource deleted"
cb_datasource_deleted,
"Datasource folder created"
cb_datasource_folder_created,
"Datasource folder updated"
cb_datasource_folder_updated,
"Datasource folder deleted"
cb_datasource_folder_deleted,
"Datasource disconnected"
cb_datasource_disconnected,
"Datasource connected"
cb_datasource_connected
}
extend enum CBEventTopic {
cb_datasource,
cb_datasource_folder,
cb_datasource_connection
}
"Datasource folder event"
type CBDatasourceFolderEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
nodePaths: [String!]! # list of folders node ids
projectId: String!
}
"Datasource event"
type CBDatasourceEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
dataSourceIds: [String!]! # list of datasources ids
projectId: String!
}
"Datasource disconnect event"
type WSDataSourceDisconnectEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
connectionId: String!
projectId: String!
timestamp: Int!
}
"Datasource connect event"
type WSDataSourceConnectEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
connectionId: String!
projectId: String!
timestamp: Int!
}
@@ -17,52 +17,11 @@ enum CBServerEventId {
"Session expired"
cb_session_expired,
"Datasource created"
cb_datasource_created,
"Datasource updated"
cb_datasource_updated,
"Datasource deleted"
cb_datasource_deleted,
"Datasource folder created"
cb_datasource_folder_created,
"Datasource folder updated"
cb_datasource_folder_updated,
"Datasource folder deleted"
cb_datasource_folder_deleted,
"Datasource disconnected"
cb_datasource_disconnected,
"Datasource connected"
cb_datasource_connected,
"Resource manager resource created"
cb_rm_resource_created,
"Resource manager resource updated"
cb_rm_resource_updated,
"Resource manager resource deleted"
cb_rm_resource_deleted,
"Resource manager project added"
cb_rm_project_added,
"Resource manager project added"
cb_rm_project_updated,
"Resource manager project removed"
cb_rm_project_removed,
"Object permissions updated (e.g., datasource)"
cb_object_permissions_updated,
"Subject permissions updated (e.g., user, team)"
cb_subject_permissions_updated,
"Database output log updated (e.g., DBMS in Oracle)"
cb_database_output_log_updated,
cb_ai_chat_message_chunk @since(version: "25.1.1")
cb_ai_chat_message_error @since(version: "25.1.1")
"Transaction count updated"
cb_transaction_count @since(version: "24.3.3"),
"Session async task info updated"
cb_session_task_info_updated @since(version: "24.3.1"),
"Workspace configuration updated"
@@ -70,11 +29,6 @@ enum CBServerEventId {
"Server notification"
cb_server_notification@since(version: "25.2.1")
"Session task confirmations"
cb_session_task_confirmation_request @since(version: "25.2.2")
cb_session_task_query_confirmation_request @since(version: "25.2.2")
cb_session_task_query_params_confirmation_request @since(version: "25.2.5")
"Open URL request in client"
cb_open_url @since(version: "25.3.1")
}
@@ -85,8 +39,7 @@ enum CBClientEventId {
cb_client_topic_unsubscribe,
cb_client_projects_active,
cb_client_session_ping,
cb_client_session_task_confirmation @since(version: "25.2.2")
cb_client_session_task_with_parameters_confirmation @since(version: "25.2.5")
cb_client_cancel_action @since(version: "26.0.2")
}
"Client subscribes on topic to receive only related events"
@@ -94,22 +47,14 @@ enum CBEventTopic {
cb_config,
cb_session_log,
cb_session,
cb_datasource,
cb_datasource_folder,
cb_scripts,
cb_projects,
cb_object_permissions,
cb_subject_permissions,
cb_database_output_log,
cb_session_task, @since(version: "24.3.1")
cb_datasource_connection,
cb_delete_temp_folder,
cb_transaction @since(version: "24.3.3"),
cb_workspace_configuration @since(version: "25.1.1")
cb_ai @since(version: "25.1.1")
cb_notification @since(version: "25.2.1")
cb_session_action @since(version: "25.3.1")
}
@@ -126,30 +71,6 @@ interface CBClientEvent {
topicId: CBEventTopic
}
"Datasource folder event"
type CBDatasourceFolderEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
nodePaths: [String!]! # list of folders node ids
projectId: String!
}
"Datasource event"
type CBDatasourceEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
dataSourceIds: [String!]! # list of datasources ids
projectId: String!
}
"Resource manager event"
type CBRMEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
resourcePath: String!
projectId: String!
}
"Server Config event"
type CBConfigEvent implements CBServerEvent {
id: CBServerEventId!
@@ -187,12 +108,6 @@ type WSSessionExpiredEvent implements CBServerEvent {
topicId: CBEventTopic
}
"RM project update event"
type CBProjectUpdateEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
projectId: String!
}
"Subject permission update event"
type CBSubjectPermissionUpdateEvent implements CBServerEvent {
@@ -222,20 +137,11 @@ type CBProjectsActiveEvent implements CBClientEvent {
projectIds: [String!]!
}
"Database output log event"
type CBDatabaseOutputLogEvent implements CBServerEvent {
id: CBServerEventId!
"Action cancelled by user"
type CBActionCancelledEvent implements CBClientEvent @since(version: "26.0.2") {
id: CBClientEventId!
topicId: CBEventTopic
contextId: String!
messages: [WSOutputLogInfo!]!
eventTimestamp: Int!
}
"Define the type for WSOutputLogInfo"
type WSOutputLogInfo {
severity: String
message: String
# Add more fields if needed
actionId: String!
}
"Async task info status event"
@@ -246,56 +152,13 @@ type WSAsyncTaskInfo @since(version: "24.3.1") {
running: Boolean!
}
"Datasource disconnect event"
type WSDataSourceDisconnectEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
connectionId: String!
projectId: String!
timestamp: Int!
}
"Datasource connect event"
type WSDataSourceConnectEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
connectionId: String!
projectId: String!
timestamp: Int!
}
"Datasource count event in transactional mode"
type WSTransactionalCountEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
contextId: String!
projectId: String!
connectionId: String!
transactionalCount: Int!
}
type WSAIChatMessageChunkEvent implements CBServerEvent @since(version: "25.1.1") {
id: CBServerEventId!
topicId: CBEventTopic
conversationId: ID!
messageId: ID!
chunk: String
completed: Boolean!
}
type WSAIChatMessageErrorEvent implements CBServerEvent @since(version: "25.1.1") {
id: CBServerEventId!
topicId: CBEventTopic
conversationId: ID!
messageId: ID!
errorMessage: String
}
enum WSServerNotificationEventType {
INFO
ERROR
LOADING
CUSTOM
}
"General event for showing user notification"
type WSServerNotificationEvent implements CBServerEvent @since(version: "25.2.1") {
id: CBServerEventId!
@@ -305,54 +168,14 @@ type WSServerNotificationEvent implements CBServerEvent @since(version: "25.2.1"
notificationType: WSServerNotificationEventType!
}
#TODO: move out
"Event for opening URL in client application"
type WSOpenUrlEvent implements CBServerEvent @since(version: "25.3.1") {
id: CBServerEventId!
topicId: CBEventTopic!
timestamp: Int!
url: String!
}
type WSSessionTaskConfirmationRequestEvent implements CBServerEvent @since(version: "25.2.2") {
id: CBServerEventId!
topicId: CBEventTopic!
taskId: ID!
title: String!
message: String!
}
type WSSessionTaskQueryConfirmationRequestEvent implements CBServerEvent @since(version: "25.2.2") {
id: CBServerEventId!
topicId: CBEventTopic!
taskId: ID!
title: String!
message: String!
query: String!
}
type WSSessionTaskConfirmationEvent implements CBClientEvent @since(version: "25.2.2") {
id: CBClientEventId!
topicId: CBEventTopic
taskId: ID!
confirmed: Boolean!
skipConfirmations: Boolean!
}
type WSSessionTaskQueryParamsConfirmationEvent implements CBServerEvent @since(version: "25.2.5") {
id: CBServerEventId!
topicId: CBEventTopic!
taskId: ID!
title: String!
message: String!
query: String!
parameters: Object!
}
type WSSessionTaskWithParametersConfirmationEvent implements CBClientEvent @since(version: "25.2.5") {
id: CBClientEventId!
topicId: CBEventTopic
taskId: ID!
parameters: Object!
actionId: String!
}
extend type Query {
@@ -0,0 +1,97 @@
extend enum CBServerEventId {
"Database output log updated (e.g., DBMS in Oracle)"
cb_database_output_log_updated,
"Transaction count updated"
cb_transaction_count @since(version: "24.3.3"),
"Session task confirmations"
cb_session_task_confirmation_request @since(version: "25.2.2")
cb_session_task_query_confirmation_request @since(version: "25.2.2")
cb_session_task_query_params_confirmation_request @since(version: "25.2.5")
}
extend enum CBClientEventId {
cb_client_session_task_confirmation @since(version: "25.2.2")
cb_client_session_task_with_parameters_confirmation @since(version: "25.2.5")
}
extend enum CBEventTopic {
cb_database_output_log,
cb_transaction @since(version: "24.3.3")
}
"Database output log event"
type CBDatabaseOutputLogEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
contextId: String!
messages: [WSOutputLogInfo!]!
eventTimestamp: Int!
}
"Define the type for WSOutputLogInfo"
type WSOutputLogInfo {
severity: String
message: String
# Add more fields if needed
}
"Datasource count event in transactional mode"
type WSTransactionalCountEvent implements CBServerEvent {
id: CBServerEventId!
topicId: CBEventTopic
contextId: String!
projectId: String!
connectionId: String!
transactionalCount: Int!
}
"Query parameter information"
type WSQueryParameterInfo {
name: String!
value: String
}
type WSSessionTaskConfirmationRequestEvent implements CBServerEvent @since(version: "25.2.2") {
id: CBServerEventId!
topicId: CBEventTopic!
taskId: ID!
title: String!
message: String!
}
type WSSessionTaskQueryConfirmationRequestEvent implements CBServerEvent @since(version: "25.2.2") {
id: CBServerEventId!
topicId: CBEventTopic!
taskId: ID!
title: String!
message: String!
query: String!
}
type WSSessionTaskConfirmationEvent implements CBClientEvent @since(version: "25.2.2") {
id: CBClientEventId!
topicId: CBEventTopic
taskId: ID!
confirmed: Boolean!
skipConfirmations: Boolean!
}
type WSSessionTaskQueryParamsConfirmationEvent implements CBServerEvent @since(version: "26.0.0") {
id: CBServerEventId!
topicId: CBEventTopic!
taskId: ID!
title: String!
message: String!
query: String!
parameters: [WSQueryParameterInfo]!
}
type WSSessionTaskWithParametersConfirmationEvent implements CBClientEvent @since(version: "25.2.5") {
id: CBClientEventId!
topicId: CBEventTopic
taskId: ID!
parameters: Object!
}
@@ -51,7 +51,9 @@ type SQLContextInfo {
}
input SQLDataFilterConstraint {
attributePosition: Int!
"Either attributeName or attributePosition must be specified"
attributeName: String
attributePosition: Int
orderPosition: Int
orderAsc: Boolean
@@ -150,6 +152,19 @@ enum SQLRowIdentifierState {
VIRTUAL_KEY
}
enum SQLResultSetGeneratorId {
"SELECT .. WHERE .. ="
dataSelect,
"SELECT .. WHERE .. IN"
dataSelectMany,
"INSERT"
dataInsert,
"UPDATE"
dataUpdate,
"DELETE by Unique Key"
dataDeleteByUniqueKey,
}
type SQLRowIdentifier {
"Single or multiple attributes acting as a row identifier"
attributes: [SQLRowIdentifierAttribute!]!
@@ -183,6 +198,8 @@ type SQLExecuteInfo {
filterText: String
# Full query that was executed, contains all used filters
fullQuery: String
# Original query that was executed, without modifications
originalQuery: String
# Results
results: [ SQLQueryResults! ]!
}
@@ -237,6 +254,11 @@ type SQLQueryGenerator {
multiObject: Boolean!
}
input SQLQueryGeneratorOptions {
useFullyQualifiedNames: Boolean!
compactSql: Boolean!
}
####################################################
# SQL Query info
####################################################
@@ -321,19 +343,26 @@ extend type Query {
"""
Generates SQL query for the specified entity query generator.
Available options:
fullyQualifiedNames - Use fully qualified names for entities
compactSQL - Use compact SQL format
showComments - Show comments in generated SQL
showPermissions - Show permissions in generated SQL
showFullDdl - Show full DDL in generated SQL
excludeAutoGeneratedColumn - Exclude auto-generated columns in generated SQL
useCustomDataFormat - Use custom data format for generated SQL
"""
sqlGenerateEntityQuery(
generatorId: String!,
options: Object!,
nodePathList: [String!]!
options: Object @deprecated(reason: "use generatorOptions (26.0.3)")
nodePathList: [String!]!,
generatorOptions: SQLQueryGeneratorOptions
): String!
"""
Returns an auto-generated SQL query based on provided rows data.
Uses generatorId value to establish the type of query (e.g. SELECT or INSERT)
"""
sqlGenerateResultSetQuery(
projectId: ID,
connectionId: ID!,
contextId: ID!,
generatorId: SQLResultSetGeneratorId!,
resultsId: ID!,
selectedRows: [SQLResultRow!]!,
generatorOptions: SQLQueryGeneratorOptions
): String!
"Parses SQL script and returns script info with queries start and end positions"
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,6 +82,7 @@ public class WebDatabaseAuthModel {
return Arrays.stream(credentialsSource.getProperties())
.filter(predicate)
.filter(p -> !p.isDesktop())
.map(p -> new WebPropertyInfo(webSession, p, credentialsSource)).toArray(WebPropertyInfo[]::new);
}
@@ -23,6 +23,7 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.connection.*;
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNative;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
@@ -186,10 +187,28 @@ public class WebDatabaseDriverInfo {
cfg.setDatabaseName(driver.getDefaultDatabase());
cfg.setUrl(driver.getConnectionURL(cfg));
DBPPropertyDescriptor[] properties = driver.getDataSourceProvider().getConnectionProperties(webSession.getProgressMonitor(), driver, cfg);
Map<String, Object> connectionProperties = driver.getConnectionProperties();
for (Map.Entry<String, Object> connProp : connectionProperties.entrySet()) {
String propName = connProp.getKey();
Object propValue = connProp.getValue();
DBPPropertyDescriptor dbpPropertyDescriptor = new PropertyDescriptor(
null,
propName,
propName,
null,
false,
String.class,
propValue,
null
);
properties = ArrayUtils.add(DBPPropertyDescriptor.class, properties, dbpPropertyDescriptor);
cfg.setProperty(propName, (String) propValue);
}
if (properties == null) {
return new WebPropertyInfo[0];
}
PropertySourceCustom propertySource = new PropertySourceCustom(
properties,
cfg.getProperties());
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -61,6 +61,7 @@ public class WebNetworkHandlerDescriptor {
public WebPropertyInfo[] getProperties() {
return Arrays.stream(descriptor.getHandlerProperties())
.filter(p -> !p.isDesktop() && !p.isHidden())
.map(p -> new WebPropertyInfo(session, p, null)).toArray(WebPropertyInfo[]::new);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,29 +19,24 @@ package io.cloudbeaver.server;
import io.cloudbeaver.server.websockets.WebSocketPingPongJob;
import org.eclipse.core.runtime.Plugin;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.app.DBACertificateStorage;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.impl.app.DefaultCertificateStorage;
import org.jkiss.dbeaver.model.qm.QMRegistry;
import org.jkiss.dbeaver.model.qm.QMUtils;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.runtime.SecurityProviderUtils;
import org.jkiss.dbeaver.runtime.qm.QMLogFileWriter;
import org.jkiss.dbeaver.runtime.qm.QMRegistryImpl;
public abstract class BaseWebPlatform extends BaseServletPlatform {
public static final String TEMP_FILE_FOLDER = "temp-sql-upload-files";
public static final String TEMP_FILE_IMPORT_FOLDER = "temp-import-files";
private QMRegistryImpl queryManager;
private QMLogFileWriter qmLogWriter;
private DBACertificateStorage certificateStorage;
private ServerGlobalWorkspace workspace;
@Override
protected synchronized void initialize() {
protected synchronized void initialize() throws DBException {
// Register BC security provider
SecurityProviderUtils.registerSecurityProvider();
@@ -49,14 +44,11 @@ public abstract class BaseWebPlatform extends BaseServletPlatform {
getApplication().beforeWorkspaceInitialization();
this.workspace = new ServerGlobalWorkspace(this, getApplication());
this.workspace.initializeProjects();
QMUtils.initApplication(this);
this.queryManager = new QMRegistryImpl();
this.qmLogWriter = new QMLogFileWriter();
this.queryManager.registerMetaListener(qmLogWriter);
QMUtils.initPlatform(true);
this.certificateStorage = new DefaultCertificateStorage(
this,
WebPlatformActivator.getInstance()
.getStateLocation()
.toFile()
@@ -92,21 +84,8 @@ public abstract class BaseWebPlatform extends BaseServletPlatform {
@Override
public synchronized void dispose() {
super.dispose();
if (this.qmLogWriter != null) {
this.queryManager.unregisterMetaListener(qmLogWriter);
this.qmLogWriter.dispose();
this.qmLogWriter = null;
}
if (this.queryManager != null) {
this.queryManager.dispose();
//queryManager = null;
}
QMUtils.disposePlatform();
DataSourceProviderRegistry.dispose();
}
@NotNull
public QMRegistry getQueryManager() {
return queryManager;
}
}
@@ -16,13 +16,21 @@
*/
package io.cloudbeaver.server;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.runtime.DBWorkbench;
public class WebAppUtils {
public static boolean isWebApplication() {
return DBWorkbench.getPlatform().getApplication() instanceof WebApplication;
}
@NotNull
public static WebApplication getWebApplication() {
return (WebApplication) DBWorkbench.getPlatform().getApplication();
}
@NotNull
public static BaseWebPlatform getWebPlatform() {
return (BaseWebPlatform) DBWorkbench.getPlatform();
}
@@ -31,6 +31,9 @@ public class WSDefaultEventHandler<EVENT extends WSEvent> implements WSEventHand
@Override
public void handleEvent(@NotNull EVENT event) {
if (!WebAppUtils.isWebApplication()) {
return;
}
log.debug(event.getTopicId() + " event handled");
Collection<BaseWebSession> allSessions = WebAppUtils.getWebApplication()
.getSessionManager()
@@ -0,0 +1,30 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server.events;
import io.cloudbeaver.model.session.BaseWebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.event.WSServerConfigurationChangedEvent;
public class WSServerConfigurationChangedEventHandler extends WSDefaultEventHandler<WSServerConfigurationChangedEvent> {
@Override
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSServerConfigurationChangedEvent event) {
activeUserSession.refreshUserData();
super.updateSessionData(activeUserSession, event);
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ import io.cloudbeaver.service.DBWBindingContext;
import io.cloudbeaver.service.DBWServiceBindingGraphQL;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.utils.ServletAppUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -202,16 +203,21 @@ public class GraphQLEndpoint extends HttpServlet {
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String path = request.getPathInfo();
if (path == null) {
path = request.getServletPath();
}
boolean develMode = ServletAppUtils.getServletApplication().getServerConfiguration().isDevelMode();
if (path.contentEquals("/schema.json") && develMode) {
if (!develMode) {
super.doGet(request, response);
return;
}
if (path.contentEquals("/schema.json")) {
executeQuery(request, response, GraphQLConstants.SCHEMA_READ_QUERY, null, null);
} else if (path.contentEquals("/console") && develMode) {
} else if (path.contentEquals("/console")) {
response.setContentType(MimeTypes.TEXT_HTML);
try (InputStream consolePageStream = WebServiceUtils.openStaticResource("static/graphiql/index.html")) {
IOUtils.copyStream(consolePageStream, response.getOutputStream());
@@ -44,7 +44,7 @@ public class GraphQLLoggerUtil {
public static String getUserId(@NotNull HttpServletRequest request) {
WebSession session = getWebSession(request);
if (session == null) {
return null;
return QMConstants.QM_UNAUTHORIZED_USER;
}
String userId = session.getUserContext().getUserId();
if (userId == null) {
@@ -19,8 +19,8 @@ package io.cloudbeaver.server.jetty;
import io.cloudbeaver.service.DBWServletContext;
import jakarta.servlet.http.HttpServlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.ee11.servlet.ServletContextHandler;
import org.eclipse.jetty.ee11.servlet.ServletHolder;
public class CBJettyServletContext implements DBWServletContext {
private final ServletContextHandler contextHandler;
@@ -18,8 +18,8 @@ package io.cloudbeaver.server.jetty;
import io.cloudbeaver.service.DBWWebSocketContext;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.ee11.servlet.ServletContextHandler;
import org.eclipse.jetty.ee11.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.Server;
import org.jkiss.code.NotNull;
@@ -17,7 +17,7 @@
package io.cloudbeaver.server.jetty;
import io.cloudbeaver.server.WebApplication;
import org.eclipse.jetty.ee10.servlet.SessionHandler;
import org.eclipse.jetty.ee11.servlet.SessionHandler;
import org.jkiss.code.NotNull;
public class CBSessionHandler extends SessionHandler {
@@ -17,7 +17,7 @@
package io.cloudbeaver.server.jetty;
import io.cloudbeaver.server.WebApplication;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee11.servlet.ServletContextHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.session.DefaultSessionCache;
import org.eclipse.jetty.session.DefaultSessionIdManager;
@@ -17,16 +17,16 @@
package io.cloudbeaver.server.servlets;
import com.google.gson.stream.JsonWriter;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebAppUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee11.servlet.DefaultServlet;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.HttpConstants;
import java.io.IOException;
import java.util.LinkedHashMap;
@@ -39,7 +39,7 @@ public class WebStatusServlet extends DefaultServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(CBConstants.APPLICATION_JSON);
response.setContentType(HttpConstants.CONTENT_TYPE_JSON);
response.setHeader("Access-Control-Allow-Origin", "*");
Map<String, Object> infoMap = new LinkedHashMap<>();
infoMap.put("health", "ok");
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.websocket.WSUtils;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import org.jkiss.dbeaver.model.websocket.registry.WSClientEventDescriptor;
import org.jkiss.dbeaver.model.websocket.registry.WSEventRegistry;
import org.jkiss.utils.CommonUtils;
public class CBClientEventProcessor {
@@ -50,6 +52,17 @@ public class CBClientEventProcessor {
return;
}
// Handle with custom handlers
WSClientEventDescriptor ced = WSEventRegistry.getInstance().getClientEvent(clientEvent.getId());
if (ced != null && ced.getHandler() != null) {
try {
ced.getHandler().handleEvent(webSession, clientEvent);
} catch (Exception e) {
log.error("Error handling event '" + clientEvent.getId() + "'", e);
}
return;
}
switch (clientEvent.getId()) {
case WSSubscribeOnTopicClientEvent.ID: {
webSession.getEventsFilter().subscribeOnEventTopic(clientEvent.getTopicId());
@@ -91,6 +104,13 @@ public class CBClientEventProcessor {
}
break;
}
case WSSessionCancelActionClientEvent.ID: {
if (webSession instanceof WebSession session) {
var cancelActionEvent = (WSSessionCancelActionClientEvent) clientEvent;
session.handleActionCancelledEvent(cancelActionEvent.getActionId());
}
break;
}
default:
var e = new DBWebException("Unknown client event: " + clientEvent.getId());
log.error(e.getMessage(), e);
@@ -93,7 +93,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
return;
}
resp.setContentType(CBConstants.APPLICATION_JSON);
resp.setContentType(HttpConstants.CONTENT_TYPE_JSON);
WSUtils.clientGson.toJson(Map.of("events", events), resp.getWriter());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -187,8 +187,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
private void addCorsHeaders(@NotNull HttpServletRequest req, @NotNull HttpServletResponse resp) {
String origin = req.getHeader("Origin");
boolean develMode = ServletAppUtils.getServletApplication().getServerConfiguration().isDevelMode();
if (!develMode || origin == null) {
if (origin == null) {
return;
}
@@ -239,7 +238,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
) {
try {
resp.setStatus(status);
resp.setContentType(CBConstants.APPLICATION_JSON);
resp.setContentType(HttpConstants.CONTENT_TYPE_JSON);
Map<String, Object> error = Map.of(
"error", message,
@@ -23,8 +23,7 @@ import jakarta.servlet.http.HttpSession;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.ee10.websocket.jakarta.server.internal.JakartaWebSocketCreator;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.ee11.websocket.jakarta.server.internal.JakartaWebSocketCreator;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -89,7 +88,7 @@ public class CBWebSocketServerConfigurator extends ServerEndpointConfig.Configur
throw new RuntimeException(e.getMessage(), e);
}
if (sec.getUserProperties().get(PROP_WEB_SESSION) == null) {
throw new BadMessageException("No web session found for websocket request");
throw new RuntimeException("No web session found for websocket request");
}
}
@@ -97,8 +96,8 @@ public class CBWebSocketServerConfigurator extends ServerEndpointConfig.Configur
private String getSessionId(@NotNull HandshakeRequest request) {
// complex auth uses bearer authentication
List<String> authHeaders = WSClientUtils.getHeaders(request.getHeaders(), HttpConstants.HEADER_AUTHORIZATION);
if (!CommonUtils.isEmpty(authHeaders) && authHeaders.get(0).startsWith("Bearer ")) {
return authHeaders.get(0).substring(7);
if (!CommonUtils.isEmpty(authHeaders) && authHeaders.getFirst().startsWith(HttpConstants.BEARER_PREFIX)) {
return authHeaders.getFirst().substring(7);
}
return request.getHttpSession() instanceof HttpSession httpSession ? httpSession.getId() : null;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.rm.RMConstants;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
@@ -160,7 +161,12 @@ public interface DBWServiceCore extends DBWService {
) throws DBWebException;
@WebAction
WebNetworkEndpointInfo testNetworkHandler(@NotNull WebSession webSession, @NotNull WebNetworkHandlerConfigInput nhConfig) throws DBWebException;
WebNetworkEndpointInfo testNetworkHandler(
@NotNull WebSession webSession,
@Nullable String projectId,
@Nullable String connectionId,
@NotNull WebNetworkHandlerConfigInput nhConfig
) throws DBWebException;
@WebAction
WebConnectionInfo closeConnection(@NotNull WebSession webSession, @Nullable String projectId, @NotNull String connectionId) throws DBWebException;
@@ -213,6 +219,14 @@ public interface DBWServiceCore extends DBWService {
@NotNull String id
) throws DBWebException;
@NotNull
Map<String, String> setObjectSettingsForDatasource(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull String objectId,
@NotNull Map<String, String> settings
) throws DBException;
///////////////////////////////////////////
// Async tasks
@@ -139,7 +139,10 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
getWebSession(env), getProjectReference(env), getArgumentVal(env, "config")
))
.dataFetcher("testNetworkHandler", env -> getService(env).testNetworkHandler(
getWebSession(env), new WebNetworkHandlerConfigInput(getArgument(env, "config"))
getWebSession(env),
getProjectReference(env),
getArgument(env, "connectionId"),
new WebNetworkHandlerConfigInput(getArgument(env, "config"))
))
.dataFetcher("closeConnection", env -> getService(env).closeConnection(
getWebSession(env),
@@ -160,6 +163,12 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
getArgumentVal(env, "id")
)
)
.dataFetcher("setObjectSettingsForDatasource", env -> getService(env).setObjectSettingsForDatasource(
getWebSession(env),
getProjectReference(env),
getArgumentVal(env, "id"),
getArgumentVal(env, "settings")
))
.dataFetcher("asyncTaskInfo", env -> getService(env).getAsyncTaskInfo(
getWebSession(env),
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,12 +58,14 @@ import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.secret.DBSSecretValue;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettingsUtils;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
import org.jkiss.dbeaver.registry.project.BaseProjectSettings;
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
import org.jkiss.dbeaver.registry.settings.ProductSettingsRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -398,7 +400,9 @@ public class WebServiceCore implements DBWServiceCore {
dataSourceContainer,
dataSourceContainer.getConnectionConfiguration()
);
saveConfig[0] = true;
// don't understand why whe need to set this flag here
// need to discover it, maybe we can avoid it at all
saveConfig[0] = sharedCredentials;
}
if (WebServiceUtils.isGlobalProject(dataSourceContainer.getProject())) {
// Do not flush config for global project (only admin can do it - CB-2415)
@@ -462,12 +466,21 @@ public class WebServiceCore implements DBWServiceCore {
throw new DBException("Node '" + nodePath + "' is not a datasource node");
}
DBPDataSourceContainer dataSourceTemplate = dbnDataSource.getDataSourceContainer();
if (!dataSourceTemplate.isExternallyProvided()) {
if (!CommonUtils.equalObjects(dataSourceTemplate.getProject(), project)) {
throw new DBException("Copying connection to another project is not allowed");
}
}
DataSourceDescriptor newDataSource = dataSourceRegistry.createDataSource(dataSourceTemplate);
ServletApplication app = ServletAppUtils.getServletApplication();
if (app instanceof WebApplication webApplication) {
newDataSource.setNavigatorSettings(webApplication.getAppConfiguration().getDefaultNavigatorSettings());
newDataSource.setNavigatorSettings(
dataSourceTemplate.isExternallyProvided() ?
webApplication.getAppConfiguration().getDefaultNavigatorSettings() :
dataSourceTemplate.getNavigatorSettings().getOriginalSettings()
);
}
WebConnectionConfig config = project.getConnectionConfigInput(connectionConfig);
@@ -580,6 +593,8 @@ public class WebServiceCore implements DBWServiceCore {
@Override
public WebNetworkEndpointInfo testNetworkHandler(
@NotNull WebSession webSession,
@Nullable String projectId,
@Nullable String connectionId,
@NotNull WebNetworkHandlerConfigInput nhConfig
) throws DBWebException {
DBRProgressMonitor monitor = webSession.getProgressMonitor();
@@ -600,7 +615,13 @@ public class WebServiceCore implements DBWServiceCore {
try {
monitor.subTask("Initialize tunnel");
DBWHandlerConfiguration configuration = new DBWHandlerConfiguration(handlerDescriptor, null);
DBWHandlerConfiguration currentConnectionHandlerConfig =
getCurrentConnectionHandlerConfig(webSession, projectId, connectionId, nhConfig.getId());
DBWHandlerConfiguration configuration = currentConnectionHandlerConfig == null
? new DBWHandlerConfiguration(handlerDescriptor, null)
: new DBWHandlerConfiguration(currentConnectionHandlerConfig);
WebDataSourceUtils.updateHandlerConfig(configuration, nhConfig);
configuration.setSavePassword(true);
configuration.setEnabled(true);
@@ -631,6 +652,22 @@ public class WebServiceCore implements DBWServiceCore {
}
}
@Nullable
private DBWHandlerConfiguration getCurrentConnectionHandlerConfig(
@NotNull WebSession webSession,
@Nullable String projectId,
@Nullable String connectionId,
@NotNull String handlerConfigId
) throws DBWebException {
if (projectId == null || connectionId == null) {
return null;
}
WebSessionProjectImpl sessionProject = webSession.getAccessibleProjectById(projectId);
WebConnectionInfo connectionInfo = sessionProject.getWebConnectionInfo(connectionId);
DBPConnectionConfiguration connectionConfiguration = connectionInfo.getDataSourceContainer().getConnectionConfiguration();
return connectionConfiguration.getHandler(handlerConfigId);
}
@Override
public WebConnectionInfo closeConnection(
@NotNull WebSession webSession,
@@ -760,6 +797,25 @@ public class WebServiceCore implements DBWServiceCore {
return connectionInfo;
}
@Override
@NotNull
public Map<String, String> setObjectSettingsForDatasource(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull String objectId,
@NotNull Map<String, String> settings
) throws DBException {
WebSessionProjectImpl projectById = webSession.getProjectById(projectId);
if (projectById == null) {
throw new DBWebException("Project '" + projectId + "' not found");
}
BaseProjectSettings projectSettings = projectById.getProjectSettings();
projectSettings.setObjectSettings(SMObjectType.datasource, objectId, settings);
Map<String, String> objectSettings = projectSettings.getObjectSettings(SMObjectType.datasource, objectId);
return objectSettings == null ? new HashMap<>() : objectSettings;
}
@Override
public WebAsyncTaskInfo getAsyncTaskInfo(WebSession webSession, String taskId, Boolean removeOnFinish)
throws DBWebException {
@@ -145,11 +145,6 @@ public class WebDefaultFeatureProvider implements DBWFeatureProvider {
return features;
}
@NotNull
private String getProjectId(@NotNull DBNNode node) {
return node.getOwnerProject().getId();
}
private boolean canCreateConnectionFromFileName(String fileName) {
String fileExtension = IOUtils.getFileExtension(fileName);
if (CommonUtils.isEmpty(fileExtension)) {
@@ -169,11 +164,16 @@ public class WebDefaultFeatureProvider implements DBWFeatureProvider {
}
private boolean hasNodePermission(@NotNull WebSession webSession, @NotNull DBNNode node, @NotNull RMProjectPermission permission) {
WebProjectImpl project = webSession.getProjectById(node.getOwnerProject().getId());
if (project == null) {
return false;
RMProject rmProject;
if (node instanceof DBNResourceManagerResource rmr) {
rmProject = rmr.getRmProject();
} else {
WebProjectImpl project = webSession.getProjectById(node.getOwnerProject().getId());
if (project == null) {
return false;
}
rmProject = project.getRMProject();
}
RMProject rmProject = project.getRMProject();
return SMUtils.hasProjectPermission(webSession, rmProject, permission);
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,12 +72,25 @@ public interface DBWServiceSQL extends DBWService {
@NotNull WebSession session,
@NotNull List<String> nodePathList) throws DBWebException;
@NotNull
@WebAction
String generateEntityQuery(
@NotNull WebSession session,
@NotNull String generatorId,
@NotNull Map<String, Object> options,
@NotNull List<String> nodePathList) throws DBWebException;
@NotNull List<String> nodePathList,
@NotNull WebSQLGeneratorOptions options
) throws DBWebException;
@NotNull
@WebAction
String sqlGenerateResultSetQuery(
@NotNull WebSession session,
@NotNull WebSQLContextInfo sqlContext,
@NotNull String generatorId,
@NotNull String resultsId,
@NotNull List<WebSQLResultsRow> selectedRows,
@NotNull WebSQLGeneratorOptions options
) throws DBWebException;
@WebAction
WebSQLContextInfo createContext(
@@ -98,7 +98,7 @@ public class WebSQLCompletionContext implements SQLCompletionContext {
@Override
public boolean isSearchInsideNames() {
return false;
return true;
}
@Override
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ public class WebSQLDataFilter {
private int offset;
private int limit ;
private int limit;
private String where;
private final List<WebSQLDataFilterConstraint> constraints = new ArrayList<>();
@@ -48,7 +48,7 @@ public class WebSQLDataFilter {
this.limit = DEFAULT_ROWS_NUMBER;
}
public WebSQLDataFilter(Map<String, Object> filterProps) {
public WebSQLDataFilter(@NotNull Map<String, Object> filterProps) {
this.offset = CommonUtils.toInt(filterProps.get("offset"));
this.limit = CommonUtils.toInt(filterProps.get("limit"));
if (this.limit <= 0) {
@@ -59,10 +59,10 @@ public class WebSQLDataFilter {
this.where = CommonUtils.toString(filterProps.get("where"), null);
Object constraints = filterProps.get("constraints");
if (constraints instanceof Collection) {
for (Object constrItem : (Collection<?>)constraints) {
for (Object constrItem : (Collection<?>) constraints) {
if (constrItem instanceof Map) {
this.constraints.add(
new WebSQLDataFilterConstraint((Map<String, Object>)constrItem));
new WebSQLDataFilterConstraint((Map<String, Object>) constrItem));
}
}
}
@@ -92,7 +92,8 @@ public class WebSQLDataFilter {
return where;
}
public static WebSQLDataFilter from(DBDDataFilter filter) {
@NotNull
public static WebSQLDataFilter from(@NotNull DBDDataFilter filter) {
var webFilter = new WebSQLDataFilter();
webFilter.where = filter.getWhere();
for (DBDAttributeConstraint constraint : filter.getConstraints()) {
@@ -101,6 +102,7 @@ public class WebSQLDataFilter {
return webFilter;
}
@NotNull
public DBDDataFilter makeDataFilter(@Nullable WebSQLResultsInfo resultInfo) throws DBException {
DBDDataFilter dataFilter = new DBDDataFilter();
dataFilter.setWhere(where);
@@ -112,12 +114,17 @@ public class WebSQLDataFilter {
}
@NotNull
private List<DBDAttributeConstraint> mapWebConstrainsToDbdConstrains(@Nullable WebSQLResultsInfo resultInfo) throws DBException {
if (resultInfo == null) {
return getDbdConstraints();
}
List<DBDAttributeConstraint> constraints = generateEmptyConstrains(resultInfo);
fillEmptyConstrains(constraints);
return constraints;
}
@NotNull
public List<DBDAttributeConstraint> generateEmptyConstrains(@Nullable WebSQLResultsInfo resultInfo) {
if (resultInfo == null) {
return Collections.emptyList();
@@ -132,7 +139,7 @@ public class WebSQLDataFilter {
private void fillEmptyConstrains(@NotNull List<DBDAttributeConstraint> emptyConstraints) throws DBException {
for (WebSQLDataFilterConstraint webConstr : constraints) {
if(webConstr.getAttributePosition() >= emptyConstraints.size()) {
if (webConstr.getAttributePosition() >= emptyConstraints.size()) {
throw new DBException(MessageFormat.format("Incorrect column position ''{0}'' in order clause", webConstr.getAttributePosition()));
}
DBDAttributeConstraint dbConstr = emptyConstraints.get(webConstr.getAttributePosition());
@@ -140,6 +147,7 @@ public class WebSQLDataFilter {
}
}
@NotNull
private DBDAttributeConstraint fillEmptyConstraint(@NotNull DBDAttributeConstraint dbConstr,
@NotNull WebSQLDataFilterConstraint webConstr) {
dbConstr.setPlainNameReference(true);
@@ -159,4 +167,33 @@ public class WebSQLDataFilter {
}
return dbConstr;
}
private List<DBDAttributeConstraint> getDbdConstraints() {
List<DBDAttributeConstraint> dbConstraints = new ArrayList<>();
for (WebSQLDataFilterConstraint constraint : constraints) {
String attributeName = constraint.getAttributeName();
DBDAttributeConstraint dbConstraint = new DBDAttributeConstraint(
attributeName, getOriginalVisualPosition(constraint, attributeName)
);
fillEmptyConstraint(dbConstraint, constraint);
dbConstraints.add(dbConstraint);
}
return dbConstraints;
}
private static int getOriginalVisualPosition(WebSQLDataFilterConstraint constraint, String attributeName) {
Integer attributePosition = constraint.getAttributePosition();
if (CommonUtils.isEmpty(attributeName)) {
if (attributePosition == null) {
throw new IllegalArgumentException("Constraint must specify either attributePosition or attributeName");
}
throw new IllegalArgumentException(
"Can't convert constraint by attributePosition without result set metadata (attributeName is required)"
);
}
return attributePosition == null ? -1 : attributePosition;
}
}
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.service.sql;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.meta.Property;
@@ -30,6 +31,7 @@ public class WebSQLExecuteInfo {
private long duration;
private String filterText;
private String fullQuery;
private String originalQuery;
private WebSQLQueryResults[] results;
@Property
@@ -70,8 +72,11 @@ public class WebSQLExecuteInfo {
@Property
public String getOriginalQuery() {
//TODO implement
return null; // should return the original query without data filters
return originalQuery;
}
public void setOriginalQuery(@NotNull String originalQuery) {
this.originalQuery = originalQuery;
}
@Property
@@ -30,7 +30,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.eclipse.jetty.ee11.servlet.ServletContextRequest;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
@@ -0,0 +1,23 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.service.sql;
public record WebSQLGeneratorOptions(
boolean useFullyQualifiedNames,
boolean compactSql
) {
}
@@ -32,7 +32,7 @@ import org.jkiss.dbeaver.model.sql.SQLQueryParameter;
import org.jkiss.dbeaver.model.sql.SQLScriptContext;
import org.jkiss.dbeaver.model.websocket.event.WSEvent;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@@ -58,11 +58,11 @@ public class WebSQLParametersProvider extends SQLParametersProviderBase {
@NotNull List<SQLQueryParameter> parameters,
@NotNull Supplier<DBDDataReceiver> dataReceiverSupplier
) {
Map<String, Object> unsetParams = new LinkedHashMap<>();
List<WebSQLQueryParameterRecord> unsetParams = new ArrayList<>();
for (SQLQueryParameter param : parameters) {
if (!param.isVariableSet()) {
// Empty value for now, maybe in future we can set some defaults here
unsetParams.put(param.getName(), "");
unsetParams.add(new WebSQLQueryParameterRecord(param.getName(), param.getValue()));
}
}
if (unsetParams.isEmpty()) {
@@ -296,7 +296,17 @@ public class WebSQLProcessor implements WebSessionProvider {
if (sqlOutputLogReaderJob != null) {
sqlOutputLogReaderJob.join();
}
fillQueryResults(contextInfo, dataContainer, dbStat, hasResultSet, executeInfo, webDataFilter, dataFilter, dataFormat);
fillQueryResults(
contextInfo,
dataContainer,
dbStat,
hasResultSet,
executeInfo,
webDataFilter,
dataFilter,
dataFormat,
sqlElement.getOriginalText()
);
}
}
}
@@ -1066,7 +1076,9 @@ public class WebSQLProcessor implements WebSessionProvider {
@NotNull WebSQLExecuteInfo executeInfo,
@NotNull WebSQLDataFilter webDataFilter,
@NotNull DBDDataFilter dataFilter,
@Nullable WebDataFormat dataFormat) throws DBException {
@Nullable WebDataFormat dataFormat,
@NotNull String originalQuery
) throws DBException {
List<WebSQLQueryResults> resultList = new ArrayList<>();
int maxResultsCount = resolveMaxResultsCount(dataContainer.getDataSource());
@@ -1110,6 +1122,7 @@ public class WebSQLProcessor implements WebSessionProvider {
setResultFilterText(dbStat.getSession().getDataSource(), executeInfo, dataFilter);
executeInfo.setFullQuery(dbStat.getQueryString());
executeInfo.setOriginalQuery(originalQuery);
}
private void setResultFilterText(
@@ -34,7 +34,6 @@ import org.jkiss.dbeaver.model.meta.MetaData;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSEntityConstraintType;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.Method;
@@ -190,15 +189,11 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier();
if (rowIdentifier == null) {
webResultSet.setRowIdentifierState(WebSQLResultSetRowIdentifierState.METADATA_NOT_FOUND);
} else if (rowIdentifier.getUniqueKey().getConstraintType().equals(DBSEntityConstraintType.VIRTUAL_KEY)) {
webResultSet.setRowIdentifierState(WebSQLResultSetRowIdentifierState.VIRTUAL_KEY);
} else if (!rowIdentifier.isIncomplete() && rowIdentifier.isValidIdentifier()) {
webResultSet.setHasRowIdentifier(true);
webResultSet.setRowIdentifierState(WebSQLResultSetRowIdentifierState.PRIMARY_KEY);
List<WebSQLResultSetRowIdentifierAttribute> attributes = rowIdentifier.getAttributes().stream()
.map(a -> new WebSQLResultSetRowIdentifierAttribute(
a.getName(), a.getOrdinalPosition(), a.getLabel(), a.getDescription()
))
.map(a -> new WebSQLResultSetRowIdentifierAttribute(a.getName(), a.getOrdinalPosition()))
.toList();
String constraintTypeName = rowIdentifier.getUniqueKey().getConstraintType().getName();
webResultSet.setRowIdentifier(
@@ -184,8 +184,9 @@ public class WebSQLQueryResultSet {
/**
* Sets info about read-only status of a result set.
*/
public void setReadOnlyInfo(@NotNull DBCExecutionContext executionContext) {
public void setReadOnlyInfo(@Nullable DBCExecutionContext executionContext) {
this.readOnly = DBExecUtils.isResultSetReadOnly(executionContext);
this.readOnlyStatus = DBExecUtils.getResultSetReadOnlyStatus(executionContext.getDataSource().getContainer());
this.readOnlyStatus = DBExecUtils.getResultSetReadOnlyStatus(
executionContext == null ? null : executionContext.getDataSource().getContainer());
}
}
@@ -29,6 +29,7 @@ import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.HttpConstants;
import org.jkiss.utils.IOUtils;
import java.io.IOException;
@@ -64,7 +65,7 @@ public class WebSQLResultServlet extends WebServiceServletBase {
}
Path dataFile = getDataFile(request);
session.addInfoMessage("Download LOB file ...");
response.setHeader("Content-Type", "application/octet-stream");
response.setHeader(HttpConstants.HEADER_CONTENT_TYPE, "application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + dataFile.getFileName().toString() + "\"");
response.setHeader("Content-Length", String.valueOf(Files.size(dataFile)));
response.setDateHeader("Expires", System.currentTimeMillis() + CBConstants.STATIC_CACHE_SECONDS * 1000);
@@ -48,8 +48,7 @@ public class WebSQLResultSetRowIdentifier {
public enum WebSQLResultSetRowIdentifierState {
METADATA_NOT_FOUND,
NONE,
PRIMARY_KEY,
VIRTUAL_KEY
PRIMARY_KEY
}
}
@@ -1,7 +1,6 @@
package io.cloudbeaver.service.sql;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
public class WebSQLResultSetRowIdentifierAttribute {
@@ -11,9 +10,7 @@ public class WebSQLResultSetRowIdentifierAttribute {
public WebSQLResultSetRowIdentifierAttribute(
@NotNull String name,
int ordinalPosition,
@NotNull String label,
@Nullable String description
int ordinalPosition
) {
this.name = name;
this.ordinalPosition = ordinalPosition;

Some files were not shown because too many files have changed in this diff Show More