mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
165
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ac2d73935 | ||
|
|
dcbf3f4d6d | ||
|
|
861c2eeb3d | ||
|
|
ad3b95f2a7 | ||
|
|
82775f9dc2 | ||
|
|
f95bb8c041 | ||
|
|
134a77e5bd | ||
|
|
2f04cc5bb0 | ||
|
|
d1ab50783e | ||
|
|
a12ab4f2a2 | ||
|
|
b71010958c | ||
|
|
ac2cb461f3 | ||
|
|
0a1616795d | ||
|
|
571882eab0 | ||
|
|
5b8c88c36a | ||
|
|
1906f9d165 | ||
|
|
5e36ef4e27 | ||
|
|
7cd9ae83bc | ||
|
|
bcf4a2b581 | ||
|
|
50f63caa4f | ||
|
|
a68a29aeb0 | ||
|
|
f5c31fbc07 | ||
|
|
aae05e4769 | ||
|
|
39777dc5fa | ||
|
|
4475513341 | ||
|
|
f4b55add99 | ||
|
|
6d13275021 | ||
|
|
2650b4dccd | ||
|
|
50f6835caf | ||
|
|
3e9e71ca6d | ||
|
|
d48cd7a0a7 | ||
|
|
f37f173c49 | ||
|
|
63b25d7cb1 | ||
|
|
67df14033c | ||
|
|
75a64c1c2d | ||
|
|
28fb7f2264 | ||
|
|
5176519fdd | ||
|
|
4d197c9548 | ||
|
|
56daa059a7 | ||
|
|
3491fb42d7 | ||
|
|
bb9872dc2e | ||
|
|
043d55c395 | ||
|
|
b5c6cbfe82 | ||
|
|
152ab83006 | ||
|
|
bea2451776 | ||
|
|
2d0b7d428c | ||
|
|
9078a379b0 | ||
|
|
bc2c204a61 | ||
|
|
2ff806ed7c | ||
|
|
5d2b246d61 | ||
|
|
212623c0a8 | ||
|
|
d08c191a08 | ||
|
|
6b3e911534 | ||
|
|
620a5cb73b | ||
|
|
4cf41da82c | ||
|
|
3844792b84 | ||
|
|
e7f2b8e0a6 | ||
|
|
f2b30d0f1f | ||
|
|
2781ec1eaa | ||
|
|
707cb3ea41 | ||
|
|
5be49b8a7e | ||
|
|
317750ea7d | ||
|
|
5fb7234e24 | ||
|
|
7a93d56459 | ||
|
|
bd1e926929 | ||
|
|
170650e6c2 | ||
|
|
ffc48e1979 | ||
|
|
c4b0dd4650 | ||
|
|
2d955087ea | ||
|
|
b5289c7865 | ||
|
|
740599f41d | ||
|
|
16915940ba | ||
|
|
1c789cf49b | ||
|
|
9474ec9718 | ||
|
|
46a07033ad | ||
|
|
eb907e95f4 | ||
|
|
cb01f6a988 | ||
|
|
b2065ffe62 | ||
|
|
9e8f4b83a6 | ||
|
|
5170fa7dfc | ||
|
|
ad26e0e849 | ||
|
|
eed9cd979e | ||
|
|
1b8ea64cd0 | ||
|
|
fb43b0f567 | ||
|
|
317ba5486a | ||
|
|
4d4049fcd1 | ||
|
|
27cfdb405f | ||
|
|
f1dc4e07bb | ||
|
|
57cc6e4808 | ||
|
|
6bfe58506f | ||
|
|
1d6e5c6ce6 | ||
|
|
3a60e04a84 | ||
|
|
8b33e3a08f | ||
|
|
b021ceec4b | ||
|
|
d318579745 | ||
|
|
8d17ed0ecd | ||
|
|
3199a99b29 | ||
|
|
39008f7170 | ||
|
|
8bf33d539f | ||
|
|
42f6720acd | ||
|
|
0a3cb12c0b | ||
|
|
202823e731 | ||
|
|
ed088fe923 | ||
|
|
df21c3396a | ||
|
|
90ccdc6432 | ||
|
|
dba371a320 | ||
|
|
d96cd8f0d2 | ||
|
|
bd6e7d48f3 | ||
|
|
8b41e4530f | ||
|
|
a555f1e759 | ||
|
|
fdaa40e431 | ||
|
|
4c2f9d92e5 | ||
|
|
a92011eee6 | ||
|
|
61a7757a0e | ||
|
|
a53ca3592f | ||
|
|
9e682617e6 | ||
|
|
16dda810d7 | ||
|
|
6042f16342 | ||
|
|
0afdd06c80 | ||
|
|
3158aebca4 | ||
|
|
99866fdc12 | ||
|
|
a58eb59a32 | ||
|
|
eda1f8720f | ||
|
|
f54f377344 | ||
|
|
81a9be17b8 | ||
|
|
af42d7f50a | ||
|
|
d083ac1330 | ||
|
|
cdb450dfde | ||
|
|
d0d9ee19ab | ||
|
|
9327ce0ac2 | ||
|
|
1fb9dd7b5a | ||
|
|
e82cac479b | ||
|
|
bf6afb0340 | ||
|
|
4779c3b405 | ||
|
|
31ee01b6fa | ||
|
|
025944a867 | ||
|
|
caee9914b8 | ||
|
|
6e2b1b7a17 | ||
|
|
a379c2d7ba | ||
|
|
8b1054a148 | ||
|
|
7fdc8f7e61 | ||
|
|
32bebd4bad | ||
|
|
4221ea7fe0 | ||
|
|
af11aaa8fe | ||
|
|
5c58343a10 | ||
|
|
a565beb777 | ||
|
|
9df99fb62e | ||
|
|
b3127b9aa7 | ||
|
|
f8677a0689 | ||
|
|
f22a936507 | ||
|
|
b5858637d6 | ||
|
|
475c8129fc | ||
|
|
50d846d087 | ||
|
|
16bd3ca575 | ||
|
|
927184e009 | ||
|
|
d4d9081936 | ||
|
|
a0bcfba73d | ||
|
|
09f4828006 | ||
|
|
cde64e255b | ||
|
|
7b134cef81 | ||
|
|
fde7502df8 | ||
|
|
955b28c520 | ||
|
|
5cfc37e26f | ||
|
|
42cb6b3a9d | ||
|
|
ad245e9f4c |
@@ -43,6 +43,24 @@ jobs:
|
||||
uses: ./.github/workflows/frontend-lint.yml
|
||||
secrets: inherit
|
||||
|
||||
required-builds:
|
||||
name: Required Builds
|
||||
if: ${{ always() && github.event.pull_request.draft != true }}
|
||||
needs: [build-server, build-frontend]
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Verify required builds
|
||||
env:
|
||||
SERVER_RESULT: ${{ needs.build-server.result }}
|
||||
FRONTEND_RESULT: ${{ needs.build-frontend.result }}
|
||||
run: |
|
||||
if [[ "$SERVER_RESULT" != "success" || "$FRONTEND_RESULT" != "success" ]]; then
|
||||
echo "Server build: $SERVER_RESULT"
|
||||
echo "Frontend build: $FRONTEND_RESULT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# call-frontend-tests:
|
||||
# name: Frontend Unit Tests
|
||||
# needs: call-frontend-build
|
||||
|
||||
Vendored
+1
-1
@@ -48,6 +48,7 @@
|
||||
"-Declipse.pde.launch=true",
|
||||
"-XstartOnFirstThread",
|
||||
"-Dfile.encoding=UTF-8",
|
||||
"--enable-native-access=ALL-UNNAMED",
|
||||
"--add-modules=ALL-DEFAULT",
|
||||
"--add-opens=java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
@@ -64,7 +65,6 @@
|
||||
"--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED",
|
||||
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED",
|
||||
"--add-opens=java.base/sun.security.ssl=ALL-UNNAMED",
|
||||
"--add-opens=java.base/sun.security.action=ALL-UNNAMED",
|
||||
"--add-opens=java.base/sun.security.util=ALL-UNNAMED",
|
||||
"--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED",
|
||||
"--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# CloudBeaver
|
||||
|
||||
## What is CloudBeaver?
|
||||
|
||||
CloudBeaver is an open-source, web-based database management application. Its modular frontend provides the database navigator, SQL editor, data editor, administration, and related features, and communicates with the CloudBeaver server through GraphQL.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Scope
|
||||
|
||||
- These instructions apply to the frontend under `webapp/` in CloudBeaver Community Edition and to any other CloudBeaver edition or product that shares this frontend architecture.
|
||||
- Do not modify `server/`, `deploy/`, or backend configuration unless the task explicitly requires it. Backend GraphQL schemas are an input to frontend code generation, not part of the default edit scope.
|
||||
- Treat `package.json`, `.yarnrc.yml`, and the frontend CI workflows as the source of truth for tool and runtime versions; do not duplicate their version numbers in documentation.
|
||||
|
||||
### Project map
|
||||
|
||||
- `webapp/package.json`: Yarn Plug'n'Play workspace root and repository-wide frontend scripts.
|
||||
- `webapp/packages/core-*`: shared application foundations, services, UI primitives, state, and infrastructure.
|
||||
- `webapp/packages/plugin-*`: feature modules. A package normally registers services and bootstraps in `src/module.ts` and exposes its public API from `src/index.ts`.
|
||||
- `webapp/packages/product-*`: CE product composition and Vite entry points. `product-default` is the runnable/bundled frontend; `product-default-impl` selects the CE plugin set.
|
||||
- `webapp/packages/core-sdk`: GraphQL operations and generated client types.
|
||||
- `webapp/common-typescript/@dbeaver/*` and `webapp/common-react/@dbeaver/*`: shared DBeaver utilities and React components used by the workspace.
|
||||
- Tests are colocated with source as `*.test.ts` or `*.test.tsx`; styles are usually colocated CSS modules.
|
||||
|
||||
### Stack
|
||||
|
||||
TypeScript ES modules, React, MobX, the project DI/module registry, GraphQL Code Generator, Vite, Yarn workspaces with PnP, Vitest and Testing Library, ESLint, Prettier, CSS Modules, and Tailwind-based theming.
|
||||
|
||||
### Setup and common commands
|
||||
|
||||
Use the Node version configured by frontend CI and the Yarn version pinned in `webapp/package.json`. Run frontend commands from `webapp/` unless noted.
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
cd webapp
|
||||
yarn install --immutable
|
||||
```
|
||||
|
||||
```sh
|
||||
# Start the frontend dev server; a compatible CloudBeaver API must run separately.
|
||||
(cd packages/product-default && yarn dev)
|
||||
|
||||
# From webapp/: repository-wide checks.
|
||||
yarn lint
|
||||
yarn test
|
||||
yarn validate-dependencies
|
||||
|
||||
# Production frontend build.
|
||||
(cd packages/product-default && yarn bundle)
|
||||
```
|
||||
|
||||
For a focused check, use a package's existing script, for example `yarn workspace @cloudbeaver/core-utils test` or `yarn workspace @cloudbeaver/plugin-sql-editor lint`. Do not invent a package script that is absent from its `package.json`.
|
||||
|
||||
### Working conventions
|
||||
|
||||
- Put changes in the smallest package that owns the behavior. Prefer existing `core-*` abstractions and UI primitives before adding cross-package helpers.
|
||||
- Keep dependencies directed from plugins to core: `core-*` packages must never import or depend on `plugin-*` packages. Plugins may use public APIs from core packages and from other plugins, subject to the administration boundary and the no-cycles rule below.
|
||||
- Put administration-only behavior in a separate package whose name ends with `-administration`. Public/non-administration plugins must not import or depend on administration packages.
|
||||
- Keep the workspace package dependency graph acyclic; do not introduce direct or indirect circular dependencies.
|
||||
- Import workspace packages through their public `@cloudbeaver/*` or `@dbeaver/*` exports; never reach into another package's `src/` directory.
|
||||
- When adding a workspace dependency, update the package's `package.json` through Yarn. The configured `ts-project-linker` synchronizes `tsconfig.json` project references during a non-immutable install; edit references manually only if automatic linking cannot complete. Run `yarn validate-dependencies` afterward.
|
||||
- Register services and bootstraps through the package's `src/module.ts`. Change product composition only when enabling or disabling a package for the CE product.
|
||||
- Put user-visible text in the package localization files and follow the neighboring `LocaleService` pattern; do not hardcode UI copy in components.
|
||||
- Add or change GraphQL operations as `.gql` files in `core-sdk`. Regenerate through `yarn workspace @cloudbeaver/core-sdk gql:gen`; never edit the ignored generated `src/sdk.ts` manually.
|
||||
- Preserve valid license headers on existing source files. When modifying an existing source file, update the ending copyright year to the current four-digit calendar year while keeping its original starting year. For new source files, use the template below and replace `<CURRENT_YEAR>` with the current year; never leave the placeholder in committed code. Use the repository ESLint and Prettier configuration rather than manual formatting rules.
|
||||
- Do not edit or commit generated/install artifacts such as `node_modules/`, `.pnp.*`, `lib/`, `dist/`, `coverage/`, or `allure-results/`.
|
||||
- Add focused tests for behavior changes. Before handoff, run lint and tests for affected packages; run the repository-wide checks and product bundle for cross-cutting or product-composition changes.
|
||||
|
||||
### License header for new source files
|
||||
|
||||
```text
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-<CURRENT_YEAR> DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
```
|
||||
|
||||
## Backend
|
||||
|
||||
- Before backend work, read and follow `../dbeaver/AGENTS.md` for inherited Java and Tycho conventions.
|
||||
- Cloudbeaver does not use SWT or Eclipse RCP, and its backend is headless.
|
||||
- Configuration file is generated using `apps/config-generator`. For making changes there, update `config/template/cloudbeaver-base.conf` or use patches (for specific product parameters).
|
||||
- The backend must support at least the following databases: PostgreSQL, MySQL, Oracle, and SQL Server. Support for additional databases is optional.
|
||||
|
||||
### GraphQL API
|
||||
|
||||
- Use consistent names. IDs must use type `ID`; ID arguments and inputs must end with `Id`, for example `projectId`.
|
||||
- Top-level methods follow `{pluginId}{methodName}`, for example `authLogin`, `rmListProjects`, and `navNodeChildren`. Mark new public API with `@since`.
|
||||
- Preserve released APIs: deprecate instead of renaming or removing, include the deprecation version, and remove only after more than one year. EA-only APIs may change before public release.
|
||||
- New arguments and input fields added to a released API must be optional.
|
||||
|
||||
### License header
|
||||
- Please use header from ../dbeaver/docs/license_header.txt for backend source files.
|
||||
@@ -28,94 +28,79 @@ You can see a live demo of CloudBeaver server here: https://demo.cloudbeaver.io
|
||||
|
||||
## Changelog
|
||||
|
||||
### 26.1.1 2026-06-22
|
||||
### 26.2.0 2026-08-31
|
||||
|
||||
### Changes since 26.1.0:
|
||||
|
||||
- General: Added a "Project Info" tab containing the project description to the project context menu.
|
||||
- Security:
|
||||
- Fixed a cross-site scripting vulnerability caused by unsanitized user input in the web interface.
|
||||
- Resolved a security flaw related to user-controlled file paths by adding strict validation to prevent unauthorized resource access.
|
||||
- Fixed the critical vulnerability (CVE-2026-9277) in the shell-quote library (updated to version 1.8.4).
|
||||
|
||||
### 26.1.0 2026-06-01
|
||||
|
||||
### Changes since 26.0.0:
|
||||
### Changes since 26.1.0
|
||||
|
||||
- AI Assistant:
|
||||
- Added AI Chat to help users generate and fix queries and explore data more easily. It’s integrated with the SQL Editor to run generated queries instantly. All AI features can be turned off in the Server configuration.
|
||||
- Added the ability to use multiple AI configurations for working with the AI chat. Administrators can define tokens, engines and models for OpenAI and Copilot in the Administration panel for each AI profile separately. After that, users can switch between these profiles while using AI features.
|
||||
- Added the ability to cancel a response in the AI Chat to stop response generation when needed.
|
||||
- The "Endpoint" setting was renamed to "API Base URL" for the OpenAI provider.
|
||||
- Added AI engine icons to AI configuration profiles, making it easier to identify AI providers in the Administration panel and AI Chat configuration.
|
||||
- Administration:
|
||||
- Added support for custom startup arguments when running CloudBeaver in a container. Administrators can specify configuration file locations to adapt the application to their environment. (thanks to @MalteHei)
|
||||
- Updated the manifest.webmanifest file to generate dynamically based on the CLOUDBEAVER_ROOT_URI environment variable. (thanks to @houssemexo26)
|
||||
- Added the database version and the driver version to diagnostic logs.
|
||||
- Fixed the issue of fetching groups during LDAP authorization. CloudBeaver now provides all matched groups regardless of fetching errors.
|
||||
- Added the Last Login time to the user form and the user table in the administration part.
|
||||
- Authorization:
|
||||
- Added HTTP header authentication support for GraphQL queries. Users can provide API tokens via Authorization or X-Api-Key headers, allowing external data tools to authenticate without modifying request bodies.
|
||||
- Fixed a bug in the authentication form: the existing password is now required for password changes.
|
||||
- SQL Editor:
|
||||
- Updated the SQL Editor to use real tab characters for indentation. The application displayed tabs as four-space gaps with a distinct visual marker.
|
||||
- Added an ability to view and edit scripts without connecting to the database.
|
||||
- Added the ability to open generated queries right from the Generate SQL window.
|
||||
- Added fuzzy search to SQL Editor auto-completion for object names. The feature allows users to find tables and containers even with typos or incorrect letter ordering.
|
||||
- Added the tooltip about quoting rules for the Bind variables parameters dialog.
|
||||
- Fixed keyword autocompletion for partially typed keywords to avoid redundant letters.
|
||||
- Added the option to upload files for certificates and keys in the SSL authorization configuration.
|
||||
- Fixed an issue causing the application to lose connection to the database after extended uptime. Access is kept automatically during long-running sessions.
|
||||
- Data Editor:
|
||||
- Added the ability to copy-paste multiple cells at once. Pasted values will be distributed across selected cells.
|
||||
- Added an ability to add, duplicate, delete, or set values to NULL in multiple highlighted rows via the Data Editor context menu.
|
||||
- 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.
|
||||
- Data Editor started to keep the state of column configurations, such as filters, sorting, and ordering, after the reconnect, page refresh, and re-login.
|
||||
- Added key column mark for tables with unique keys in the data grid.
|
||||
- Added more settings to the Generated SQL window, including the ability to select fully qualified names for query or use compact SQL formatting.
|
||||
- Added a DDL generation option to the Data Editor and Result Set context menu.
|
||||
- Fixed copy and paste behavior for multiline and special characters in the Data Editor. Added visual glyphs to indicate line breaks and tabs in the data grid.
|
||||
- Fixed the ability to copy Boolean values in the clipboard in the Data Editor.
|
||||
- Fixed the ability to calculate row count for non-standard SELECT statements. (thanks to @fdcastel)
|
||||
- Navigator tree:
|
||||
- Reorganized the context menu on the connection level to make it more compact.
|
||||
- Added support for special symbols (pipe, comma, and asterisk) for the search field.
|
||||
- Removed the "Folders" setting from the settings list for the Navigator Tree.
|
||||
- Added the References panel to the Data Editor. The panel displays related records from connected tables. Users can explore table relationships directly.
|
||||
- Added the ability to export generated SQL to a .sql file from the Generate SQL dialog.
|
||||
- Fixed copying of large JSON values in the Data Editor - now the full value can be copied.
|
||||
- Resolved an issue where the Data Editor ignored the text preview's maximum length quota. Increasing this quota now enables the ability to edit large values.
|
||||
- Data Transfer:
|
||||
- Added advanced data import settings, including database-specific replace methods, transaction support, bulk loading, and the option to use a separate connection for data import.
|
||||
- Improved memory usage when importing large CSV files to improve the application performance.
|
||||
- Accessibility:
|
||||
- Added the Skip to content option for quick keyboard access to the Navigator Tree, editors, and shortcuts list tab to improve application accessibility.
|
||||
- Improved keyboard navigation for context menu and buttons for Data Editor, SQL Editor, and Navigator tree.
|
||||
- Added the Ctrl + / keyboard shortcut to open the context menu for selected cells in the Data Editor.
|
||||
- Fixed the ability to switch tabs in the Data Editor using the keyboard.
|
||||
- Fixed contrast for elements across different application parts in the light and dark themes to meet WCAG requirements.
|
||||
- Improved accessibility for the search on the Users section in the Administration panel. The application allows focusing on the search field with the Tab key.
|
||||
- Added a loader to the Save button in the Administration panel to improve indication of the long-performing operations.
|
||||
- Improved drag-and-drop in the Navigator Tree for objects by enlarging the auto-scroll trigger areas.
|
||||
- Added the ability to expand and collapse code block elements, including JSON, using the keyboard in the Data Editor.
|
||||
- Fixed shortcuts behavior for AZERTY keyboards. Now shortcuts like Ctrl/Cmd + Z work correctly.
|
||||
- Added the ability to use the Enter or space keys to turn checkboxes on/off in the connection properties tab.
|
||||
- General:
|
||||
- Marked icons for non-saved (temporary) scripts with the hourglass symbol to add visual distinction from the saved ones.
|
||||
- Added the ability to set to NULL any value in the Driver Properties.
|
||||
- Fixed custom driver properties display for the connection page.
|
||||
- Fixed data export in SQL format for values containing single quotes.
|
||||
- Databases and drivers:
|
||||
- ClickHouse:
|
||||
- Fixed the display of Array(JSON) types in the data grid.
|
||||
- Updated driver version to 0.9.7.
|
||||
- Fixed IP address display in the Data Editor by removing the leading slash.
|
||||
- Added support for the "prompt=false" connection property for DB2 for IBM i to turn off interactive prompts and prevent related errors.
|
||||
- DuckDB:
|
||||
- Fixed map rendering for GEOMETRY types with CRS parameters.
|
||||
- Fixed geometry type display for database versions > 1.5.
|
||||
- H2 database:
|
||||
- Added new embedded driver version 2.4.2.
|
||||
- Updated server driver to version 2.4.2.
|
||||
- Firebird: Expanded database-specific SQL dialect coverage for the SQL Editor. Added more keywords, built-in functions, and SQL generators.
|
||||
- MySQL: Fixed query boundaries recognition in the SQL Editor for scripts with the DELIMITER keyword.
|
||||
- PostgreSQL:
|
||||
- Updated driver to version 42.7.11
|
||||
- Fixed missing INOUT parameters in CHECK generation for procedures and functions.
|
||||
- Fixed handling of 24:xx values in time columns.
|
||||
- Fixed an error when connecting via URL if the database name contains a hyphen.
|
||||
- SQL Server:
|
||||
- Fixed DDL generation for external tables.
|
||||
- Fixed the database connectivity issue after the Java update.
|
||||
- Added connection types to visually distinguish database connections using background colors across the Database Navigator and application UI.
|
||||
- Improved SQL autocomplete suggestion ordering for object names. Now suggestions are sorted alphabetically.
|
||||
- Added a Project Info tab containing the project description to the project context menu.
|
||||
- Fixed synchronization between devices. Users see updated connections and folders in other devices without manual refreshes.
|
||||
- Databases:
|
||||
- ClickHouse
|
||||
- The driver was updated to version 0.10.0
|
||||
- Improved JSON display in the Value panel
|
||||
- Added support for the Map data type visualization for the Data Editor
|
||||
- The Databend driver was updated to version 0.4.8.
|
||||
- LibSQL: Added support for database views.
|
||||
- MySQL: Fixed support for comments starting with '#' symbol in the SQL Editor.
|
||||
- PostgreSQL
|
||||
- Updated the driver to version 42.7.13.
|
||||
- Added a Show full DDL option for schemas and tables to generate complete DDL, including objects within a schema and object comments and privileges.
|
||||
- Fixed an error that occurred when applying filters to database nodes
|
||||
- Fixed generated calls for procedures with OUT parameters
|
||||
- Security:
|
||||
- Added an administrative setting to restrict SSH tunneling capabilities. Administrators can now limit tunnel configuration to authorized users, reducing the risk of unauthorized network access.
|
||||
- Fixed the critical vulnerability (CVE-2025-62718) in the axios library. The library was updated to version 1.15.0.
|
||||
- Fixed the high vulnerability (CVE-2026-4800) in the lodash library. The library was updated to version 4.18.0.
|
||||
- Fixed the high vulnerability (CVE-2026-39363) in the vite library. The library was updated to version 7.3.2.
|
||||
- Fixed the high vulnerability (CVE-2026-42035) in the axios library. The library was updated to version 1.15.1.
|
||||
- Fixed the high vulnerability (CVE-2026-3505) in the bcpg-jdk18on library. The library was updated to version 1.84.0.
|
||||
- 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-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.
|
||||
- Fixed the medium vulnerability (CVE-2026-41305) in the postcss library. The library was updated to version 8.5.14.
|
||||
- Removed the ability to rename SQL scripts that start with dots to prevent them from being moved outside the Scripts directory into the root Tree.
|
||||
- Added a new "Auto-create users" setting for the reverse proxy provider, allowing new user creation on login. It is enabled by default.
|
||||
- Fixed a security flaw allowing unauthorized users to test database connections. The application now requires proper permissions for this action, preventing malicious requests and protecting server files.
|
||||
- Fixed a security flaw in the LDAP authentication process. The application validates the username input to prevent unauthorized access and protect directory data.
|
||||
- Fixed a cross-site scripting vulnerability caused by unsanitized user input in the web interface.
|
||||
- Resolved a security flaw related to user-controlled file paths by adding strict validation to prevent unauthorized resource access.
|
||||
- Updated session handling to prevent session fixation vulnerabilities. The application generates new session identifiers upon login.
|
||||
- Updated the PostgreSQL JDBC driver to version 42.7.13, including a fix for a high-severity security vulnerability (CVE-2026-54291).
|
||||
- Fixed the critical vulnerability (CVE-2026-59873) in the tar library. The library was updated to version 7.5.19.
|
||||
- Fixed the critical vulnerability (CVE-2026-9277) in the shell-quote library (updated to version 1.8.4)
|
||||
- Fixed the high vulnerability (CVE-2026-10050) in the jetty-security library. The library was updated to version 12.1.11.
|
||||
- Fixed the high vulnerability (CVE-2026-14257) in the brace-expansion library. The library was updated to version 5.0.8.
|
||||
- Fixed the high vulnerability (GHSA-r28c-9q8g-f849) in the postcss library. The library was updated to version 8.5.18.
|
||||
- Fixed the high vulnerability (GHSA-pm4m-ph32-ghv5) in the js-yaml library. The library was updated to version 5.2.2.
|
||||
- Fixed the high vulnerability (CVE-2026-59880) in the immutable library. The library was updated to version 5.1.8.
|
||||
- Fixed the high vulnerability (CVE-2026-59879) in the fast-uri library. The library was updated to version 3.1.3.
|
||||
- Fixed the high vulnerability (CVE-2026-13311) in the shell-quote library. The library was updated to version 1.9.0.
|
||||
- Fixed the high vulnerability (GHSA-gcfj-64vw-6mp9) in the axios library. The library was updated to version 1.18.0.
|
||||
- Fixed the high vulnerability (CVE-2026-67213) in the nanoid library. The library was updated to version 3.3.17.
|
||||
- Fixed the high vulnerability (CVE-2025-71329) in the less library. The library was updated to version 4.7.0.
|
||||
- Fixed the high vulnerability (CVE-2026-13697) in the undici library. The library was updated to version 8.9.0.
|
||||
- Fixed the high vulnerability (CVE-2026-18446) in the fast-uri library. The library was updated to version 4.1.2.
|
||||
- Fixed the high vulnerability (CVE-2026-69152) in the brace-expansion library. The library was updated to version 5.0.9.
|
||||
- Fixed the high vulnerability (GHSA-gv7w-rqvm-qjhr) in the esbuild library. The library was updated to version 0.28.1.
|
||||
- Fixed the high vulnerability (CVE-2026-12143) in the form-data library. The library was updated to version 4.0.6.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.13.2</version>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -8,7 +8,15 @@ mkdir -p ./cloudbeaver/web
|
||||
cd ../../cloudbeaver/webapp
|
||||
|
||||
yarn install --immutable
|
||||
cd ./packages/product-default
|
||||
yarn clear
|
||||
|
||||
cd common-react
|
||||
yarn clear
|
||||
|
||||
cd ../common-typescript
|
||||
yarn clear
|
||||
|
||||
cd ../packages/product-default
|
||||
yarn run bundle
|
||||
|
||||
if [[ "$?" -ne 0 ]] ; then
|
||||
|
||||
@@ -9,8 +9,6 @@ RUN set -eux; \
|
||||
apt-get update && \
|
||||
apt-get dist-upgrade -y --no-install-recommends && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
# curl required for historical reasons, see https://github.com/adoptium/containers/issues/255
|
||||
curl \
|
||||
# java.lang.UnsatisfiedLinkError: libfontmanager.so: libfreetype.so.6: cannot open shared object file: No such file or directory
|
||||
# java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager
|
||||
# https://github.com/docker-library/openjdk/pull/235#issuecomment-424466077
|
||||
|
||||
@@ -17,6 +17,7 @@ If Not Defined JAVA_OPTS (
|
||||
|
||||
java %JAVA_OPTS% ^
|
||||
-Dfile.encoding=UTF-8 ^
|
||||
--enable-native-access=ALL-UNNAMED ^
|
||||
--add-modules=ALL-DEFAULT ^
|
||||
--add-opens=java.base/java.io=ALL-UNNAMED ^
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED ^
|
||||
@@ -33,7 +34,6 @@ java %JAVA_OPTS% ^
|
||||
--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED ^
|
||||
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED ^
|
||||
--add-opens=java.base/sun.security.ssl=ALL-UNNAMED ^
|
||||
--add-opens=java.base/sun.security.action=ALL-UNNAMED ^
|
||||
--add-opens=java.base/sun.security.util=ALL-UNNAMED ^
|
||||
--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED ^
|
||||
--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED ^
|
||||
|
||||
@@ -11,6 +11,7 @@ echo "Starting Cloudbeaver Server"
|
||||
|
||||
exec java ${JAVA_OPTS} \
|
||||
-Dfile.encoding=UTF-8 \
|
||||
--enable-native-access=ALL-UNNAMED \
|
||||
--add-modules=ALL-DEFAULT \
|
||||
--add-opens=java.base/java.io=ALL-UNNAMED \
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED \
|
||||
@@ -27,7 +28,6 @@ exec java ${JAVA_OPTS} \
|
||||
--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED \
|
||||
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED \
|
||||
--add-opens=java.base/sun.security.ssl=ALL-UNNAMED \
|
||||
--add-opens=java.base/sun.security.action=ALL-UNNAMED \
|
||||
--add-opens=java.base/sun.security.util=ALL-UNNAMED \
|
||||
--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED \
|
||||
--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED \
|
||||
|
||||
@@ -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.13.qualifier
|
||||
Bundle-Release-Date: 20260706
|
||||
Bundle-Version: 1.0.18.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
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.13-SNAPSHOT</version>
|
||||
<version>1.0.18-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,21 +3,20 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Model
|
||||
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
|
||||
Bundle-Version: 1.0.104.qualifier
|
||||
Bundle-Release-Date: 20260706
|
||||
Bundle-Version: 1.0.109.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.sm;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.event;visibility:=reexport,
|
||||
org.jkiss.dbeaver.model.nio;visibility:=reexport,
|
||||
org.jkiss.dbeaver.registry;visibility:=reexport,
|
||||
org.jkiss.dbeaver.net.ssh,
|
||||
org.jkiss.dbeaver.net.ssh;visibility:=reexport,
|
||||
org.jkiss.bundle.logback;visibility:=reexport,
|
||||
org.jkiss.bundle.graphql;visibility:=reexport,
|
||||
org.jkiss.bundle.apache.dbcp,
|
||||
jakarta.servlet-api;bundle-version:="6.0.0";visibility:=reexport
|
||||
jakarta.servlet-api;visibility:=reexport
|
||||
Export-Package: io.cloudbeaver,
|
||||
io.cloudbeaver.auth,
|
||||
io.cloudbeaver.auth.provider,
|
||||
|
||||
@@ -13,7 +13,7 @@ meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductName.
|
||||
meta.io.cloudbeaver.model.app.ServletSystemInformationCollector.smDbProductVersion.name=Security manager database product version
|
||||
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Read-only connection
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Auto-commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommitMode.name = Auto-commit mode
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Keep-alive interval (seconds)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Default schema
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Default catalog
|
||||
@@ -1,5 +1,5 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Только для чтения
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Авто-коммит
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommitMode.name = Авто-коммит
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Интервал Keep-alive (в секундах)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Схема по умолчанию
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Каталог по умолчанию
|
||||
@@ -1,4 +1,4 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Kết nối chỉ đọc
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Tự động commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommitMode.name = Tự động commit
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Giữ kết nối (tính bằng giây)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = Không tự động ngắt kết nối
|
||||
@@ -1,3 +1,3 @@
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = 自动提交
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommitMode.name = 自动提交
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = 保持连接 (秒)
|
||||
meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.description = 无自动断开连接
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model</artifactId>
|
||||
<version>1.0.104-SNAPSHOT</version>
|
||||
<version>1.0.109-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</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.
|
||||
@@ -20,6 +20,7 @@ import graphql.ErrorClassification;
|
||||
import graphql.ErrorType;
|
||||
import graphql.GraphQLError;
|
||||
import graphql.language.SourceLocation;
|
||||
import org.jkiss.dbeaver.DBDatabaseException;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.sql.SQLState;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -146,19 +147,22 @@ 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();
|
||||
}
|
||||
// Preserve enriched web/database messages, but unwrap generic exceptions to their most specific cause.
|
||||
Throwable messageCause = cause instanceof DBWebException || cause instanceof DBDatabaseException
|
||||
? cause
|
||||
: CommonUtils.getRootCause(cause);
|
||||
String causeMessage = messageCause.getMessage();
|
||||
if (CommonUtils.isEmpty(causeMessage)) {
|
||||
causeMessage = messageCause.getClass().getSimpleName();
|
||||
}
|
||||
if (CommonUtils.isEmpty(message)) {
|
||||
if (cause.getMessage() != null) {
|
||||
return cause.getMessage();
|
||||
}
|
||||
return cause.getClass().getName();
|
||||
return causeMessage;
|
||||
}
|
||||
if (CommonUtils.equalObjects(message, cause.getMessage())) {
|
||||
if (CommonUtils.equalObjects(message, causeMessage)) {
|
||||
return message;
|
||||
}
|
||||
return message + ":\n" + cause.getMessage();
|
||||
return message + ":\n" + causeMessage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-3
@@ -77,9 +77,7 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
|
||||
}
|
||||
|
||||
dataSource.setFolder(input.getFolder() != null ? registry.getFolder(input.getFolder()) : null);
|
||||
if (input.isDefaultAutoCommit() != null) {
|
||||
dataSource.getConnectionConfiguration().getBootstrap().setDefaultAutoCommit(input.isDefaultAutoCommit());
|
||||
}
|
||||
dataSource.getConnectionConfiguration().getBootstrap().setDefaultAutoCommit(input.isDefaultAutoCommit());
|
||||
WebDataSourceUtils.setConnectionConfiguration(
|
||||
dataSource.getDriver(),
|
||||
dataSource.getConnectionConfiguration(),
|
||||
|
||||
+1
-1
@@ -367,7 +367,7 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
|
||||
dataSourceRegistry.initializeDataSources();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nullable
|
||||
@Override
|
||||
public DataSourceParseResults loadDataSources(
|
||||
@NotNull List<DBPDataSourceConfigurationStorage> storages,
|
||||
|
||||
@@ -103,7 +103,7 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
return projectSettings;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
@Override
|
||||
public DBNModel getNavigatorModel() {
|
||||
return webSession.getNavigatorModel();
|
||||
@@ -135,8 +135,11 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
if (registryIsLoaded) {
|
||||
return;
|
||||
}
|
||||
getDataSourceRegistry().getDataSources().forEach(this::addConnection);
|
||||
Throwable lastError = getDataSourceRegistry().getLastError();
|
||||
DBPDataSourceRegistry dataSourceRegistry = getDataSourceRegistry();
|
||||
// Connection node paths are resolved through the navigator model, so materialize its lazy database branch first.
|
||||
webSession.initializeProjectNavigator(this);
|
||||
dataSourceRegistry.getDataSources().forEach(this::addConnection);
|
||||
Throwable lastError = dataSourceRegistry.getLastError();
|
||||
if (lastError != null) {
|
||||
webSession.addSessionError(lastError);
|
||||
log.error("Error refreshing connections from project '" + getId() + "'", lastError);
|
||||
@@ -237,6 +240,18 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
public synchronized boolean updateProjectDataSources(@NotNull WSDataSourceEvent event) {
|
||||
var sendDataSourceUpdatedEvent = false;
|
||||
DBPDataSourceRegistry registry = getDataSourceRegistry();
|
||||
Map<String, DataSourceDescriptor> dataSourceSnapshots = new HashMap<>();
|
||||
if (WSDataSourceEvent.UPDATED.equals(event.getId())) {
|
||||
for (String dsId : event.getDataSourceIds()) {
|
||||
DBPDataSourceContainer dataSource = registry.getDataSource(dsId);
|
||||
if (dataSource instanceof DataSourceDescriptor descriptor) {
|
||||
DBPDataSourceContainer snapshot = registry.createDataSource(descriptor);
|
||||
if (snapshot instanceof DataSourceDescriptor snapshotDescriptor) {
|
||||
dataSourceSnapshots.put(dsId, snapshotDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (WSDataSourceEvent.CREATED.equals(event.getId()) || WSDataSourceEvent.UPDATED.equals(event.getId())) {
|
||||
registry.refreshConfig(event.getDataSourceIds());
|
||||
}
|
||||
@@ -251,6 +266,10 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
}
|
||||
case WSDataSourceEvent.UPDATED -> {
|
||||
DataSourceDescriptor snapshot = dataSourceSnapshots.get(dsId);
|
||||
if (snapshot != null && !event.getProperty().hasEffectiveChanges(snapshot, ds)) {
|
||||
continue;
|
||||
}
|
||||
if (event.getProperty() == WSDataSourceProperty.CONFIGURATION) {
|
||||
WebDataSourceUtils.disconnectDataSource(webSession, ds, true);
|
||||
}
|
||||
@@ -357,6 +376,13 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
|
||||
return getInputConfigHandler(configInput).createDataSourceContainer();
|
||||
}
|
||||
|
||||
public void updateDataSourceContainerFromInput(
|
||||
@NotNull WebConnectionConfig configInput,
|
||||
@NotNull DataSourceDescriptor dataSource
|
||||
) throws DBWebException {
|
||||
getInputConfigHandler(configInput).updateDataSource(dataSource);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public WebConnectionConfig getConnectionConfigInput(@Nullable Map<String, Object> configMap) {
|
||||
return new WebConnectionConfig(configMap == null ? Map.of() : configMap);
|
||||
|
||||
+7
@@ -69,6 +69,13 @@ public interface SMAuthProviderExternal<AUTH_SESSION extends SMSession> extends
|
||||
@NotNull WebUser user,
|
||||
boolean selfIdentity) throws DBException;
|
||||
|
||||
/**
|
||||
* Whether a user that is not found should be provisioned (auto-created) on external login.
|
||||
*/
|
||||
default boolean isAutoUserProvisioningEnabled(@Nullable SMAuthProviderCustomConfiguration providerConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make some post authentication actions
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
public enum AutoCommitMode {
|
||||
DEFAULT("Default", null),
|
||||
AUTO_COMMIT("Auto commit", true),
|
||||
MANUAL_COMMIT("Manual commit", false);
|
||||
|
||||
private final String title;
|
||||
private final Boolean value;
|
||||
|
||||
AutoCommitMode(@NotNull String title, @Nullable Boolean value) {
|
||||
this.title = title;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static AutoCommitMode fromValue(@Nullable Boolean value) {
|
||||
if (value == null) {
|
||||
return DEFAULT;
|
||||
}
|
||||
return value ? AUTO_COMMIT : MANUAL_COMMIT;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Boolean from(@Nullable String mode) {
|
||||
if (CommonUtils.isEmpty(mode)) {
|
||||
return null;
|
||||
}
|
||||
return valueOf(mode).getValue();
|
||||
}
|
||||
}
|
||||
+19
-2
@@ -65,6 +65,7 @@ public class WebConnectionConfig {
|
||||
private Boolean defaultAutoCommit;
|
||||
private String defaultCatalogName;
|
||||
private String defaultSchemaName;
|
||||
private String connectionType;
|
||||
@NotNull
|
||||
private Map<String, String> defaultUserPreferences = new LinkedHashMap<>();
|
||||
|
||||
@@ -102,8 +103,14 @@ public class WebConnectionConfig {
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
|
||||
readOnly = JSONUtils.getBoolean(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
|
||||
defaultAutoCommit = JSONUtils.getBoolean(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
|
||||
|
||||
if (expertSettingsValues != null) {
|
||||
defaultAutoCommit = AutoCommitMode.from(
|
||||
JSONUtils.getString(expertSettingsValues, WebExpertSettingsProperties.PROP_AUTO_COMMIT_MODE));
|
||||
} else {
|
||||
defaultAutoCommit = JSONUtils.getBoolean(params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
|
||||
}
|
||||
|
||||
defaultCatalogName = JSONUtils.getString(
|
||||
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_CATALOG);
|
||||
defaultSchemaName = JSONUtils.getString(
|
||||
@@ -120,6 +127,7 @@ public class WebConnectionConfig {
|
||||
for (Map<String, Object> nhc : JSONUtils.getObjectList(params, "networkHandlersConfig")) {
|
||||
networkHandlersConfig.add(new WebNetworkHandlerConfigInput(nhc));
|
||||
}
|
||||
connectionType = JSONUtils.getString(params, "connectionType");
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -381,4 +389,13 @@ public class WebConnectionConfig {
|
||||
public void setDefaultUserPreferences(@NotNull Map<String, String> defaultUserPreferences) {
|
||||
this.defaultUserPreferences = defaultUserPreferences;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getConnectionType() {
|
||||
return connectionType;
|
||||
}
|
||||
|
||||
public void setConnectionType(@Nullable String connectionType) {
|
||||
this.connectionType = connectionType;
|
||||
}
|
||||
}
|
||||
|
||||
+35
-7
@@ -72,6 +72,11 @@ public class WebConnectionInfo {
|
||||
private static final String FEATURE_PROVIDED = "provided";
|
||||
private static final String FEATURE_MANAGEABLE = "manageable";
|
||||
|
||||
private static final String FEATURE_RESTRICT_DATA_EDIT = "restrictDataEdit";
|
||||
private static final String FEATURE_RESTRICT_SCRIPT_EXECUTE = "restrictScriptExecute";
|
||||
private static final String FEATURE_RESTRICT_DATA_IMPORT = "restrictDataImport";
|
||||
private static final String FEATURE_RESTRICT_METADATA_EDIT = "restrictMetadataEdit";
|
||||
|
||||
private static final String TOOL_SESSION_MANAGER = "sessionManager";
|
||||
|
||||
private final WebSession session;
|
||||
@@ -275,6 +280,18 @@ public class WebConnectionInfo {
|
||||
if (dataSourceContainer.isConnectionReadOnly()) {
|
||||
features.add(FEATURE_READ_ONLY);
|
||||
}
|
||||
if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_DATA)) {
|
||||
features.add(FEATURE_RESTRICT_DATA_EDIT);
|
||||
}
|
||||
if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EXECUTE_SCRIPTS)) {
|
||||
features.add(FEATURE_RESTRICT_SCRIPT_EXECUTE);
|
||||
}
|
||||
if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_IMPORT_DATA)) {
|
||||
features.add(FEATURE_RESTRICT_DATA_IMPORT);
|
||||
}
|
||||
if (!dataSourceContainer.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_METADATA)) {
|
||||
features.add(FEATURE_RESTRICT_METADATA_EDIT);
|
||||
}
|
||||
if (dataSourceContainer.isProvided()) {
|
||||
features.add(FEATURE_PROVIDED);
|
||||
}
|
||||
@@ -467,7 +484,7 @@ public class WebConnectionInfo {
|
||||
@Property
|
||||
public Map<String, Object> getExpertSettingsValues() {
|
||||
Map<String, Object> expertSettings = new LinkedHashMap<>();
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT, isAutocommit());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT_MODE, AutoCommitMode.fromValue(isAutocommit()));
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, getKeepAliveInterval());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_READ_ONLY, isReadOnly());
|
||||
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_CATALOG, getDefaultCatalogName());
|
||||
@@ -541,12 +558,9 @@ public class WebConnectionInfo {
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isAutocommit() {
|
||||
Boolean isAutoCommit = dataSourceContainer.getConnectionConfiguration().getBootstrap().getDefaultAutoCommit();
|
||||
if (isAutoCommit == null) {
|
||||
return true;
|
||||
}
|
||||
return isAutoCommit;
|
||||
@Nullable
|
||||
public Boolean isAutocommit() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getBootstrap().getDefaultAutoCommit();
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -586,6 +600,11 @@ public class WebConnectionInfo {
|
||||
return tools;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getConnectionType() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getConnectionType().getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates param that checks whether credentials were saved only in session.
|
||||
*/
|
||||
@@ -593,4 +612,13 @@ public class WebConnectionInfo {
|
||||
this.credentialsSavedInSession = credentialsSavedInSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database-dependent driver configuration used to drive data import
|
||||
*/
|
||||
@Property
|
||||
@NotNull
|
||||
public WebDriverConfiguration getDriverConfiguration() {
|
||||
return new WebDriverConfiguration(dataSourceContainer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
/**
|
||||
* Generic id/name/description dictionary value.
|
||||
*/
|
||||
public class WebDictionaryValueInfo {
|
||||
|
||||
@NotNull
|
||||
private final String id;
|
||||
@NotNull
|
||||
private final String name;
|
||||
@Nullable
|
||||
private final String description;
|
||||
|
||||
public WebDictionaryValueInfo(@NotNull String id, @NotNull String name, @Nullable String description) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Property
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Property
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.sql.SQLDialectInsertReplaceMethod;
|
||||
import org.jkiss.dbeaver.model.sql.SQLDialectMetadata;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataBulkLoader;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WebDriverConfiguration {
|
||||
|
||||
@NotNull
|
||||
private final DBPDataSourceContainer dataSourceContainer;
|
||||
|
||||
public WebDriverConfiguration(@NotNull DBPDataSourceContainer dataSourceContainer) {
|
||||
this.dataSourceContainer = dataSourceContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'On duplicate key' replace methods supported by this connection's SQL dialect
|
||||
*/
|
||||
@Property
|
||||
@NotNull
|
||||
public List<WebDictionaryValueInfo> getSupportedInsertReplaceMethods() {
|
||||
SQLDialectMetadata dialect = dataSourceContainer.getDriver().getScriptDialect();
|
||||
List<SQLDialectInsertReplaceMethod> replaceMethods = dialect.getSupportedInsertReplaceMethodsDescriptors();
|
||||
if (CommonUtils.isEmpty(replaceMethods)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return replaceMethods.stream()
|
||||
.map(method -> new WebDictionaryValueInfo(method.getId(), method.getLabel(), method.getDescription()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the connection's database supports bulk load during data import
|
||||
*/
|
||||
@Property
|
||||
public boolean isSupportsBulkLoad() {
|
||||
DBPDataSource dataSource = dataSourceContainer.getDataSource();
|
||||
return dataSource != null && DBUtils.getAdapter(DBSDataBulkLoader.class, dataSource) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the connection's database supports transaction
|
||||
*/
|
||||
@Property
|
||||
public boolean isSupportsTransactions() {
|
||||
DBPDataSource dataSource = dataSourceContainer.getDataSource();
|
||||
return dataSource != null && dataSource.getInfo().supportsTransactions();
|
||||
}
|
||||
}
|
||||
+43
-3
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
@@ -5,6 +21,7 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConstants;
|
||||
import org.jkiss.dbeaver.model.meta.IPropertyValueListProvider;
|
||||
import org.jkiss.dbeaver.model.meta.IPropertyValueValidator;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -15,6 +32,7 @@ import org.jkiss.utils.CommonUtils;
|
||||
public class WebExpertSettingsProperties implements DBPObject {
|
||||
public static final String PROP_READ_ONLY = "readOnly";
|
||||
public static final String PROP_AUTO_COMMIT = "autocommit";
|
||||
public static final String PROP_AUTO_COMMIT_MODE = "autoCommitMode";
|
||||
public static final String PROP_KEEP_ALIVE_INTERVAL = "keepAliveInterval";
|
||||
public static final String PROP_DEFAULT_CATALOG = "defaultCatalogName";
|
||||
public static final String PROP_DEFAULT_SCHEMA = "defaultSchemaName";
|
||||
@@ -30,9 +48,15 @@ public class WebExpertSettingsProperties implements DBPObject {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Property(order = 2, id = PROP_AUTO_COMMIT, visibleIf = AutoCommitFieldValidator.class)
|
||||
public boolean isAutoCommit() {
|
||||
return true;
|
||||
@NotNull
|
||||
@Property(
|
||||
order = 2,
|
||||
id = PROP_AUTO_COMMIT_MODE,
|
||||
visibleIf = AutoCommitFieldValidator.class,
|
||||
listProvider = AutoCommitListProvider.class
|
||||
)
|
||||
public AutoCommitMode isAutoCommitMode() {
|
||||
return AutoCommitMode.DEFAULT;
|
||||
}
|
||||
|
||||
@Property(order = 3, id = PROP_READ_ONLY, visibleIf = ReadOnlyFieldValidator.class)
|
||||
@@ -40,11 +64,13 @@ public class WebExpertSettingsProperties implements DBPObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property(order = 4, id = PROP_DEFAULT_CATALOG, visibleIf = DefaultCatalogFieldVisibleValidator.class)
|
||||
public String getDefaultCatalog() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property(order = 5, id = PROP_DEFAULT_SCHEMA, visibleIf = DefaultSchemaFieldVisibleValidator.class)
|
||||
public String getDefaultSchema() {
|
||||
return null;
|
||||
@@ -85,4 +111,18 @@ public class WebExpertSettingsProperties implements DBPObject {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCommitListProvider implements IPropertyValueListProvider<WebExpertSettingsProperties> {
|
||||
|
||||
@Override
|
||||
public boolean allowCustomValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object[] getPossibleValues(@Nullable WebExpertSettingsProperties object) {
|
||||
return AutoCommitMode.values();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ 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;
|
||||
import org.jkiss.dbeaver.model.meta.PropertyConstraints;
|
||||
import org.jkiss.dbeaver.model.meta.PropertyLength;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
|
||||
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
|
||||
@@ -42,63 +43,58 @@ import java.util.*;
|
||||
* Web connection info
|
||||
*/
|
||||
public class WebPropertyInfo {
|
||||
private WebSession session;
|
||||
private DBPPropertyDescriptor property;
|
||||
private DBPPropertySource propertySource;
|
||||
private boolean showProtected;
|
||||
@NotNull
|
||||
private final WebSession session;
|
||||
@NotNull
|
||||
private final DBPPropertyDescriptor property;
|
||||
@Nullable
|
||||
private final DBPPropertySource propertySource;
|
||||
|
||||
private Object[] validValues;
|
||||
|
||||
private String[] supportedConfigurationTypes = new String[0];
|
||||
|
||||
private Object defaultValue;
|
||||
|
||||
public WebPropertyInfo(WebSession session, DBPPropertyDescriptor property, DBPPropertySource propertySource) {
|
||||
public WebPropertyInfo(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBPPropertyDescriptor property,
|
||||
@Nullable DBPPropertySource propertySource
|
||||
) {
|
||||
this.session = session;
|
||||
this.property = property;
|
||||
this.propertySource = propertySource;
|
||||
}
|
||||
|
||||
public WebPropertyInfo(WebSession session, DBPPropertyDescriptor property) {
|
||||
this.session = session;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
public boolean isShowProtected() {
|
||||
return showProtected;
|
||||
}
|
||||
|
||||
public void setShowProtected(boolean showProtected) {
|
||||
this.showProtected = showProtected;
|
||||
public WebPropertyInfo(@NotNull WebSession session, @NotNull DBPPropertyDescriptor property) {
|
||||
this(session, property, null);
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
// General properties
|
||||
///////////////////////////////////
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getId() {
|
||||
return CommonUtils.toString(property.getId());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getDisplayName() {
|
||||
if (property instanceof DBPNamedObjectLocalized) {
|
||||
return ((DBPNamedObjectLocalized) property).getLocalizedName(session.getLocale());
|
||||
if (property instanceof DBPNamedObjectLocalized localized) {
|
||||
return localized.getLocalizedName(session.getLocale());
|
||||
} else {
|
||||
return property.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getDescription() {
|
||||
if (property instanceof DBPObjectWithDescriptionLocalized) {
|
||||
return ((DBPObjectWithDescriptionLocalized) property).getLocalizedDescription(session.getLocale());
|
||||
if (property instanceof DBPObjectWithDescriptionLocalized localized) {
|
||||
return localized.getLocalizedDescription(session.getLocale());
|
||||
} else {
|
||||
return property.getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getHint() {
|
||||
return property.getHint();
|
||||
@@ -109,38 +105,53 @@ public class WebPropertyInfo {
|
||||
return property instanceof ObjectPropertyDescriptor ? ((ObjectPropertyDescriptor) property).getOrderNumber() : -1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getCategory() {
|
||||
return property.getCategory();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getType() {
|
||||
return getDataType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getDataType() {
|
||||
Class<?> dataType = property.getDataType();
|
||||
return dataType == null ? null : dataType.getSimpleName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public PropertyLength getLength() {
|
||||
return property.getLength();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public Object getDefaultValue() throws DBException {
|
||||
var defaultValue = property.getDefaultValue() == null ? this.defaultValue : property.getDefaultValue();
|
||||
return defaultValue == null ? getValue() : defaultValue;
|
||||
Object defaultValue = property.getDefaultValue();
|
||||
if (property instanceof ObjectPropertyDescriptor && defaultValue == null) {
|
||||
return getValue();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public PropertyConstraints getConstraints() {
|
||||
return property instanceof ObjectPropertyDescriptor descriptor ? descriptor.getConstraints() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public Object getValue() throws DBException {
|
||||
Object value = propertySource == null ? null : propertySource.getPropertyValue(session.getProgressMonitor(), property.getId());
|
||||
if (property instanceof ObjectPropertyDescriptor opd &&
|
||||
(!showProtected && opd.isPassword() || opd.isHidden())
|
||||
(opd.isPassword() || opd.isHidden())
|
||||
) {
|
||||
return maskValue(value);
|
||||
} else if (property instanceof PropertyDescriptor pd) {
|
||||
@@ -152,6 +163,7 @@ public class WebPropertyInfo {
|
||||
return value == null ? null : makePropertyValue(value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public Object[] getValidValues() {
|
||||
if (property instanceof IPropertyValueListProvider) {
|
||||
@@ -164,17 +176,18 @@ public class WebPropertyInfo {
|
||||
}
|
||||
return validValues;
|
||||
}
|
||||
return validValues;
|
||||
}
|
||||
return validValues;
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String[] getFeatures() {
|
||||
String[] features = property.getFeatures();
|
||||
return features == null ? new String[0] : features;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String[] getSupportedConfigurationTypes() {
|
||||
if (property instanceof ProviderPropertyDescriptor) {
|
||||
@@ -182,7 +195,7 @@ public class WebPropertyInfo {
|
||||
.map(DBPDriverConfigurationType::toString)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
return supportedConfigurationTypes;
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -203,12 +216,14 @@ public class WebPropertyInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return CommonUtils.toString(property.getId());
|
||||
}
|
||||
|
||||
private Object makePropertyValue(Object value) {
|
||||
@Nullable
|
||||
private Object makePropertyValue(@Nullable Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
// } else if (value instanceof DBSObject) {
|
||||
@@ -290,21 +305,6 @@ public class WebPropertyInfo {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
|
||||
//TODO: delete after refactoring on front-end
|
||||
public void setDefaultValue(String defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
//TODO: delete after refactoring on front-end
|
||||
public void setValidValues(Object[] validValues) {
|
||||
this.validValues = validValues;
|
||||
}
|
||||
|
||||
//TODO: delete after refactoring on front-end
|
||||
public void setSupportedConfigurationTypes(String[] supportedConfigurationTypes) {
|
||||
this.supportedConfigurationTypes = supportedConfigurationTypes;
|
||||
}
|
||||
|
||||
public record Condition(@NotNull String expression, @NotNull Type conditionType) {
|
||||
public enum Type {
|
||||
HIDE,
|
||||
|
||||
+11
-15
@@ -37,7 +37,6 @@ import org.jkiss.dbeaver.model.impl.app.BaseApplicationImpl;
|
||||
import org.jkiss.dbeaver.model.impl.app.BaseWorkspaceImpl;
|
||||
import org.jkiss.dbeaver.model.rm.RMController;
|
||||
import org.jkiss.dbeaver.model.secret.DBSSecretController;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSEventController;
|
||||
import org.jkiss.dbeaver.runtime.IVariableResolver;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
@@ -186,20 +185,20 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void patchConfigurationWithProperties(
|
||||
Map<String, Object> configProps, IVariableResolver varResolver
|
||||
@NotNull Map<String, Object> configProps,
|
||||
@NotNull IVariableResolver varResolver
|
||||
) {
|
||||
for (Map.Entry<String, Object> entry : configProps.entrySet()) {
|
||||
Object propValue = entry.getValue();
|
||||
if (propValue instanceof String) {
|
||||
entry.setValue(GeneralUtils.replaceVariables((String) propValue, varResolver));
|
||||
if (propValue instanceof String strValue) {
|
||||
entry.setValue(GeneralUtils.replaceVariables(strValue, varResolver));
|
||||
} else if (propValue instanceof Map) {
|
||||
patchConfigurationWithProperties((Map<String, Object>) propValue, varResolver);
|
||||
} else if (propValue instanceof List) {
|
||||
List value = (List) propValue;
|
||||
} else if (propValue instanceof List value) {
|
||||
for (int i = 0; i < value.size(); i++) {
|
||||
Object colItem = value.get(i);
|
||||
if (colItem instanceof String) {
|
||||
value.set(i, GeneralUtils.replaceVariables((String) colItem, varResolver));
|
||||
if (colItem instanceof String strItem) {
|
||||
value.set(i, GeneralUtils.replaceVariables(strItem, varResolver));
|
||||
} else if (colItem instanceof Map) {
|
||||
patchConfigurationWithProperties((Map<String, Object>) colItem, varResolver);
|
||||
}
|
||||
@@ -250,21 +249,18 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
return BaseWorkspaceImpl.readWorkspaceIdProperty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getApplicationId() {
|
||||
try {
|
||||
return getApplicationInstanceId();
|
||||
} catch (DBException e) {
|
||||
return null;
|
||||
// Fallback to hash code
|
||||
return getClass().getSimpleName() + "#" + hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WSEventController getEventController() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract ServletServerConfigurationController getServerConfigurationController();
|
||||
public abstract ServletServerConfigurationController<?> getServerConfigurationController();
|
||||
|
||||
@Override
|
||||
public boolean isEnvironmentVariablesAccessible() {
|
||||
|
||||
+2
-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.
|
||||
@@ -23,6 +23,7 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
|
||||
public interface ServletAuthApplication extends ServletApplication {
|
||||
@NotNull
|
||||
ServletAuthConfiguration getAuthConfiguration();
|
||||
|
||||
@Nullable
|
||||
|
||||
+3
-5
@@ -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.
|
||||
@@ -67,8 +67,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties)
|
||||
throws DBException {
|
||||
public RMProject getProject(@NotNull String projectId, boolean readResources, boolean readProperties) throws DBException {
|
||||
RMProject project = makeProjectFromId(projectId, true);
|
||||
if (project == null) {
|
||||
return null;
|
||||
@@ -110,8 +109,7 @@ public abstract class BaseLocalResourceController implements RMController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProjectsDataSources(@NotNull String projectId, @Nullable String[] dataSourceIds)
|
||||
throws DBException {
|
||||
public String getProjectsDataSources(@NotNull String projectId, @Nullable String[] dataSourceIds) throws DBException {
|
||||
DBPProject projectMetadata = getWebProject(projectId, false);
|
||||
return doFileReadOperation(
|
||||
projectId,
|
||||
|
||||
+62
-6
@@ -40,6 +40,7 @@ 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;
|
||||
import org.jkiss.dbeaver.model.net.DBWNetworkProfile;
|
||||
import org.jkiss.dbeaver.model.rm.*;
|
||||
import org.jkiss.dbeaver.model.security.SMAdminController;
|
||||
import org.jkiss.dbeaver.model.sql.DBQuotaException;
|
||||
@@ -48,6 +49,7 @@ import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEvent;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSNetworkProfileEvent;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceParseResults;
|
||||
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
|
||||
@@ -407,8 +409,13 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
registry::createDataSource
|
||||
)
|
||||
);
|
||||
Map<String, DBWNetworkProfile> oldNetworkProfiles = registry.getNetworkProfiles().getProfiles().stream()
|
||||
.collect(Collectors.toMap(
|
||||
DBWNetworkProfile::getProfileId,
|
||||
p -> p
|
||||
));
|
||||
DataSourceParseResults parseResults = super.updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds);
|
||||
sendDataSourcesConfigUpdatedEvent(registry, oldDataSources, parseResults);
|
||||
sendConfigUpdatedEvent(registry, oldDataSources, oldNetworkProfiles, parseResults);
|
||||
return parseResults != null;
|
||||
}
|
||||
|
||||
@@ -501,14 +508,48 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
return DBNLocalFolder.makeLocalFolderItemPath(projectId, folderPath);
|
||||
}
|
||||
|
||||
private void sendDataSourcesConfigUpdatedEvent(
|
||||
private void sendConfigUpdatedEvent(
|
||||
@NotNull DBPDataSourceRegistry registry,
|
||||
@NotNull Map<String, DataSourceDescriptor> oldDataSources,
|
||||
@NotNull Map<String, DBWNetworkProfile> oldNetworkProfiles,
|
||||
@Nullable DataSourceParseResults parseResults
|
||||
) {
|
||||
if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null || oldDataSources.isEmpty()) {
|
||||
if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null) {
|
||||
return;
|
||||
}
|
||||
boolean profilesChanged = !parseResults.removedProfiles.isEmpty();
|
||||
|
||||
// We don't need to check for updated profiles if there are already added or removed profiles
|
||||
if (!profilesChanged) {
|
||||
for (DBWNetworkProfile updatedProfile : parseResults.updatedProfiles) {
|
||||
if (oldNetworkProfiles.containsKey(updatedProfile.getProfileId())) {
|
||||
DBWNetworkProfile oldProfile = oldNetworkProfiles.get(updatedProfile.getProfileId());
|
||||
if (!oldProfile.equalConfigurations(updatedProfile)) {
|
||||
profilesChanged = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// profile was added
|
||||
profilesChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (profilesChanged) {
|
||||
ServletAppUtils.getServletApplication().getEventController().addEvent(
|
||||
WSNetworkProfileEvent.update(
|
||||
credentialsProvider.getActiveUserCredentials().getSmSessionId(),
|
||||
credentialsProvider.getActiveUserCredentials().getUserId(),
|
||||
registry.getProject().getId()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (oldDataSources.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> updatedConfigurationDataSourceIds = new ArrayList<>();
|
||||
List<String> updatedNameDataSourceIds = new ArrayList<>();
|
||||
List<String> updatedInternalConfigurationDataSourceIds = new ArrayList<>();
|
||||
@@ -612,7 +653,6 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -988,8 +1028,24 @@ public class LocalResourceController extends BaseLocalResourceController {
|
||||
try {
|
||||
while (resourcePath.startsWith("/")) resourcePath = resourcePath.substring(1);
|
||||
Path targetPath = projectPath.resolve(resourcePath).normalize();
|
||||
if (!targetPath.startsWith(projectPath)) {
|
||||
throw new DBException("Invalid resource path");
|
||||
// hack for custom path implementations that returns / as root path
|
||||
Iterator<Path> projectParts = projectPath.iterator();
|
||||
Iterator<Path> targetParts = targetPath.iterator();
|
||||
Iterator<Path> unnormalizedTargetParts = projectPath.resolve(resourcePath).iterator();
|
||||
while (projectParts.hasNext()) {
|
||||
if (!targetParts.hasNext()) {
|
||||
throw new DBException("Invalid resource path");
|
||||
}
|
||||
Path projectPart = projectParts.next();
|
||||
Path targetPart = targetParts.next();
|
||||
unnormalizedTargetParts.next();
|
||||
if (!projectPart.equals(targetPart)) {
|
||||
throw new DBException("Invalid resource path");
|
||||
}
|
||||
}
|
||||
// validating unnormalized target parts to avoid invalid characters in resource names
|
||||
while (unnormalizedTargetParts.hasNext()) {
|
||||
GeneralUtils.validateResourceNameUnconditionally(unnormalizedTargetParts.next().toString());
|
||||
}
|
||||
return targetPath;
|
||||
} catch (InvalidPathException e) {
|
||||
|
||||
+11
@@ -155,6 +155,17 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes user permissions, teams and accessible projects.
|
||||
* <p>
|
||||
* Unlike {@link #refreshUserData()} this method must not re-create heavyweight session state
|
||||
* (navigator model, session projects, connection caches), so it is safe to call
|
||||
* for foreign sessions on server-wide events.
|
||||
*/
|
||||
public void refreshUserPermissions() {
|
||||
refreshUserData();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SMAuthSpace getSessionSpace() {
|
||||
|
||||
+63
-2
@@ -262,12 +262,54 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
@Override
|
||||
public void refreshUserData() {
|
||||
super.refreshUserData();
|
||||
refreshSessionAuth();
|
||||
refreshUserPermissions();
|
||||
|
||||
initNavigatorModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshUserPermissions() {
|
||||
super.refreshUserData();
|
||||
refreshSessionAuth();
|
||||
if (getUserId() == null && globalProject != null) {
|
||||
// refreshSessionAuth() updates accessible connections for named users only,
|
||||
// for anonymous sessions this was done by initNavigatorModel()
|
||||
globalProject.refreshAccessibleConnectionIds();
|
||||
}
|
||||
syncSessionProjects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/removes only those session projects whose accessibility actually changed.
|
||||
* <p>
|
||||
* Unlike {@link #initNavigatorModel()} this keeps existing projects, and therefore
|
||||
* their live connections and SQL contexts, intact.
|
||||
*/
|
||||
private void syncSessionProjects() {
|
||||
if (getNavigatorModel() == null) {
|
||||
// model was never initialized for this session - nothing to sync
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Map<String, RMProject> actualProjects = new LinkedHashMap<>();
|
||||
for (RMProject rmProject : getRmController().listAccessibleProjects()) {
|
||||
actualProjects.put(rmProject.getId(), rmProject);
|
||||
}
|
||||
for (WebSessionProjectImpl project : new ArrayList<>(getWorkspace().getProjects())) {
|
||||
if (actualProjects.remove(project.getId()) == null && !project.isInMemory()) {
|
||||
// in-memory (anonymous) projects are never listed by RM
|
||||
deleteSessionProject(project);
|
||||
}
|
||||
}
|
||||
for (RMProject rmProject : actualProjects.values()) {
|
||||
createWebProject(rmProject);
|
||||
}
|
||||
} catch (DBException e) {
|
||||
addSessionError(e);
|
||||
log.error("Error synchronizing accessible projects", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: for admin use only
|
||||
public void resetUserState(boolean needResetUserCache) throws DBException {
|
||||
clearAuthTokens();
|
||||
@@ -460,6 +502,25 @@ public class WebSession extends BaseWebSession
|
||||
navigatorModelLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the project's lazy database branch while preventing the navigator model from being replaced.
|
||||
*/
|
||||
public void initializeProjectNavigator(@NotNull WebSessionProjectImpl project) {
|
||||
navigatorModelLock.readLock().lock();
|
||||
try {
|
||||
if (navigatorModel == null) {
|
||||
return;
|
||||
}
|
||||
var projectNode = navigatorModel.getRoot().getProjectNode(project);
|
||||
if (projectNode != null) {
|
||||
projectNode.getDatabases();
|
||||
}
|
||||
} finally {
|
||||
navigatorModelLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and clears progress messages
|
||||
*/
|
||||
|
||||
+8
-1
@@ -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.
|
||||
@@ -68,6 +68,13 @@ public class WebSessionEventsFilter {
|
||||
}
|
||||
}
|
||||
|
||||
public void migrateTo(@NotNull WebSessionEventsFilter target) {
|
||||
synchronized (target.subscribedProjectIds) {
|
||||
target.subscribedEventTopics.addAll(this.subscribedEventTopics);
|
||||
target.subscribedProjectIds.addAll(this.subscribedProjectIds);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEventAllowed(WSEvent event) {
|
||||
if (event.isForceProcessed()) {
|
||||
return true;
|
||||
|
||||
+6
@@ -59,6 +59,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
private final boolean isPrivate;
|
||||
private final boolean isAuthHidden;
|
||||
private final boolean isCaseInsensitive;
|
||||
private final boolean sameTabRedirectOnLogout;
|
||||
private final boolean serviceProvider;
|
||||
private final String[] requiredFeatures;
|
||||
private final boolean isRequired;
|
||||
@@ -75,6 +76,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
this.isRequired = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_REQUIRED));
|
||||
this.isAuthHidden = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_AUTH_HIDDEN));
|
||||
this.isCaseInsensitive = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_CASE_INSENSITIVE));
|
||||
this.sameTabRedirectOnLogout = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_SAME_TAB_LOGOUT_REDIRECT));
|
||||
this.serviceProvider = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_SERVICE_PROVIDER));
|
||||
|
||||
for (IConfigurationElement cfgElement : cfg.getChildren(WebRegistryConstant.TAG_CONFIGURATION)) {
|
||||
@@ -129,6 +131,10 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
return trusted;
|
||||
}
|
||||
|
||||
public boolean isSameTabRedirectOnLogout() {
|
||||
return sameTabRedirectOnLogout;
|
||||
}
|
||||
|
||||
public boolean isPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ public class WebRegistryConstant {
|
||||
public static final String ATTR_REQUIRED = "required";
|
||||
public static final String ATTR_AUTH_HIDDEN = "authHidden";
|
||||
public static final String ATTR_CASE_INSENSITIVE = "caseInsensitive";
|
||||
public static final String ATTR_SAME_TAB_LOGOUT_REDIRECT = "sameTabRedirectOnLogout";
|
||||
public static final String ATTR_REQUIRED_FEATURES = "requiredFeatures";
|
||||
public static final String ATTR_CATEGORIES = "categories";
|
||||
public static final String ATTR_SERVICE_PROVIDER = "serviceProvider";
|
||||
|
||||
+2
-2
@@ -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.
|
||||
@@ -76,7 +76,7 @@ public abstract class BaseServletPlatform extends BasePlatformImpl {
|
||||
// Remove temp folder
|
||||
if (tempFolder != null) {
|
||||
|
||||
if (!ContentUtils.deleteFileRecursive(tempFolder.toFile())) {
|
||||
if (!ContentUtils.deleteFileRecursive(tempFolder)) {
|
||||
log.warn("Can't delete temp folder '" + tempFolder.toAbsolutePath() + "'");
|
||||
}
|
||||
tempFolder = null;
|
||||
|
||||
+2
-2
@@ -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.
|
||||
@@ -30,7 +30,7 @@ public interface DBWServiceBindingServlet<APPLICATION extends ServletApplication
|
||||
return true;
|
||||
}
|
||||
|
||||
void addServlets(APPLICATION application, DBWServletContext servletContext) throws DBException;
|
||||
void addServlets(@NotNull APPLICATION application, @NotNull DBWServletContext servletContext) throws DBException;
|
||||
|
||||
@NotNull
|
||||
default List<String> getExcludedServletPaths(@NotNull APPLICATION application) {
|
||||
|
||||
@@ -192,13 +192,16 @@ public class ServletAppUtils {
|
||||
@NotNull String serviceId,
|
||||
@NotNull String origin
|
||||
) throws DBException {
|
||||
String finalOrigin = webAuthApplication.modifyOrigin(origin);
|
||||
String finalOrigin = ServletAppUtils.substringBeforeRootURI(webAuthApplication.modifyOrigin(origin));
|
||||
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(rootUri)) {
|
||||
authUriBuilder.append("/").append(rootUri);
|
||||
}
|
||||
if (CommonUtils.isNotEmpty(serviceUriSegment)) {
|
||||
authUriBuilder.append("/").append(serviceUriSegment);
|
||||
}
|
||||
@@ -232,6 +235,14 @@ public class ServletAppUtils {
|
||||
response.addCookie(sessionCookie);
|
||||
}
|
||||
|
||||
public static String getHeaderOrLegacy(HttpServletRequest request, String headerName, String legacyHeaderName) {
|
||||
String value = request.getHeader(headerName);
|
||||
if (CommonUtils.isEmpty(value)) {
|
||||
value = request.getHeader(legacyHeaderName);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String getRequestCookie(HttpServletRequest request, String cookieName) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
@@ -297,12 +308,21 @@ public class ServletAppUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getHostOriginFromRequest(@NotNull HttpServletRequest request) {
|
||||
return substringBeforeRootURI(getOriginFromRequest(request));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getOriginFromRequest(@NotNull HttpServletRequest request) {
|
||||
String origin = request.getHeader(HEADER_ORIGIN);
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Origin header: " + origin);
|
||||
}
|
||||
if ("null".equals(origin)) {
|
||||
//strange identity provider bug
|
||||
origin = null;
|
||||
}
|
||||
if (CommonUtils.isEmpty(origin)) {
|
||||
origin = request.getHeader(HEADER_X_ORIGIN);
|
||||
if (log.isTraceEnabled()) {
|
||||
|
||||
+20
-6
@@ -33,6 +33,7 @@ import org.jkiss.dbeaver.model.access.DBAAuthCredentials;
|
||||
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionType;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDataSourceProviderDescriptor;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials;
|
||||
@@ -40,6 +41,7 @@ import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
|
||||
import org.jkiss.dbeaver.model.net.ssh.SSHConstants;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceDisconnectEvent;
|
||||
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
|
||||
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
|
||||
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
@@ -55,9 +57,12 @@ public class WebDataSourceUtils {
|
||||
private WebDataSourceUtils() {
|
||||
}
|
||||
|
||||
public static void saveCredentialsInDataSource(WebConnectionInfo webConnectionInfo, DBPDataSourceContainer dataSourceContainer, DBPConnectionConfiguration configuration) {
|
||||
public static void saveCredentialsInDataSource(
|
||||
@NotNull WebConnectionInfo webConnectionInfo,
|
||||
@NotNull DBPDataSourceContainer dataSourceContainer,
|
||||
@NotNull DBPConnectionConfiguration configuration
|
||||
) {
|
||||
// Properties passed from web
|
||||
// webConnectionInfo may be null in some cases (e.g. connection test when no actual connection exist yet)
|
||||
Map<String, Object> authProperties = webConnectionInfo.getSavedAuthProperties();
|
||||
if (authProperties != null) {
|
||||
authProperties.forEach((s, o) -> configuration.setAuthProperty(s, CommonUtils.toString(o)));
|
||||
@@ -305,9 +310,7 @@ public class WebDataSourceUtils {
|
||||
if (config.getKeepAliveInterval() >= 0) {
|
||||
dsConfig.setKeepAliveInterval(config.getKeepAliveInterval());
|
||||
}
|
||||
if (config.isDefaultAutoCommit() != null) {
|
||||
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
|
||||
}
|
||||
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
|
||||
dsConfig.getBootstrap().setDefaultCatalogName(config.getDefaultCatalogName());
|
||||
dsConfig.getBootstrap().setDefaultSchemaName(config.getDefaultSchemaName());
|
||||
// Save provider props
|
||||
@@ -320,8 +323,19 @@ public class WebDataSourceUtils {
|
||||
if (config.getConfigurationType() != null) {
|
||||
dsConfig.setConfigurationType(config.getConfigurationType());
|
||||
}
|
||||
if (config.getConnectionType() != null) {
|
||||
DBPConnectionType connectionType = DataSourceProviderRegistry.getInstance().getConnectionType(config.getConnectionType(), null);
|
||||
if (connectionType != null) {
|
||||
dsConfig.setConnectionType(connectionType);
|
||||
}
|
||||
}
|
||||
|
||||
if (CommonUtils.isEmpty(config.getUrl())) {
|
||||
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
|
||||
try {
|
||||
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
|
||||
} catch (DBException e) {
|
||||
log.error("Error preparing connection URL", e);
|
||||
}
|
||||
}
|
||||
// Save network handlers
|
||||
if (config.getNetworkHandlersConfig() != null) {
|
||||
|
||||
@@ -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: 26.1.2.qualifier
|
||||
Bundle-Release-Date: 20260706
|
||||
Bundle-Version: 26.2.1.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
</product>
|
||||
</extension>
|
||||
|
||||
<extension point="io.cloudbeaver.feature">
|
||||
<feature id="ai" label="AI Integration" description="Enables AI Chat in the application." enabledByDefault="true"/>
|
||||
</extension>
|
||||
|
||||
<extension point="org.jkiss.dbeaver.application">
|
||||
<application
|
||||
id="cloudbeaver-ce"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce</artifactId>
|
||||
<version>26.1.2-SNAPSHOT</version>
|
||||
<version>26.2.1-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.149.qualifier
|
||||
Bundle-Release-Date: 20260706
|
||||
Bundle-Version: 1.0.154.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<driver id="generic:trino_jdbc"/>
|
||||
<driver id="duckdb:duckdb_jdbc"/>
|
||||
<driver id="generic:kyuubi_hive"/>
|
||||
<driver id="databend:databend"/>
|
||||
<driver id="databend:databend_jdbc"/>
|
||||
</extension>
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
|
||||
<version>1.0.149-SNAPSHOT</version>
|
||||
<version>1.0.154-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: CloudBeaver Community
|
||||
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
|
||||
Bundle-Version: 26.1.2.qualifier
|
||||
Bundle-Release-Date: 20260706
|
||||
Bundle-Version: 26.2.1.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
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>26.1.2-SNAPSHOT</version>
|
||||
<version>26.2.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Core API
|
||||
####################################################
|
||||
|
||||
extend type ServerConfig {
|
||||
serverURL: String! @deprecated
|
||||
rootURI: String!
|
||||
|
||||
+3
-3
@@ -14,11 +14,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.admin;
|
||||
package io.cloudbeaver.model.config;
|
||||
|
||||
import io.cloudbeaver.model.config.CBAppConfig;
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.json.JSONUtils;
|
||||
@@ -57,7 +57,7 @@ public class AdminServerConfig {
|
||||
@Nullable
|
||||
private String bindSessionToIp;
|
||||
|
||||
public AdminServerConfig(Map<String, Object> params) {
|
||||
public AdminServerConfig(@NotNull Map<String, Object> params) {
|
||||
this.serverName = JSONUtils.getString(params, "serverName");
|
||||
this.adminName = JSONUtils.getString(params, "adminName");
|
||||
this.adminPassword = JSONUtils.getString(params, "adminPassword");
|
||||
@@ -181,6 +181,7 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
return getServerConfigurationController().getServerConfiguration();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ServletAuthConfiguration getAuthConfiguration() {
|
||||
return getAppConfiguration();
|
||||
@@ -751,6 +752,7 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
eventController.addEvent(new WSServerConfigurationChangedEvent(sessionId, null));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public abstract CBServerConfigurationController<T> getServerConfigurationController();
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ public class CBApplicationCE extends CBApplication<CBServerConfig> {
|
||||
return new LocalFileController(DBWorkbench.getPlatform().getWorkspace().getAbsolutePath().resolve(DBFileController.DATA_FOLDER));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CBServerConfigurationControllerEmbedded<CBServerConfig> getServerConfigurationController() {
|
||||
return serverConfigController;
|
||||
|
||||
+8
-6
@@ -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.
|
||||
@@ -20,10 +20,7 @@ import com.google.gson.*;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import io.cloudbeaver.model.app.BaseServerConfigurationController;
|
||||
import io.cloudbeaver.model.app.BaseServletApplication;
|
||||
import io.cloudbeaver.model.config.CBAppConfig;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.model.config.PasswordPolicyConfiguration;
|
||||
import io.cloudbeaver.model.config.SMControllerConfiguration;
|
||||
import io.cloudbeaver.model.config.*;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -374,6 +371,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
try (Writer out = new OutputStreamWriter(Files.newOutputStream(runtimeConfigPath), StandardCharsets.UTF_8)) {
|
||||
Gson gson = new GsonBuilder()
|
||||
.setStrictness(Strictness.LENIENT)
|
||||
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
gson.toJson(configurationProperties, out);
|
||||
@@ -489,6 +487,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
}
|
||||
}
|
||||
appConfigProperties.put("enabledFeatures", Arrays.asList(appConfig.getEnabledFeatures()));
|
||||
appConfigProperties.put("disabledFeatures", Arrays.asList(appConfig.getDisabledFeatures()));
|
||||
if (appConfig.getEnabledAuthProviders() != null) {
|
||||
appConfigProperties.put("enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()));
|
||||
}
|
||||
@@ -508,7 +507,7 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
@NotNull
|
||||
protected Map<String, Object> collectServerConfigProperties(
|
||||
@NotNull CBServerConfig serverConfig,
|
||||
Map<String, Object> originServerConfig
|
||||
@NotNull Map<String, Object> originServerConfig
|
||||
) {
|
||||
var serverConfigProperties = new LinkedHashMap<String, Object>();
|
||||
if (!CommonUtils.isEmpty(serverConfig.getServerName())) {
|
||||
@@ -633,6 +632,9 @@ public abstract class CBServerConfigurationController<T extends CBServerConfig>
|
||||
return serverConfiguration;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public abstract CBServerConfigurationMapper<T, ? extends AdminServerConfig> createServerConfigurationInputMapper();
|
||||
|
||||
public CBAppConfig getAppConfiguration() {
|
||||
return appConfiguration;
|
||||
}
|
||||
|
||||
+7
-1
@@ -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.
|
||||
@@ -19,6 +19,7 @@ package io.cloudbeaver.server;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import io.cloudbeaver.model.config.AdminServerConfig;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.model.config.WebDatabaseConfig;
|
||||
import org.jkiss.code.NotNull;
|
||||
@@ -41,6 +42,11 @@ public class CBServerConfigurationControllerEmbedded<T extends CBServerConfig> e
|
||||
super(serverConfig, homeDirectory);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CBServerConfigurationMapper<T, ? extends AdminServerConfig> createServerConfigurationInputMapper() {
|
||||
return new CBServerConfigurationMapper<>();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Map<String, Object> collectServerConfigProperties(
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import io.cloudbeaver.DBWFeatureSet;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.config.AdminServerConfig;
|
||||
import io.cloudbeaver.model.config.CBAppConfig;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import io.cloudbeaver.utils.ServletAppUtils;
|
||||
import io.cloudbeaver.utils.WebDataSourceUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class CBServerConfigurationMapper<C extends CBServerConfig, I extends AdminServerConfig> {
|
||||
private static final Log log = Log.getLog(CBServerConfigurationMapper.class);
|
||||
|
||||
@NotNull
|
||||
public CBServerConfigurations loadServerConfigurationsFromParams(@NotNull Map<String, Object> params) {
|
||||
CBAppConfig appConfig = new CBAppConfig(CBApplication.getInstance().getAppConfiguration());
|
||||
C serverConfig = createServerConfiguration();
|
||||
serverConfig.setServerName(CBApplication.getInstance().getServerName());
|
||||
serverConfig.setMaxSessionIdleTime(CBApplication.getInstance().getMaxSessionIdleTime());
|
||||
String adminName = null;
|
||||
String adminPassword = null;
|
||||
|
||||
if (!params.isEmpty()) { // FE can send an empty configuration
|
||||
I config = createInputConfiguration(params);
|
||||
adminName = config.getAdminName();
|
||||
adminPassword = config.getAdminPassword();
|
||||
|
||||
populateConfigurations(config, serverConfig, appConfig);
|
||||
}
|
||||
|
||||
return new CBServerConfigurations(
|
||||
serverConfig,
|
||||
appConfig,
|
||||
adminName,
|
||||
adminPassword
|
||||
);
|
||||
}
|
||||
|
||||
protected void populateConfigurations(@NotNull I input, @NotNull C serverConfig, @NotNull CBAppConfig appConfig) {
|
||||
appConfig.setAnonymousAccessEnabled(input.isAnonymousAccessEnabled());
|
||||
appConfig.setSupportsCustomConnections(input.isCustomConnectionsEnabled());
|
||||
appConfig.setPublicCredentialsSaveEnabled(input.isPublicCredentialsSaveEnabled());
|
||||
appConfig.setAdminCredentialsSaveEnabled(input.isAdminCredentialsSaveEnabled());
|
||||
updateDisabledFeaturesConfig(appConfig, input.getEnabledFeatures());
|
||||
// custom logic for enabling embedded drivers
|
||||
updateDisabledDriversConfig(appConfig, input.getDisabledDrivers());
|
||||
appConfig.setResourceManagerEnabled(input.isResourceManagerEnabled());
|
||||
appConfig.setSecretManagerEnabled(input.isSecretManagerEnabled());
|
||||
|
||||
if (CommonUtils.isEmpty(input.getEnabledAuthProviders())) {
|
||||
// All of them
|
||||
appConfig.setEnabledAuthProviders(new String[0]);
|
||||
} else {
|
||||
appConfig.setEnabledAuthProviders(input.getEnabledAuthProviders().toArray(new String[0]));
|
||||
}
|
||||
|
||||
appConfig.setDefaultNavigatorSettings(
|
||||
CBApplication.getInstance().getAppConfiguration().getDefaultNavigatorSettings());
|
||||
|
||||
serverConfig.setServerName(input.getServerName());
|
||||
serverConfig.setMaxSessionIdleTime(input.getSessionExpireTime());
|
||||
if (input.getForceHttps() != null) {
|
||||
serverConfig.setForceHttps(input.getForceHttps());
|
||||
}
|
||||
if (input.getSupportedHosts() != null) {
|
||||
serverConfig.setSupportedHosts(input.getSupportedHosts());
|
||||
}
|
||||
if (input.getBindSessionToIp() != null) {
|
||||
serverConfig.setBindSessionToIp(input.getBindSessionToIp());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDisabledFeaturesConfig(@NotNull CBAppConfig appConfig, @NotNull List<String> enabledFeatures) {
|
||||
Set<String> enabledIds = new LinkedHashSet<>(enabledFeatures);
|
||||
appConfig.setEnabledFeatures(enabledFeatures.toArray(new String[0]));
|
||||
String[] disabledFeatures = ServletAppUtils.getServletApplication().getFeatureRegistry().getWebFeatures().stream()
|
||||
.map(DBWFeatureSet::getId)
|
||||
.filter(id -> !enabledIds.contains(id))
|
||||
.toArray(String[]::new);
|
||||
appConfig.setDisabledFeatures(disabledFeatures);
|
||||
}
|
||||
|
||||
// we disable embedded drivers by default and enable it in enabled drivers list
|
||||
// that's why we need so complicated logic for disabling drivers
|
||||
|
||||
private void updateDisabledDriversConfig(@NotNull CBAppConfig appConfig, @NotNull String[] disabledDriversConfig) {
|
||||
Set<String> disabledIds = new LinkedHashSet<>(Arrays.asList(disabledDriversConfig));
|
||||
Set<String> enabledIds = new LinkedHashSet<>(Arrays.asList(appConfig.getEnabledDrivers()));
|
||||
|
||||
// remove all disabled embedded drivers from enabled drivers list
|
||||
enabledIds.removeAll(disabledIds);
|
||||
|
||||
// enable embedded driver if it is not in disabled drivers list
|
||||
for (String driverId : appConfig.getDisabledDrivers()) {
|
||||
if (disabledIds.contains(driverId)) {
|
||||
// driver is also disabled
|
||||
continue;
|
||||
}
|
||||
// driver is removed from disabled list
|
||||
// we need to enable if it is embedded
|
||||
try {
|
||||
DBPDriver driver = WebDataSourceUtils.getDriverById(driverId);
|
||||
if (driver.isEmbedded()) {
|
||||
enabledIds.add(driverId);
|
||||
}
|
||||
} catch (DBWebException e) {
|
||||
log.error("Failed to find driver by id", e);
|
||||
}
|
||||
}
|
||||
appConfig.setDisabledDrivers(disabledDriversConfig);
|
||||
appConfig.setEnabledDrivers(enabledIds.toArray(String[]::new));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected C createServerConfiguration() {
|
||||
return (C) new CBServerConfig();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected I createInputConfiguration(@NotNull Map<String, Object> params) {
|
||||
return (I) new AdminServerConfig(params);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import io.cloudbeaver.model.config.CBAppConfig;
|
||||
import io.cloudbeaver.model.config.CBServerConfig;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
|
||||
public record CBServerConfigurations(
|
||||
@NotNull CBServerConfig serverConfig,
|
||||
@NotNull CBAppConfig appConfig,
|
||||
@Nullable String adminName,
|
||||
@Nullable String adminPassword
|
||||
) {
|
||||
}
|
||||
+1
@@ -227,6 +227,7 @@ public class CBSessionManager implements WebAppSessionManager {
|
||||
throw new DBWebException(e);
|
||||
}
|
||||
oldWebSession.migrateEventHandlersTo(newWebSession);
|
||||
oldWebSession.getEventsFilter().migrateTo(newWebSession.getEventsFilter());
|
||||
String oldSessionId = oldWebSession.getSessionId();
|
||||
synchronized (sessionMap) {
|
||||
sessionMap.remove(oldSessionId);
|
||||
|
||||
@@ -3,16 +3,11 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Server
|
||||
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
|
||||
Bundle-Version: 26.1.2.qualifier
|
||||
Bundle-Release-Date: 20260706
|
||||
Bundle-Version: 26.2.1.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: org.jkiss.dbeaver.model.sql;visibility:=reexport,
|
||||
org.jkiss.dbeaver.data.gis,
|
||||
org.jkiss.bundle.jetty.server;visibility:=reexport,
|
||||
org.jkiss.bundle.graphql;visibility:=reexport,
|
||||
org.jkiss.bundle.apache.dbcp,
|
||||
org.jkiss.dbeaver.net.ssh,
|
||||
Require-Bundle: org.jkiss.bundle.jetty.server;visibility:=reexport,
|
||||
io.cloudbeaver.model;visibility:=reexport,
|
||||
io.cloudbeaver.model.cli,
|
||||
io.cloudbeaver.service.security;visibility:=reexport
|
||||
|
||||
@@ -45,6 +45,9 @@
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSDataSourceUpdatedEventHandlerImpl">
|
||||
<topic id="cb_datasource"/>
|
||||
</eventHandler>
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSNetworkProfileEventHandler">
|
||||
<topic id="cb_profiles"/>
|
||||
</eventHandler>
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSFolderUpdatedEventHandlerImpl">
|
||||
<topic id="cb_datasource_folder"/>
|
||||
</eventHandler>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server</artifactId>
|
||||
<version>26.1.2-SNAPSHOT</version>
|
||||
<version>26.2.1-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Any object (JSON)
|
||||
####################################################
|
||||
# Base schema
|
||||
####################################################
|
||||
|
||||
"Any object (JSON)"
|
||||
scalar Object
|
||||
# Date/Time
|
||||
"Date/Time"
|
||||
scalar DateTime
|
||||
scalar Date
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ type ObjectPropertyInfo {
|
||||
validValues: [ Object ]
|
||||
"Default property value"
|
||||
defaultValue: Object
|
||||
"Constraints for the property value"
|
||||
constraints: PropertyConstraints @since(version: "26.2.0")
|
||||
"Property value length"
|
||||
length: ObjectPropertyLength!
|
||||
"List of supported features (e.g., system, hidden, inherited, foreign, expensive)"
|
||||
@@ -41,6 +43,13 @@ type ObjectPropertyInfo {
|
||||
conditions: [Condition!] @since(version: "25.0.1")
|
||||
}
|
||||
|
||||
type PropertyConstraints @since(version: "26.2.0") {
|
||||
"Minimum allowed value for numeric properties"
|
||||
min: Float
|
||||
"Maximum allowed value for numeric properties"
|
||||
max: Float
|
||||
}
|
||||
|
||||
enum ObjectPropertyLength {
|
||||
"1 character"
|
||||
TINY,
|
||||
@@ -556,7 +565,7 @@ type ConnectionInfo {
|
||||
supportedDataFormats: [ ResultDataFormat! ]!
|
||||
configurationType: DriverConfigurationType
|
||||
|
||||
#Access properties
|
||||
"Access properties"
|
||||
canViewSettings: Boolean!
|
||||
canEdit: Boolean!
|
||||
canDelete: Boolean!
|
||||
@@ -570,6 +579,31 @@ type ConnectionInfo {
|
||||
|
||||
"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")
|
||||
|
||||
connectionType: ID! @since(version: "26.1.2")
|
||||
|
||||
"Driver configuration"
|
||||
driverConfiguration: DriverConfiguration! @since(version: "26.1.2")
|
||||
}
|
||||
|
||||
"Database-dependent metadata describing features supported by the driver"
|
||||
type DriverConfiguration {
|
||||
"Supported 'on duplicate key' replace methods for this connection's SQL dialect"
|
||||
supportedInsertReplaceMethods: [DictionaryValueInfo!]!
|
||||
"Whether the connection's database supports bulk load during data import"
|
||||
supportsBulkLoad: Boolean!
|
||||
"Whether the connection's database supports transactions"
|
||||
supportsTransactions: Boolean!
|
||||
}
|
||||
|
||||
"Generic dictionary value: an identifier with a human readable name and an optional description"
|
||||
type DictionaryValueInfo {
|
||||
"Value ID"
|
||||
id: ID!
|
||||
"Human readable value name"
|
||||
name: String!
|
||||
"Value description"
|
||||
description: String
|
||||
}
|
||||
|
||||
type ConnectionFolderInfo {
|
||||
@@ -633,6 +667,39 @@ type LogEntry {
|
||||
stackTrace: String
|
||||
}
|
||||
|
||||
type ConnectionType @since(version: "26.1.2") {
|
||||
"Unique identifier of the connection type"
|
||||
id: ID!
|
||||
"Human-readable name of the connection type"
|
||||
name: String!
|
||||
"Description of the connection type"
|
||||
description: String
|
||||
"Color of the connection type (for light theme)"
|
||||
colorLight: String!
|
||||
"Alternative color of the connection type (for dark theme)"
|
||||
colorDark: String
|
||||
"Is autocommit enabled for this connection type"
|
||||
autocommit: Boolean!
|
||||
"Confirm execution of queries for this connection type"
|
||||
confirmExecute: Boolean!
|
||||
"Confirm data change operations (INSERT, UPDATE, DELETE) for this connection type"
|
||||
confirmDataChange: Boolean!
|
||||
"Smart commit mode is enabled for this connection type"
|
||||
smartCommit: Boolean!
|
||||
"Recover smart commit mode is enabled for this connection type"
|
||||
smartCommitRecover: Boolean!
|
||||
"Auto-close transactions is enabled for this connection type"
|
||||
autoCloseTransactions: Boolean!
|
||||
"Auto-close idle transactions period in seconds for this connection type"
|
||||
closeIdleTransactionPeriod: Int!
|
||||
"Auto-close connections is enabled for this connection type"
|
||||
autoCloseConnections: Boolean!
|
||||
"Auto-close idle connections period in seconds for this connection type"
|
||||
closeIdleConnectionPeriod: Int!
|
||||
"Specifies whether this connection type is system-defined or user-defined. System-defined connection types cannot be deleted."
|
||||
predefined: Boolean!
|
||||
}
|
||||
|
||||
####################################################
|
||||
# Input types
|
||||
####################################################
|
||||
@@ -679,7 +746,7 @@ input ConnectionConfig {
|
||||
"ID of database driver"
|
||||
driverId: ID
|
||||
|
||||
# Custom connection parameters (all optional)
|
||||
"Custom connection parameters (all optional)"
|
||||
host: String
|
||||
port: String
|
||||
serverName: String
|
||||
@@ -745,6 +812,8 @@ input ConnectionConfig {
|
||||
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")
|
||||
|
||||
connectionType: ID @since(version: "26.1.2")
|
||||
}
|
||||
|
||||
####################################################
|
||||
@@ -790,6 +859,8 @@ extend type Query {
|
||||
|
||||
"Returns connection driver properties based on configuration"
|
||||
listConnectionDriverProperties( projectId: ID!, config: ConnectionConfig! ): [ObjectPropertyInfo!]!
|
||||
|
||||
listConnectionTypes: [ConnectionType!]! @since(version: "26.1.2")
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
@@ -862,4 +933,3 @@ extend type Mutation {
|
||||
"Get async task info by ID"
|
||||
asyncTaskInfo(id: String!, removeOnFinish: Boolean!): AsyncTaskInfo!
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
####################################################
|
||||
# Datasource events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
|
||||
"Datasource created"
|
||||
|
||||
@@ -31,6 +31,10 @@ enum CBServerEventId {
|
||||
|
||||
"Open URL request in client"
|
||||
cb_open_url @since(version: "25.3.1")
|
||||
"User-specific object settings updated"
|
||||
cb_object_settings_updated @since(version: "26.1.4")
|
||||
"User-specific object settings deleted"
|
||||
cb_object_settings_deleted @since(version: "26.1.4")
|
||||
}
|
||||
|
||||
"Events sent by client"
|
||||
@@ -57,6 +61,7 @@ enum CBEventTopic {
|
||||
cb_workspace_configuration @since(version: "25.1.1")
|
||||
cb_notification @since(version: "25.2.1")
|
||||
cb_session_action @since(version: "25.3.1")
|
||||
cb_object_settings @since(version: "26.1.4")
|
||||
}
|
||||
|
||||
"Base server event interface"
|
||||
@@ -178,6 +183,26 @@ type WSOpenUrlEvent implements CBServerEvent @since(version: "25.3.1") {
|
||||
actionId: String!
|
||||
}
|
||||
|
||||
"Type of the object that owns user-specific settings"
|
||||
enum WSObjectSettingsType @since(version: "26.1.4") {
|
||||
datasource
|
||||
project
|
||||
}
|
||||
|
||||
"User-specific object settings change event (e.g. navigator view)"
|
||||
type WSObjectSettingsEvent implements CBServerEvent @since(version: "26.1.4") {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic!
|
||||
"Type of the object the settings belong to"
|
||||
smObjectType: WSObjectSettingsType!
|
||||
"Project of the object"
|
||||
projectId: String
|
||||
"Id of the object"
|
||||
objectId: String!
|
||||
"Ids of the settings that were changed"
|
||||
settingIds: [String!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
emptyEvent: Boolean
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# SQL events
|
||||
####################################################
|
||||
|
||||
extend enum CBServerEventId {
|
||||
"Database output log updated (e.g., DBMS in Oracle)"
|
||||
cb_database_output_log_updated,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
####################################################
|
||||
# Nodes, objects and properties
|
||||
####################################################
|
||||
|
||||
type ObjectDescriptor {
|
||||
id: Int
|
||||
@@ -143,7 +145,7 @@ extend type Query {
|
||||
"Refreshes node based on the node path"
|
||||
navRefreshNode( nodePath: ID! ): Boolean @deprecated # Use navReloadNode method from Mutation (24.2.4)
|
||||
|
||||
# contextId currently not using
|
||||
"contextId currently not using"
|
||||
navGetStructContainers( projectId: ID, connectionId: ID!, contextId: ID, catalog: ID ): DatabaseStructContainers!
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ type SQLCompletionProposal {
|
||||
# Data read / SQL execution
|
||||
####################################################
|
||||
|
||||
# SQL context must be created for each SQL editor
|
||||
"SQL context must be created for each SQL editor"
|
||||
type SQLContextInfo {
|
||||
|
||||
id: ID!
|
||||
@@ -68,19 +68,39 @@ input SQLDataFilter {
|
||||
limit: Int
|
||||
|
||||
constraints: [ SQLDataFilterConstraint ]
|
||||
"When true constraints are combined with OR instead of AND"
|
||||
anyConstraint: Boolean
|
||||
where: String
|
||||
orderBy: String
|
||||
}
|
||||
|
||||
type SQLResultReference @since(version: "26.1.1") {
|
||||
"Pair (source-row column index, target-entity column name) used to build a navigation filter"
|
||||
type SQLReferenceColumnMapping @since(version: "26.1.2") {
|
||||
"Position in the result-set bindings"
|
||||
sourceColumnIndex: Int!
|
||||
"Column name in the source result set"
|
||||
sourceColumnName: String!
|
||||
"Ordinal position of the column in the target entity definition"
|
||||
targetColumnIndex: Int!
|
||||
"Column name in the target entity"
|
||||
targetColumnName: String!
|
||||
}
|
||||
|
||||
type SQLResultAssociation @since(version: "26.1.2") {
|
||||
"True for reverse references (incoming FKs)"
|
||||
reference: Boolean!
|
||||
"Name of the association (foreign key)"
|
||||
associationName: String!
|
||||
"Fully qualified name of the entity"
|
||||
"Name of the catalog the target entity belongs to"
|
||||
targetCatalogName: String
|
||||
"Name of the schema the target entity belongs to"
|
||||
targetSchemaName: String
|
||||
"Plain name of the target entity"
|
||||
targetEntityName: String
|
||||
"Navigator node URI of the target entity, suitable for opening it directly in the UI. Null if the entity is not present in the navigator tree"
|
||||
nodePath: String
|
||||
"Indices of result-set columns that participate as the source-side attributes of this reference"
|
||||
columnIndexList: [Int!]!
|
||||
targetNodePath: String
|
||||
"List of column mappings between the source result set and the target entity"
|
||||
columnMapping: [SQLReferenceColumnMapping!]!
|
||||
}
|
||||
|
||||
type SQLResultColumn {
|
||||
@@ -133,15 +153,12 @@ type SQLResultSet {
|
||||
"Returns list of rows in the result set. Each row contains data and metadata"
|
||||
rowsWithMetaData: [SQLResultRowMetaData] @since(version: "23.3.5")
|
||||
|
||||
"Forward foreign keys navigable from this result set"
|
||||
associations: [SQLResultReference!]! @since(version: "26.1.1")
|
||||
"Reverse references (incoming FKs from other entities) navigable from this result set"
|
||||
references: [SQLResultReference!]! @since(version: "26.1.1")
|
||||
|
||||
# True means that resultset was generated by single entity query
|
||||
# New rows can be added, old rows can be deleted
|
||||
"""
|
||||
True means that resultset was generated by single entity query
|
||||
New rows can be added, old rows can be deleted
|
||||
"""
|
||||
singleEntity: Boolean!
|
||||
# server always returns hasMoreData = false
|
||||
"server always returns hasMoreData = false"
|
||||
hasMoreData: Boolean!
|
||||
"Identifies if result set has row identifier. If true then it is possible update data or load LOB files"
|
||||
hasRowIdentifier: Boolean! @deprecated (reason: "use rowIdentifierState (25.3.4)")
|
||||
@@ -198,7 +215,7 @@ type SQLQueryResults {
|
||||
updateRowCount: Float
|
||||
sourceQuery: String
|
||||
|
||||
# Actual data format of this result
|
||||
"Actual data format of this result"
|
||||
dataFormat: ResultDataFormat
|
||||
|
||||
resultSet: SQLResultSet
|
||||
@@ -206,17 +223,17 @@ type SQLQueryResults {
|
||||
|
||||
type SQLExecuteInfo {
|
||||
|
||||
# Status message
|
||||
"Status message"
|
||||
statusMessage: String
|
||||
# Execute time (ms)
|
||||
"Execute time (ms)"
|
||||
duration: Int!
|
||||
# Actual conditions applied to query
|
||||
"Actual conditions applied to query"
|
||||
filterText: String
|
||||
# Full query that was executed, contains all used filters
|
||||
"Full query that was executed, contains all used filters"
|
||||
fullQuery: String
|
||||
# Original query that was executed, without modifications
|
||||
"Original query that was executed, without modifications"
|
||||
originalQuery: String
|
||||
# Results
|
||||
"Results"
|
||||
results: [ SQLQueryResults! ]!
|
||||
}
|
||||
|
||||
@@ -281,6 +298,8 @@ type SQLQueryGenerator {
|
||||
input SQLQueryGeneratorOptions {
|
||||
useFullyQualifiedNames: Boolean!
|
||||
compactSql: Boolean!
|
||||
"Include nested objects, comments and permissions in generated DDL (since 26.0.4)"
|
||||
showFullDdl: Boolean
|
||||
}
|
||||
|
||||
####################################################
|
||||
@@ -328,6 +347,15 @@ type TransactionLogInfos {
|
||||
|
||||
extend type Query {
|
||||
|
||||
"Foreign-key navigation metadata for the given result set. Set isReference to true for reverse references, false for forward associations, or omit it to get both"
|
||||
sqlResultAssociations(
|
||||
projectId: ID,
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
resultsId: ID!,
|
||||
isReference: Boolean
|
||||
): [SQLResultAssociation!]! @since(version: "26.1.2")
|
||||
|
||||
"Returns SQL dialect info for the specified connection"
|
||||
sqlDialectInfo( projectId: ID, connectionId: ID! ): SQLDialectInfo
|
||||
|
||||
@@ -373,7 +401,7 @@ extend type Query {
|
||||
options: Object @deprecated(reason: "use generatorOptions (26.0.3)")
|
||||
nodePathList: [String!]!,
|
||||
generatorOptions: SQLQueryGeneratorOptions
|
||||
): String!
|
||||
): String! @deprecated(reason: "use asyncSqlGenerateEntityQuery")
|
||||
|
||||
"""
|
||||
Returns an auto-generated SQL query based on provided rows data.
|
||||
@@ -467,19 +495,6 @@ extend type Mutation {
|
||||
dataFormat: ResultDataFormat
|
||||
): AsyncTaskInfo!
|
||||
|
||||
"Creates async task for navigating a foreign-key reference exposed in SQLResultSet.references. Pass associationName, isReference and any one of the chosen reference's columnIndices (used to locate the source entity)."
|
||||
asyncSqlNavigateForeignKey(
|
||||
projectId: ID,
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
resultsId: ID!,
|
||||
row: SQLResultRow!,
|
||||
columnIndex: Int!,
|
||||
associationName: String!,
|
||||
isReference: Boolean!,
|
||||
dataFormat: ResultDataFormat
|
||||
): AsyncTaskInfo! @since(version: "26.1.1")
|
||||
|
||||
"Returns transaction log info for the specified project, connection and context"
|
||||
getTransactionLogInfo(
|
||||
projectId: ID!,
|
||||
@@ -582,6 +597,13 @@ extend type Mutation {
|
||||
"Returns row count for async SQL row data count task"
|
||||
asyncSqlRowDataCountResult(taskId: ID!): Int!
|
||||
|
||||
"Creates async task to generate entity SQL. Generated SQL is returned in the taskResult field"
|
||||
asyncSqlGenerateEntityQuery(
|
||||
generatorId: String!,
|
||||
nodePathList: [String!]!,
|
||||
generatorOptions: SQLQueryGeneratorOptions
|
||||
): AsyncTaskInfo!
|
||||
|
||||
"Reads dynamic trace from provided database results"
|
||||
sqlGetDynamicTrace(
|
||||
projectId: ID,
|
||||
|
||||
@@ -50,7 +50,6 @@ import org.jkiss.dbeaver.model.rm.RMProjectType;
|
||||
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
|
||||
import org.jkiss.dbeaver.runtime.properties.PropertyCollector;
|
||||
import org.jkiss.dbeaver.runtime.properties.PropertySourceCustom;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
@@ -209,9 +208,16 @@ public class WebServiceUtils extends WebCommonUtils {
|
||||
dataSourceContainer,
|
||||
cfg
|
||||
);
|
||||
Map<String, Object> connectionProperties = driver.getConnectionProperties();
|
||||
List<DBPPropertyDescriptor> propertyList = new ArrayList<>(Arrays.asList(properties));
|
||||
Set<String> propertyNames = propertyList.stream().map(DBPPropertyDescriptor::getId).collect(Collectors.toSet());
|
||||
Map<String, Object> connectionProperties = new LinkedHashMap<>(driver.getConnectionProperties());
|
||||
// In case of collision, value from connectionProperties will be used for a driver property.
|
||||
// Default value of property will be from driver.
|
||||
for (Map.Entry<String, Object> connProp : connectionProperties.entrySet()) {
|
||||
String propName = connProp.getKey();
|
||||
if (propertyNames.contains(propName)) {
|
||||
continue;
|
||||
}
|
||||
Object propValue = connProp.getValue();
|
||||
DBPPropertyDescriptor dbpPropertyDescriptor = new PropertyDescriptor(
|
||||
null,
|
||||
@@ -223,19 +229,20 @@ public class WebServiceUtils extends WebCommonUtils {
|
||||
propValue,
|
||||
null
|
||||
);
|
||||
properties = ArrayUtils.add(DBPPropertyDescriptor.class, properties, dbpPropertyDescriptor);
|
||||
propertyList.add(dbpPropertyDescriptor);
|
||||
cfg.setProperty(propName, (String) propValue);
|
||||
}
|
||||
if (properties == null) {
|
||||
if (propertyList.isEmpty()) {
|
||||
return new WebPropertyInfo[0];
|
||||
}
|
||||
connectionProperties.putAll(cfg.getProperties());
|
||||
|
||||
PropertySourceCustom propertySource = new PropertySourceCustom(
|
||||
properties,
|
||||
cfg.getProperties()
|
||||
propertyList,
|
||||
connectionProperties
|
||||
);
|
||||
|
||||
return Arrays.stream(properties)
|
||||
return propertyList.stream()
|
||||
.map(p -> new WebPropertyInfo(webSession, p, propertySource)).toArray(WebPropertyInfo[]::new);
|
||||
} catch (DBException e) {
|
||||
log.error("Error reading driver properties:\n" + e.getMessage());
|
||||
|
||||
+22
-7
@@ -21,8 +21,10 @@ import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
import org.jkiss.dbeaver.model.DatabaseURL;
|
||||
import org.jkiss.dbeaver.model.connection.*;
|
||||
import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNative;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
@@ -180,13 +182,26 @@ public class WebDatabaseDriverInfo {
|
||||
@NotNull
|
||||
@Property
|
||||
public WebPropertyInfo[] getDriverProperties() throws DBWebException {
|
||||
DBPConnectionConfiguration cfg = new DBPConnectionConfiguration();
|
||||
cfg.setUrl(CommonUtils.notEmpty(driver.getSampleURL()));
|
||||
cfg.setHostName(CBConstants.HOST_LOCALHOST);
|
||||
cfg.setHostPort(driver.getDefaultPort());
|
||||
cfg.setDatabaseName(driver.getDefaultDatabase());
|
||||
cfg.setUrl(driver.getConnectionURL(cfg));
|
||||
return WebServiceUtils.getDriverProperties(webSession, driver, null, cfg);
|
||||
try {
|
||||
DBPConnectionConfiguration cfg = new DBPConnectionConfiguration();
|
||||
cfg.setUrl(CommonUtils.notEmpty(driver.getSampleURL()));
|
||||
DatabaseURL.Pattern dbUrlPattern = DatabaseURL.getUrlPattern(CommonUtils.notEmpty(driver.getSampleURL()));
|
||||
for (String propName : dbUrlPattern.getMandatoryPropertyNames()) {
|
||||
switch (propName) {
|
||||
case DBConstants.PROP_HOST -> cfg.setHostName(CBConstants.HOST_LOCALHOST);
|
||||
case DBConstants.PROP_PORT -> cfg.setHostPort(CommonUtils.notNull(driver.getDefaultPort(), "0"));
|
||||
case DBConstants.PROP_SERVER -> cfg.setServerName(CommonUtils.notNull(driver.getDefaultServer(), propName));
|
||||
case DBConstants.PROP_USER -> cfg.setUserName(CommonUtils.notNull(driver.getDefaultUser(), propName));
|
||||
case DBConstants.PROP_DATABASE, DBConstants.PROP_FOLDER, DBConstants.PROP_FILE ->
|
||||
cfg.setDatabaseName(CommonUtils.notNull(driver.getDefaultDatabase(), propName));
|
||||
default -> log.debug("Unexpected mandatory URL property " + propName);
|
||||
}
|
||||
}
|
||||
cfg.setUrl(driver.getConnectionURL(cfg));
|
||||
return WebServiceUtils.getDriverProperties(webSession, driver, null, cfg);
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error obtaining driver properties", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Property
|
||||
|
||||
+6
-2
@@ -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.
|
||||
@@ -19,6 +19,7 @@ package io.cloudbeaver.server.events;
|
||||
import io.cloudbeaver.server.BaseWebPlatform;
|
||||
import io.cloudbeaver.server.WebAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.websocket.WSEventHandler;
|
||||
@@ -33,7 +34,10 @@ public class WSDeleteTempFileHandler implements WSEventHandler<WSEventDeleteTemp
|
||||
|
||||
private static final Log log = Log.getLog(WSDeleteTempFileHandler.class);
|
||||
|
||||
public void resetTempFolder(String sessionId) {
|
||||
public void resetTempFolder(@Nullable String sessionId) {
|
||||
if (sessionId == null) {
|
||||
return;
|
||||
}
|
||||
Path path = WebAppUtils.getWebPlatform()
|
||||
.getTempFolder(new VoidProgressMonitor(), BaseWebPlatform.TEMP_FILE_FOLDER)
|
||||
.resolve(sessionId);
|
||||
|
||||
+2
-5
@@ -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.
|
||||
@@ -20,7 +20,6 @@ import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.WorkspaceConfigEventManager;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSWorkspaceConfigurationChangedEvent;
|
||||
import org.jkiss.dbeaver.registry.driver.DriverDescriptorSerializerLegacy;
|
||||
|
||||
public class WSEventHandlerWorkspaceConfigUpdate extends WSDefaultEventHandler<WSWorkspaceConfigurationChangedEvent> {
|
||||
private static final Log log = Log.getLog(WSEventHandlerWorkspaceConfigUpdate.class);
|
||||
@@ -30,8 +29,6 @@ public class WSEventHandlerWorkspaceConfigUpdate extends WSDefaultEventHandler<W
|
||||
String configFileName = event.getConfigFilePath();
|
||||
log.info("Config file changed: " + configFileName);
|
||||
WorkspaceConfigEventManager.fireConfigChangedEvent(configFileName);
|
||||
if (DriverDescriptorSerializerLegacy.DRIVERS_FILE_NAME.equals(event.getConfigFilePath())) {
|
||||
super.handleEvent(event);
|
||||
}
|
||||
super.handleEvent(event);
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.server.events;
|
||||
|
||||
import io.cloudbeaver.WebSessionProjectImpl;
|
||||
import io.cloudbeaver.model.session.BaseWebSession;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.websocket.event.datasource.WSNetworkProfileEvent;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
|
||||
public class WSNetworkProfileEventHandler extends WSDefaultEventHandler<WSNetworkProfileEvent> {
|
||||
private static final Log log = Log.getLog(WSNetworkProfileEventHandler.class);
|
||||
|
||||
@Override
|
||||
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSNetworkProfileEvent event) {
|
||||
if (event.getProjectId() != null) {
|
||||
if (activeUserSession instanceof WebSession webSession) {
|
||||
WebSessionProjectImpl project = webSession.getProjectById(event.getProjectId());
|
||||
if (project == null) {
|
||||
log.debug("Project " + event.getProjectId() + " is not found in session " + webSession.getSessionId());
|
||||
return;
|
||||
}
|
||||
project.getDataSourceRegistry().getNetworkProfiles().reloadProfiles();
|
||||
} else {
|
||||
super.updateSessionData(activeUserSession, event);
|
||||
}
|
||||
} else {
|
||||
DBWorkbench.getPlatform().getNetworkProfiles().reloadProfiles();
|
||||
super.updateSessionData(activeUserSession, event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAcceptableInSession(@NotNull BaseWebSession activeUserSession, @NotNull WSNetworkProfileEvent event) {
|
||||
if (event.getProjectId() != null && !activeUserSession.isProjectAccessible(event.getProjectId())) {
|
||||
return false;
|
||||
}
|
||||
return super.isAcceptableInSession(activeUserSession, event);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -24,7 +24,7 @@ public class WSServerConfigurationChangedEventHandler extends WSDefaultEventHand
|
||||
|
||||
@Override
|
||||
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSServerConfigurationChangedEvent event) {
|
||||
activeUserSession.refreshUserData();
|
||||
activeUserSession.refreshUserPermissions();
|
||||
super.updateSessionData(activeUserSession, event);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -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.
|
||||
@@ -44,12 +44,17 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {
|
||||
private final BlockingQueue<WSEvent> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
|
||||
private final CBClientEventProcessor processor;
|
||||
private volatile long lastPoll;
|
||||
private final CBEventsLongPollingServlet servlet;
|
||||
|
||||
public CBEventsLongPolling(@NotNull BaseWebSession webSession) {
|
||||
public CBEventsLongPolling(
|
||||
@NotNull BaseWebSession webSession,
|
||||
@NotNull CBEventsLongPollingServlet owner
|
||||
) {
|
||||
this.webSession = webSession;
|
||||
this.lastPoll = System.currentTimeMillis();
|
||||
this.webSession.addEventHandler(this);
|
||||
this.processor = new CBClientEventProcessor(this.webSession);
|
||||
this.servlet = owner;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -90,8 +95,10 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {
|
||||
|
||||
@Override
|
||||
public void migrateToSession(@NotNull BaseWebSession newSession) {
|
||||
String oldSid = this.webSession.getSessionId();
|
||||
this.webSession = newSession;
|
||||
this.processor.setWebSession(newSession);
|
||||
servlet.rekeySession(oldSid, newSession.getSessionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,6 +119,7 @@ public class CBEventsLongPolling implements CBWebSessionEventHandler {
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CBEventsLongPolling{" +
|
||||
|
||||
+19
-20
@@ -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.
|
||||
@@ -83,7 +83,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
return;
|
||||
}
|
||||
|
||||
CBEventsLongPolling ps = getOrCreatePollSession(ws);
|
||||
CBEventsLongPolling ps = getOrCreatePollSession(ws, resp);
|
||||
ps.onPoll();
|
||||
|
||||
List<WSEvent> events = ps.pollEvents(POLL_TIMEOUT_SEC);
|
||||
@@ -111,7 +111,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
return;
|
||||
}
|
||||
|
||||
CBEventsLongPolling ps = getOrCreatePollSession(ws);
|
||||
CBEventsLongPolling ps = getOrCreatePollSession(ws, resp);
|
||||
ps.onUserActivity();
|
||||
|
||||
String json = new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
@@ -163,23 +163,12 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
) {
|
||||
|
||||
try {
|
||||
BaseWebSession ws = resolveSession(req);
|
||||
|
||||
resp.setHeader(WSConstants.WS_SESSION_HEADER, ws.getSessionId());
|
||||
ServletAppUtils.addResponseCookie(
|
||||
req,
|
||||
resp,
|
||||
CBConstants.CB_SESSION_COOKIE_NAME,
|
||||
ws.getSessionId(),
|
||||
-1
|
||||
);
|
||||
|
||||
return ws;
|
||||
return resolveSession(req);
|
||||
} catch (SMException e) {
|
||||
log.debug("LP request rejected: access token expired");
|
||||
sendError(resp, HttpConstants.CODE_TOKEN_EXPIRED, e.getMessage());
|
||||
} catch (SecurityException e) {
|
||||
log.warn("LP session resolve failed", e);
|
||||
log.debug("LP request rejected: " + e.getMessage());
|
||||
sendError(resp, HttpConstants.CODE_UNAUTHORIZED, e.getMessage());
|
||||
}
|
||||
return null;
|
||||
@@ -214,7 +203,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CBEventsLongPolling getOrCreatePollSession(@NotNull BaseWebSession ws) {
|
||||
private CBEventsLongPolling getOrCreatePollSession(@NotNull BaseWebSession ws, @NotNull HttpServletResponse resp) {
|
||||
final String sid = ws.getSessionId();
|
||||
|
||||
return sessions.compute(sid, (key, existing) -> {
|
||||
@@ -222,9 +211,11 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
return existing;
|
||||
}
|
||||
|
||||
CBEventsLongPolling ps = new CBEventsLongPolling(ws);
|
||||
CBEventsLongPolling ps = new CBEventsLongPolling(ws, this);
|
||||
|
||||
ps.handleWebSessionEvent(new WSSocketConnectedEvent(ws.getApplication().getApplicationRunId()));
|
||||
resp.setHeader(WSConstants.WS_SESSION_HEADER, sid);
|
||||
resp.setHeader(WSConstants.WS_SESSION_HEADER_LEGACY, sid);
|
||||
log.debug("HTTP Long-Poll channel opened for session " + sid);
|
||||
|
||||
return ps;
|
||||
@@ -253,7 +244,8 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
|
||||
@NotNull
|
||||
protected static WebHttpRequestInfo createRequestInfo(
|
||||
@Nullable String sessionId, @NotNull HttpServletRequest req
|
||||
@Nullable String sessionId,
|
||||
@NotNull HttpServletRequest req
|
||||
) {
|
||||
return new WebHttpRequestInfo(
|
||||
sessionId,
|
||||
@@ -263,6 +255,13 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
);
|
||||
}
|
||||
|
||||
void rekeySession(@NotNull String oldSid, @NotNull String newSid) {
|
||||
CBEventsLongPolling moved = sessions.get(oldSid);
|
||||
if (moved != null) {
|
||||
sessions.put(newSid, moved);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a {@link BaseWebSession} from an incoming HTTP request.
|
||||
* <p>
|
||||
@@ -306,7 +305,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
|
||||
@Override
|
||||
public BaseWebSession resolve(@NotNull HttpServletRequest req) throws SMException {
|
||||
|
||||
String sid = req.getHeader(WSConstants.WS_SESSION_HEADER);
|
||||
String sid = ServletAppUtils.getHeaderOrLegacy(req, WSConstants.WS_SESSION_HEADER, WSConstants.WS_SESSION_HEADER_LEGACY);
|
||||
String token = req.getHeader(WSConstants.WS_AUTH_HEADER);
|
||||
|
||||
if (CommonUtils.isEmpty(sid) && CommonUtils.isEmpty(token)) {
|
||||
|
||||
+6
-1
@@ -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.
|
||||
@@ -47,6 +47,11 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
|
||||
} else {
|
||||
this.webSession = (BaseWebSession) session.getUserProperties()
|
||||
.get(CBWebSocketServerConfigurator.PROP_WEB_SESSION);
|
||||
if (this.webSession == null) {
|
||||
log.debug("No web session for websocket connection, closing");
|
||||
close();
|
||||
return;
|
||||
}
|
||||
this.webSession.addEventHandler(this);
|
||||
handleEvent(new WSSocketConnectedEvent(webSession.getApplication().getApplicationRunId()));
|
||||
log.debug("EventWebSocket connected to the " + webSession.getSessionId() + " session");
|
||||
|
||||
+3
-2
@@ -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.
|
||||
@@ -88,7 +88,8 @@ public class CBWebSocketServerConfigurator extends ServerEndpointConfig.Configur
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
if (sec.getUserProperties().get(PROP_WEB_SESSION) == null) {
|
||||
throw new RuntimeException("No web session found for websocket request");
|
||||
// no throwing exception here. will be handled in onOpen
|
||||
log.debug("No web session found for websocket request, connection will be closed on open");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -275,6 +275,12 @@ public abstract class WebServiceBindingBase<API_TYPE extends DBWService> impleme
|
||||
if (project == null) {
|
||||
throw new DBException("Project not found:" + projectId);
|
||||
}
|
||||
var customConnectionsEnabled =
|
||||
ServletAppUtils.getServletApplication().getAppConfiguration().isSupportsCustomConnections();
|
||||
if (!customConnectionsEnabled && project.isPrivateProject()) {
|
||||
throw new DBWebExceptionAccessDenied("Access to private project is denied");
|
||||
}
|
||||
|
||||
RMProject rmProject = project.getRMProject();
|
||||
|
||||
for (String reqProjectPermission : requireProjectPermissions) {
|
||||
|
||||
+13
-2
@@ -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.
|
||||
@@ -71,7 +71,7 @@ public abstract class WebServiceServletBase extends HttpServlet {
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
errorMessage = e.getMessage();
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error processing request: " + errorMessage);
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Error processing request: " + flattenForResponse(errorMessage));
|
||||
} finally {
|
||||
if (WebAppUtils.getWebApplication() instanceof ApiCallInterceptor apiCallInterceptor) {
|
||||
apiCallInterceptor.onApiCallEvent(
|
||||
@@ -83,6 +83,17 @@ public abstract class WebServiceServletBase extends HttpServlet {
|
||||
|
||||
protected abstract void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException;
|
||||
|
||||
/**
|
||||
* Replaces control characters (line breaks, tabs, etc.) with spaces so the message can be safely
|
||||
* embedded into the JSON error body produced by the servlet container's error handler.
|
||||
*/
|
||||
private String flattenForResponse(String message) {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
return message.replaceAll("\\p{Cntrl}+", " ").trim();
|
||||
}
|
||||
|
||||
protected Map<String, Object> getVariables(HttpServletRequest request) {
|
||||
return gson.fromJson(request.getParameter(REQUEST_PARAM_VARIABLES), MAP_STRING_OBJECT_TYPE);
|
||||
}
|
||||
|
||||
+8
-4
@@ -25,6 +25,7 @@ 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.connection.DBPConnectionType;
|
||||
import org.jkiss.dbeaver.model.rm.RMConstants;
|
||||
import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings;
|
||||
import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor;
|
||||
@@ -115,6 +116,9 @@ public interface DBWServiceCore extends DBWService {
|
||||
@NotNull Map<String, Object> connectionConfig
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
List<DBPConnectionType> getConnectionTypes(@NotNull WebSession webSession, @Nullable String id);
|
||||
|
||||
@WebAction
|
||||
WebConnectionInfo getConnectionState(WebSession webSession, @Nullable String projectId, String connectionId) throws DBWebException;
|
||||
|
||||
@@ -159,17 +163,17 @@ public interface DBWServiceCore extends DBWService {
|
||||
@NotNull Map<String, Object> connectionConfig
|
||||
) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
|
||||
WebConnectionInfo testConnection(
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable String projectId,
|
||||
@Nullable @WebObjectId String projectId,
|
||||
@NotNull Map<String, Object> connectionConfig
|
||||
) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
@WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT})
|
||||
WebNetworkEndpointInfo testNetworkHandler(
|
||||
@NotNull WebSession webSession,
|
||||
@Nullable String projectId,
|
||||
@Nullable @WebObjectId String projectId,
|
||||
@Nullable String connectionId,
|
||||
@NotNull WebNetworkHandlerConfigInput nhConfig
|
||||
) throws DBWebException;
|
||||
|
||||
+5
@@ -87,6 +87,11 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
|
||||
getArgumentVal(env, "projectId"),
|
||||
getArgumentVal(env, "config")
|
||||
)
|
||||
).dataFetcher(
|
||||
"listConnectionTypes", env -> getService(env).getConnectionTypes(
|
||||
getWebSession(env),
|
||||
getArgument(env, "id")
|
||||
)
|
||||
)
|
||||
;
|
||||
|
||||
|
||||
+30
-14
@@ -45,6 +45,7 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.auth.SMObjectType;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
|
||||
import org.jkiss.dbeaver.model.connection.DBPConnectionType;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.exec.DBCConnectException;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
|
||||
@@ -310,17 +311,32 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
|
||||
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
|
||||
webSession, projectId, configInput.getConnectionId());
|
||||
DataSourceDescriptor testDataSource = getDataSourceDescriptor(webSession, dataSource, configInput, project);
|
||||
DBPConnectionConfiguration connectionConfiguration = new DBPConnectionConfiguration(testDataSource.getConnectionConfiguration());
|
||||
return WebServiceUtils.getDriverProperties(
|
||||
webSession,
|
||||
testDataSource.getDriver(),
|
||||
testDataSource,
|
||||
connectionConfiguration
|
||||
);
|
||||
try {
|
||||
DataSourceDescriptor testDataSource = getDataSourceDescriptor(webSession, dataSource, configInput, project);
|
||||
DBPConnectionConfiguration connectionConfiguration
|
||||
= new DBPConnectionConfiguration(testDataSource.getConnectionConfiguration());
|
||||
return WebServiceUtils.getDriverProperties(
|
||||
webSession,
|
||||
testDataSource.getDriver(),
|
||||
testDataSource,
|
||||
connectionConfiguration
|
||||
);
|
||||
} catch (DBWebException e) {
|
||||
log.error("Error getting driver properties", e);
|
||||
return new WebPropertyInfo[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<DBPConnectionType> getConnectionTypes(@NotNull WebSession webSession, @Nullable String id) {
|
||||
return DataSourceProviderRegistry.getInstance().getConnectionTypes().stream()
|
||||
.map(ct -> id == null || id.equals(ct.getId()) ? ct : null)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebConnectionInfo initConnection(
|
||||
@@ -541,6 +557,9 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
DataSourceDescriptor dataSource = (DataSourceDescriptor) WebDataSourceUtils.getLocalOrGlobalDataSource(
|
||||
webSession, projectId, configInput.getConnectionId());
|
||||
DataSourceDescriptor testDataSource = getDataSourceDescriptor(webSession, dataSource, configInput, project);
|
||||
testDataSource.setTemporary(true);
|
||||
WebConnectionInfo connectionInfo = project.addConnection(testDataSource);
|
||||
connectionInfo.setSavedCredentials(configInput.getCredentials(), configInput.getNetworkHandlersConfig());
|
||||
try {
|
||||
ConnectionTestJob ct = new ConnectionTestJob(
|
||||
testDataSource, param -> {
|
||||
@@ -557,7 +576,6 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
throw new DBWebException("Connection failed", ct.getConnectError());
|
||||
}
|
||||
WebConnectionInfo connectionInfo = project.createConnectionInfo(testDataSource);
|
||||
connectionInfo.setConnectError(ct.getConnectError());
|
||||
connectionInfo.setServerVersion(ct.getServerVersion());
|
||||
connectionInfo.setClientVersion(ct.getClientVersion());
|
||||
@@ -565,6 +583,8 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
return connectionInfo;
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error connecting to database", e);
|
||||
} finally {
|
||||
project.removeConnection(testDataSource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,11 +605,7 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
|
||||
testDataSource = (DataSourceDescriptor) dataSource.createCopy(dataSource.getRegistry());
|
||||
WebDataSourceUtils.setConnectionConfiguration(
|
||||
testDataSource.getDriver(),
|
||||
testDataSource.getConnectionConfiguration(),
|
||||
configInput
|
||||
);
|
||||
project.updateDataSourceContainerFromInput(configInput, testDataSource);
|
||||
if (configInput.getSelectedSecretId() != null) {
|
||||
try {
|
||||
dataSource.listSharedCredentials()
|
||||
|
||||
+24
-7
@@ -33,7 +33,6 @@ import org.jkiss.dbeaver.model.struct.rdb.DBSSchema;
|
||||
import org.jkiss.dbeaver.model.struct.rdb.DBSTable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -58,6 +57,7 @@ public class WebDatabaseObjectInfo {
|
||||
public static final String OBJECT_FEATURE_SCHEMA = "schema";
|
||||
public static final String OBJECT_FEATURE_SCRIPT = "script";
|
||||
public static final String OBJECT_FEATURE_SCRIPT_EXTENDED = "scriptExtended";
|
||||
public static final String OBJECT_FEATURE_SUPPORTS_FULL_DDL = "supportsFullDdl";
|
||||
|
||||
private final WebSession session;
|
||||
private final DBSObject object;
|
||||
@@ -168,10 +168,21 @@ public class WebDatabaseObjectInfo {
|
||||
return features.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private void getObjectFeatures(DBSObject object, List<String> features) {
|
||||
private void getObjectFeatures(@NotNull DBSObject object, @NotNull List<String> features) {
|
||||
if (object instanceof DBPScriptObject) {
|
||||
features.add(OBJECT_FEATURE_SCRIPT);
|
||||
}
|
||||
if (object instanceof DBPScriptObjectExt) {
|
||||
features.add(OBJECT_FEATURE_SCRIPT_EXTENDED);
|
||||
}
|
||||
if (object instanceof DBPScriptObjectExt2 scriptObjectExt2 && (
|
||||
scriptObjectExt2.supportsObjectDefinitionOption(DBPScriptObject.OPTION_INCLUDE_NESTED_OBJECTS)
|
||||
|| scriptObjectExt2.supportsObjectDefinitionOption(DBPScriptObject.OPTION_INCLUDE_COMMENTS)
|
||||
|| scriptObjectExt2.supportsObjectDefinitionOption(DBPScriptObject.OPTION_INCLUDE_PERMISSIONS))
|
||||
) {
|
||||
features.add(OBJECT_FEATURE_SUPPORTS_FULL_DDL);
|
||||
}
|
||||
boolean isDiagramSupported = true;
|
||||
if (object instanceof DBPScriptObject) features.add(OBJECT_FEATURE_SCRIPT);
|
||||
if (object instanceof DBPScriptObjectExt) features.add(OBJECT_FEATURE_SCRIPT_EXTENDED);
|
||||
if (object instanceof DBSDataContainer) {
|
||||
features.add(OBJECT_FEATURE_DATA_CONTAINER);
|
||||
if (((DBSDataContainer) object).isFeatureSupported(DBSDataContainer.FEATURE_DATA_FILTER)) {
|
||||
@@ -181,7 +192,9 @@ public class WebDatabaseObjectInfo {
|
||||
isDiagramSupported = false;
|
||||
}
|
||||
}
|
||||
if (object instanceof DBSDataManipulator) features.add(OBJECT_FEATURE_DATA_MANIPULATOR);
|
||||
if (object instanceof DBSDataManipulator) {
|
||||
features.add(OBJECT_FEATURE_DATA_MANIPULATOR);
|
||||
}
|
||||
if (object instanceof DBSEntity) {
|
||||
features.add(OBJECT_FEATURE_ENTITY);
|
||||
if (object instanceof DBSDataType
|
||||
@@ -192,8 +205,12 @@ public class WebDatabaseObjectInfo {
|
||||
features.add(OBJECT_FEATURE_RELATIONAL_ENTITY);
|
||||
}
|
||||
}
|
||||
if (object instanceof DBSSchema) features.add(OBJECT_FEATURE_SCHEMA);
|
||||
if (object instanceof DBSCatalog) features.add(OBJECT_FEATURE_CATALOG);
|
||||
if (object instanceof DBSSchema) {
|
||||
features.add(OBJECT_FEATURE_SCHEMA);
|
||||
}
|
||||
if (object instanceof DBSCatalog) {
|
||||
features.add(OBJECT_FEATURE_CATALOG);
|
||||
}
|
||||
if (object instanceof DBSObjectContainer objectContainer) {
|
||||
features.add(OBJECT_FEATURE_OBJECT_CONTAINER);
|
||||
try {
|
||||
|
||||
+13
-5
@@ -279,7 +279,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
// in that way all related scripts will use the same context and overwrite the user's define context
|
||||
// So why we need restore the default execution context after refreshing datasource
|
||||
DBCExecutionContext defaultContext = DBUtils.getDefaultContext(refreshableObject, false);
|
||||
DBCExecutionContextDefaults contextDefaults = defaultContext.getContextDefaults();
|
||||
DBCExecutionContextDefaults<?, ?> contextDefaults = defaultContext.getContextDefaults();
|
||||
refreshableObject.refreshObject(monitor);
|
||||
if (contextDefaults != null && contextDefaults.getDefaultSchema() != null
|
||||
&& contextDefaults.getDefaultCatalog() != null) {
|
||||
@@ -461,6 +461,7 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
return node.getNodeUri();
|
||||
}
|
||||
if (node instanceof DBNDatabaseNode dbNode) {
|
||||
checkMetadataEditPermission(dbNode);
|
||||
return renameDatabaseObject(
|
||||
session,
|
||||
dbNode,
|
||||
@@ -545,8 +546,9 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
throw new DBWebException("Navigator node '" + path + "' not found");
|
||||
}
|
||||
checkProjectEditAccess(node, session);
|
||||
if (node instanceof DBNDatabaseNode) {
|
||||
DBSObject object = ((DBNDatabaseNode) node).getObject();
|
||||
if (node instanceof DBNDatabaseNode dbnDatabaseNode) {
|
||||
checkMetadataEditPermission(dbnDatabaseNode);
|
||||
DBSObject object = dbnDatabaseNode.getObject();
|
||||
DBEObjectMaker objectDeleter = DBWorkbench.getPlatform().getEditorsRegistry().getObjectManager(
|
||||
object.getClass(), DBEObjectMaker.class);
|
||||
if (objectDeleter == null || !objectDeleter.canDeleteObject(object)) {
|
||||
@@ -602,9 +604,15 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMetadataEditPermission(@NotNull DBNDatabaseNode node) throws DBException {
|
||||
if (!node.getDataSourceContainer().hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_METADATA)) {
|
||||
throw new DBWebException("Structure edit is restricted for this connection");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkProjectEditAccess(@NotNull DBNNode node, @NotNull WebSession session) throws DBException {
|
||||
BaseWebProjectImpl project = (BaseWebProjectImpl) node.getOwnerProject();
|
||||
if (project == null || !hasNodeEditPermission(session, node, project.getRMProject())) {
|
||||
var project = node.getOwnerProject();
|
||||
if (!(project instanceof BaseWebProjectImpl bwp) || !hasNodeEditPermission(session, node, bwp.getRMProject())) {
|
||||
throw new DBException("Access denied");
|
||||
}
|
||||
}
|
||||
|
||||
+12
-6
@@ -81,6 +81,15 @@ public interface DBWServiceSQL extends DBWService {
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
WebAsyncTaskInfo asyncGenerateEntityQuery(
|
||||
@NotNull WebSession session,
|
||||
@NotNull String generatorId,
|
||||
@NotNull List<String> nodePathList,
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
String sqlGenerateResultSetQuery(
|
||||
@@ -127,15 +136,12 @@ public interface DBWServiceSQL extends DBWService {
|
||||
@Nullable WebDataFormat dataFormat) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebAsyncTaskInfo asyncNavigateForeignKey(
|
||||
List<WebSQLQueryResultAssociation> getSqlResultAssociations(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull WebSQLResultsRow row,
|
||||
int columnIndex,
|
||||
@NotNull String associationName,
|
||||
boolean isReference,
|
||||
@Nullable WebDataFormat dataFormat) throws DBException;
|
||||
@Nullable Boolean isReference
|
||||
) throws DBException;
|
||||
|
||||
/**
|
||||
* Reads dynamic trace from provided database results.
|
||||
|
||||
+60
-9
@@ -22,6 +22,7 @@ import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeConstraint;
|
||||
import org.jkiss.dbeaver.model.data.DBDDataFilter;
|
||||
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.struct.DBSAttributeBase;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
@@ -41,6 +42,7 @@ public class WebSQLDataFilter {
|
||||
private int offset;
|
||||
private int limit;
|
||||
private String where;
|
||||
private boolean anyConstraint;
|
||||
private final List<WebSQLDataFilterConstraint> constraints = new ArrayList<>();
|
||||
|
||||
public WebSQLDataFilter() {
|
||||
@@ -57,9 +59,10 @@ public class WebSQLDataFilter {
|
||||
this.limit = MAX_ROWS_NUMBER;
|
||||
}
|
||||
this.where = CommonUtils.toString(filterProps.get("where"), null);
|
||||
this.anyConstraint = CommonUtils.toBoolean(filterProps.get("anyConstraint"));
|
||||
Object constraints = filterProps.get("constraints");
|
||||
if (constraints instanceof Collection) {
|
||||
for (Object constrItem : (Collection<?>) constraints) {
|
||||
if (constraints instanceof Collection<?> col) {
|
||||
for (Object constrItem : col) {
|
||||
if (constrItem instanceof Map) {
|
||||
this.constraints.add(
|
||||
new WebSQLDataFilterConstraint((Map<String, Object>) constrItem));
|
||||
@@ -92,10 +95,18 @@ public class WebSQLDataFilter {
|
||||
return where;
|
||||
}
|
||||
|
||||
/**
|
||||
* When true constraints are combined with OR instead of AND
|
||||
*/
|
||||
public boolean isAnyConstraint() {
|
||||
return anyConstraint;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static WebSQLDataFilter from(@NotNull DBDDataFilter filter) {
|
||||
var webFilter = new WebSQLDataFilter();
|
||||
webFilter.where = filter.getWhere();
|
||||
webFilter.anyConstraint = filter.isAnyConstraint();
|
||||
for (DBDAttributeConstraint constraint : filter.getConstraints()) {
|
||||
webFilter.constraints.add(WebSQLDataFilterConstraint.from(constraint));
|
||||
}
|
||||
@@ -103,24 +114,32 @@ public class WebSQLDataFilter {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DBDDataFilter makeDataFilter(@Nullable WebSQLResultsInfo resultInfo) throws DBException {
|
||||
public DBDDataFilter makeDataFilter(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@Nullable WebSQLResultsInfo resultInfo
|
||||
) throws DBException {
|
||||
DBDDataFilter dataFilter = new DBDDataFilter();
|
||||
dataFilter.setWhere(where);
|
||||
dataFilter.setAnyConstraint(anyConstraint);
|
||||
if (CommonUtils.isEmpty(constraints)) {
|
||||
return dataFilter;
|
||||
}
|
||||
dataFilter.addConstraints(mapWebConstrainsToDbdConstrains(resultInfo));
|
||||
dataFilter.addConstraints(mapWebConstrainsToDbdConstrains(monitor, resultInfo));
|
||||
return dataFilter;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private List<DBDAttributeConstraint> mapWebConstrainsToDbdConstrains(@Nullable WebSQLResultsInfo resultInfo) throws DBException {
|
||||
private List<DBDAttributeConstraint> mapWebConstrainsToDbdConstrains(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@Nullable WebSQLResultsInfo resultInfo
|
||||
) throws DBException {
|
||||
if (resultInfo == null) {
|
||||
return getDbdConstraints();
|
||||
}
|
||||
List<DBDAttributeConstraint> constraints = generateEmptyConstrains(resultInfo);
|
||||
fillEmptyConstrains(constraints);
|
||||
WebSQLUtils.convertConstraintValues(monitor, resultInfo, constraints);
|
||||
return constraints;
|
||||
}
|
||||
|
||||
@@ -139,14 +158,42 @@ public class WebSQLDataFilter {
|
||||
|
||||
private void fillEmptyConstrains(@NotNull List<DBDAttributeConstraint> emptyConstraints) throws DBException {
|
||||
for (WebSQLDataFilterConstraint webConstr : constraints) {
|
||||
if (webConstr.getAttributePosition() >= emptyConstraints.size()) {
|
||||
throw new DBException(MessageFormat.format("Incorrect column position ''{0}'' in order clause", webConstr.getAttributePosition()));
|
||||
DBDAttributeConstraint dbConstr = findConstraintToFill(emptyConstraints, webConstr);
|
||||
if (dbConstr.hasCondition()) {
|
||||
// The attribute already has a condition from a previous constraint
|
||||
dbConstr = new DBDAttributeConstraint(dbConstr);
|
||||
dbConstr.reset();
|
||||
emptyConstraints.add(dbConstr);
|
||||
}
|
||||
DBDAttributeConstraint dbConstr = emptyConstraints.get(webConstr.getAttributePosition());
|
||||
fillEmptyConstraint(dbConstr, webConstr);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private DBDAttributeConstraint findConstraintToFill(
|
||||
@NotNull List<DBDAttributeConstraint> emptyConstraints,
|
||||
@NotNull WebSQLDataFilterConstraint webConstr
|
||||
) throws DBException {
|
||||
Integer attributePosition = webConstr.getAttributePosition();
|
||||
if (attributePosition != null) {
|
||||
if (attributePosition < 0 || attributePosition >= emptyConstraints.size()) {
|
||||
throw new DBException(MessageFormat.format("Incorrect column position ''{0}'' in order clause", attributePosition));
|
||||
}
|
||||
return emptyConstraints.get(attributePosition);
|
||||
}
|
||||
// try by attribute name
|
||||
String attributeName = webConstr.getAttributeName();
|
||||
if (!CommonUtils.isEmpty(attributeName)) {
|
||||
for (DBDAttributeConstraint dbConstr : emptyConstraints) {
|
||||
if (CommonUtils.equalObjects(dbConstr.getAttributeName(), attributeName)) {
|
||||
return dbConstr;
|
||||
}
|
||||
}
|
||||
throw new DBException(MessageFormat.format("Constraint attribute ''{0}'' not found in result set", attributeName));
|
||||
}
|
||||
throw new DBException("Constraint must specify either attributePosition or attributeName");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private DBDAttributeConstraint fillEmptyConstraint(@NotNull DBDAttributeConstraint dbConstr,
|
||||
@NotNull WebSQLDataFilterConstraint webConstr) {
|
||||
@@ -168,6 +215,7 @@ public class WebSQLDataFilter {
|
||||
return dbConstr;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<DBDAttributeConstraint> getDbdConstraints() {
|
||||
List<DBDAttributeConstraint> dbConstraints = new ArrayList<>();
|
||||
for (WebSQLDataFilterConstraint constraint : constraints) {
|
||||
@@ -182,7 +230,10 @@ public class WebSQLDataFilter {
|
||||
return dbConstraints;
|
||||
}
|
||||
|
||||
private static int getOriginalVisualPosition(WebSQLDataFilterConstraint constraint, String attributeName) {
|
||||
private static int getOriginalVisualPosition(
|
||||
@NotNull WebSQLDataFilterConstraint constraint,
|
||||
@NotNull String attributeName
|
||||
) {
|
||||
Integer attributePosition = constraint.getAttributePosition();
|
||||
|
||||
if (CommonUtils.isEmpty(attributeName)) {
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ package io.cloudbeaver.service.sql;
|
||||
|
||||
public record WebSQLGeneratorOptions(
|
||||
boolean useFullyQualifiedNames,
|
||||
boolean compactSql
|
||||
boolean compactSql,
|
||||
boolean showFullDdl
|
||||
) {
|
||||
}
|
||||
|
||||
+6
-4
@@ -195,7 +195,8 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
DBCExecutionContext context = getExecutionContext(dataContainer);
|
||||
|
||||
try {
|
||||
final DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId)));
|
||||
final DBDDataFilter dataFilter = filter.makeDataFilter(
|
||||
monitor, (resultId == null ? null : contextInfo.getResults(resultId)));
|
||||
if (dataFilter.hasFilters()) {
|
||||
sql = context.getDataSource().getSQLDialect().addFiltersToQuery(
|
||||
monitor,
|
||||
@@ -346,7 +347,8 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
WebSQLExecuteInfo executeInfo = new WebSQLExecuteInfo();
|
||||
|
||||
DBCExecutionContext executionContext = DBUtils.getOrOpenDefaultContext(dataContainer, false);
|
||||
DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId)));
|
||||
DBDDataFilter dataFilter = filter.makeDataFilter(
|
||||
monitor, (resultId == null ? null : contextInfo.getResults(resultId)));
|
||||
DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> {
|
||||
try (DBCSession session = executionContext.openSession(monitor, resolveQueryPurpose(dataFilter), "Read data from container")) {
|
||||
try (
|
||||
@@ -479,7 +481,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
sendTransactionalEvent(contextInfo);
|
||||
}
|
||||
|
||||
WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet(webSession);
|
||||
WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet();
|
||||
updatedResultSet.setResultsInfo(resultsInfo);
|
||||
updatedResultSet.setColumns(resultsInfo.getAttributes());
|
||||
|
||||
@@ -651,7 +653,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
DBDAttributeBinding[] allAttributes = resultsInfo.getAttributes();
|
||||
DBDAttributeBinding[] keyAttributes = rowIdentifier.getAttributes().toArray(new DBDAttributeBinding[0]);
|
||||
|
||||
WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet(webSession);
|
||||
WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet();
|
||||
updatedResultSet.setResultsInfo(resultsInfo);
|
||||
updatedResultSet.setColumns(resultsInfo.getAttributes());
|
||||
|
||||
|
||||
+1
-2
@@ -54,7 +54,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
|
||||
private final WebDataFormat dataFormat;
|
||||
@Nullable
|
||||
private final DBDDataFilter dataFilter;
|
||||
private final WebSQLQueryResultSet webResultSet;
|
||||
private final WebSQLQueryResultSet webResultSet = new WebSQLQueryResultSet();
|
||||
|
||||
private DBDAttributeBinding[] bindings;
|
||||
private DBCTrace trace;
|
||||
@@ -71,7 +71,6 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
|
||||
this.dataContainer = dataContainer;
|
||||
this.dataFormat = dataFormat;
|
||||
this.dataFilter = dataFilter;
|
||||
this.webResultSet = new WebSQLQueryResultSet(contextInfo.getProcessor().getWebSession());
|
||||
rowLimit = ServletAppUtils.getServletApplication()
|
||||
.getAppConfiguration()
|
||||
.getResourceQuota(WebSQLConstants.QUOTA_PROP_ROW_LIMIT);
|
||||
|
||||
+37
-13
@@ -21,19 +21,20 @@ 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.DBPEvaluationContext;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.struct.DBSEntity;
|
||||
import org.jkiss.dbeaver.model.struct.DBSEntityAssociation;
|
||||
import org.jkiss.dbeaver.model.struct.DBSEntityConstraint;
|
||||
import org.jkiss.dbeaver.model.struct.rdb.DBSCatalog;
|
||||
import org.jkiss.dbeaver.model.struct.rdb.DBSSchema;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WebSQLQueryResultReference {
|
||||
public class WebSQLQueryResultAssociation {
|
||||
|
||||
private static final Log log = Log.getLog(WebSQLQueryResultReference.class);
|
||||
private static final Log log = Log.getLog(WebSQLQueryResultAssociation.class);
|
||||
|
||||
@Nullable
|
||||
private final WebSession session;
|
||||
@@ -41,18 +42,23 @@ public class WebSQLQueryResultReference {
|
||||
private final DBSEntityAssociation association;
|
||||
private final boolean reverse;
|
||||
@NotNull
|
||||
private final List<Integer> columnIndexList;
|
||||
private final List<WebSQLReferenceColumnMapping> columnMapping;
|
||||
|
||||
public WebSQLQueryResultReference(
|
||||
public WebSQLQueryResultAssociation(
|
||||
@Nullable WebSession session,
|
||||
@NotNull DBSEntityAssociation association,
|
||||
boolean reverse,
|
||||
@NotNull List<Integer> columnIndexList
|
||||
@NotNull List<WebSQLReferenceColumnMapping> columnMapping
|
||||
) {
|
||||
this.session = session;
|
||||
this.association = association;
|
||||
this.reverse = reverse;
|
||||
this.columnIndexList = columnIndexList;
|
||||
this.columnMapping = columnMapping;
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isReference() {
|
||||
return reverse;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -63,18 +69,36 @@ public class WebSQLQueryResultReference {
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getTargetEntityName() {
|
||||
public String getTargetCatalogName() {
|
||||
DBSEntity targetEntity = getTargetEntity();
|
||||
if (targetEntity == null) {
|
||||
return null;
|
||||
}
|
||||
return DBUtils.getObjectFullName(targetEntity, DBPEvaluationContext.UI);
|
||||
DBSCatalog catalog = DBUtils.getParentOfType(DBSCatalog.class, targetEntity);
|
||||
return catalog == null ? null : catalog.getName();
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getNodePath() {
|
||||
public String getTargetSchemaName() {
|
||||
DBSEntity targetEntity = getTargetEntity();
|
||||
if (targetEntity == null) {
|
||||
return null;
|
||||
}
|
||||
DBSSchema schema = DBUtils.getParentOfType(DBSSchema.class, targetEntity);
|
||||
return schema == null ? null : schema.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getTargetEntityName() {
|
||||
DBSEntity targetEntity = getTargetEntity();
|
||||
return targetEntity == null ? null : targetEntity.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Property
|
||||
public String getTargetNodePath() {
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -94,8 +118,8 @@ public class WebSQLQueryResultReference {
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public List<Integer> getColumnIndexList() {
|
||||
return columnIndexList;
|
||||
public List<WebSQLReferenceColumnMapping> getColumnMapping() {
|
||||
return columnMapping;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
+1
-24
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.sql;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.sql.WebSQLResultSetRowIdentifier.WebSQLResultSetRowIdentifierState;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -36,10 +35,6 @@ public class WebSQLQueryResultSet {
|
||||
|
||||
private static final Log log = Log.getLog(WebSQLQueryResultSet.class);
|
||||
|
||||
@Nullable
|
||||
private final WebSession session;
|
||||
@Nullable
|
||||
private DBDAttributeBinding[] referencesBindings;
|
||||
private WebSQLQueryResultColumn[] columns;
|
||||
private List<WebSQLQueryResultSetRow> rows = Collections.emptyList();
|
||||
private boolean hasMoreData;
|
||||
@@ -55,8 +50,7 @@ public class WebSQLQueryResultSet {
|
||||
private boolean readOnly;
|
||||
private String readOnlyStatus;
|
||||
|
||||
public WebSQLQueryResultSet(@Nullable WebSession session) {
|
||||
this.session = session;
|
||||
public WebSQLQueryResultSet() {
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -79,23 +73,6 @@ public class WebSQLQueryResultSet {
|
||||
columns[i] = new WebSQLQueryResultColumn(bindings[i]);
|
||||
}
|
||||
this.columns = columns;
|
||||
this.referencesBindings = bindings;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public List<WebSQLQueryResultReference> getAssociations() {
|
||||
return session != null && referencesBindings != null
|
||||
? WebSQLUtils.collectAssociations(session, referencesBindings)
|
||||
: Collections.emptyList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public List<WebSQLQueryResultReference> getReferences() {
|
||||
return session != null && referencesBindings != null
|
||||
? WebSQLUtils.collectReferences(session, referencesBindings)
|
||||
: Collections.emptyList();
|
||||
}
|
||||
|
||||
@Property
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
public class WebSQLReferenceColumnMapping {
|
||||
|
||||
private final int sourceColumnIndex;
|
||||
@NotNull
|
||||
private final String sourceColumnName;
|
||||
private final int targetColumnIndex;
|
||||
@NotNull
|
||||
private final String targetColumnName;
|
||||
|
||||
public WebSQLReferenceColumnMapping(
|
||||
int sourceColumnIndex,
|
||||
@NotNull String sourceColumnName,
|
||||
int targetColumnIndex,
|
||||
@NotNull String targetColumnName
|
||||
) {
|
||||
this.sourceColumnIndex = sourceColumnIndex;
|
||||
this.sourceColumnName = sourceColumnName;
|
||||
this.targetColumnIndex = targetColumnIndex;
|
||||
this.targetColumnName = targetColumnName;
|
||||
}
|
||||
|
||||
@Property
|
||||
public int getSourceColumnIndex() {
|
||||
return sourceColumnIndex;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getSourceColumnName() {
|
||||
return sourceColumnName;
|
||||
}
|
||||
|
||||
@Property
|
||||
public int getTargetColumnIndex() {
|
||||
return targetColumnIndex;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Property
|
||||
public String getTargetColumnName() {
|
||||
return targetColumnName;
|
||||
}
|
||||
}
|
||||
+88
-31
@@ -29,8 +29,11 @@ import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBPEvaluationContext;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.data.*;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
|
||||
import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;
|
||||
import org.jkiss.dbeaver.model.exec.DBCSession;
|
||||
import org.jkiss.dbeaver.model.gis.DBGeometry;
|
||||
import org.jkiss.dbeaver.model.gis.GisConstants;
|
||||
@@ -266,6 +269,49 @@ public class WebSQLUtils {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Need to convert string values in constraints to real values using value handlers
|
||||
*/
|
||||
public static void convertConstraintValues(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull WebSQLResultsInfo resultInfo,
|
||||
@NotNull List<DBDAttributeConstraint> constraints
|
||||
) {
|
||||
DBCExecutionContext executionContext = DBUtils.getDefaultContext(resultInfo.getDataContainer(), false);
|
||||
if (executionContext == null || !executionContext.isConnected()) {
|
||||
return;
|
||||
}
|
||||
DBCSession session = null;
|
||||
try {
|
||||
for (DBDAttributeConstraint constraint : constraints) {
|
||||
if (!(constraint.getValue() instanceof String strValue) ||
|
||||
!(constraint.getAttribute() instanceof DBDAttributeBinding binding)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (session == null) {
|
||||
session = executionContext.openSession(monitor, DBCExecutionPurpose.UTIL, "Convert filter values");
|
||||
}
|
||||
try {
|
||||
Object value = binding.getValueHandler().getValueFromObject(session, binding, strValue, false, false);
|
||||
if (value != null) {
|
||||
constraint.setValue(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug(
|
||||
"Can't convert filter value '" + strValue + "' for attribute '"
|
||||
+ binding.getName() + "'", e
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fully qualified name for a column.
|
||||
*/
|
||||
@@ -331,23 +377,23 @@ public class WebSQLUtils {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<WebSQLQueryResultReference> collectAssociations(
|
||||
public static List<WebSQLQueryResultAssociation> collectAssociations(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBDAttributeBinding[] bindings
|
||||
) {
|
||||
return collectWebSQLQueryResultReference(session, bindings, false);
|
||||
return collectWebSQLQueryResultAssociation(session, bindings, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<WebSQLQueryResultReference> collectReferences(
|
||||
public static List<WebSQLQueryResultAssociation> collectReferences(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBDAttributeBinding[] bindings
|
||||
) {
|
||||
return collectWebSQLQueryResultReference(session, bindings, true);
|
||||
return collectWebSQLQueryResultAssociation(session, bindings, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<WebSQLQueryResultReference> collectWebSQLQueryResultReference(
|
||||
private static List<WebSQLQueryResultAssociation> collectWebSQLQueryResultAssociation(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBDAttributeBinding[] bindings,
|
||||
boolean reverse
|
||||
@@ -369,7 +415,7 @@ public class WebSQLUtils {
|
||||
return idx == null ? null : bindings[idx];
|
||||
};
|
||||
|
||||
List<WebSQLQueryResultReference> result = new ArrayList<>();
|
||||
List<WebSQLQueryResultAssociation> result = new ArrayList<>();
|
||||
DBRProgressMonitor monitor = session.getProgressMonitor();
|
||||
for (DBSEntity entity : entities) {
|
||||
try {
|
||||
@@ -377,9 +423,9 @@ public class WebSQLUtils {
|
||||
? DBStructUtils.readReferences(monitor, entity, attrToBinding)
|
||||
: DBStructUtils.readAssociations(monitor, entity, attrToBinding);
|
||||
for (DBSEntityAssociation association : source) {
|
||||
List<Integer> columnIndex = collectOwnColumnIndex(monitor, association, reverse, attrToIndex);
|
||||
if (columnIndex != null) {
|
||||
result.add(new WebSQLQueryResultReference(session, association, reverse, columnIndex));
|
||||
List<WebSQLReferenceColumnMapping> mapping = collectColumnMapping(monitor, association, reverse, attrToIndex);
|
||||
if (mapping != null) {
|
||||
result.add(new WebSQLQueryResultAssociation(session, association, reverse, mapping));
|
||||
}
|
||||
}
|
||||
} catch (DBException e) {
|
||||
@@ -389,38 +435,49 @@ public class WebSQLUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pairs source-side attributes (in our result set) with target-side attributes
|
||||
*/
|
||||
@Nullable
|
||||
private static List<Integer> collectOwnColumnIndex(
|
||||
private static List<WebSQLReferenceColumnMapping> collectColumnMapping(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull DBSEntityAssociation association,
|
||||
boolean reverse,
|
||||
@NotNull Map<DBSEntityAttribute, Integer> attrToIndex
|
||||
) throws DBException {
|
||||
DBSEntityReferrer ownSide;
|
||||
if (reverse) {
|
||||
DBSEntityConstraint refConstraint = association.getReferencedConstraint();
|
||||
if (!(refConstraint instanceof DBSEntityReferrer referrer)) {
|
||||
return null;
|
||||
}
|
||||
ownSide = referrer;
|
||||
} else {
|
||||
if (!(association instanceof DBSEntityReferrer associationRef)) {
|
||||
return null;
|
||||
}
|
||||
ownSide = associationRef;
|
||||
}
|
||||
List<? extends DBSEntityAttributeRef> attrs = ownSide.getAttributeReferences(monitor);
|
||||
if (attrs == null || attrs.isEmpty()) {
|
||||
if (!(association instanceof DBSEntityReferrer associationReferrer)) {
|
||||
return null;
|
||||
}
|
||||
List<Integer> indexList = new ArrayList<>(attrs.size());
|
||||
for (DBSEntityAttributeRef attrRef : attrs) {
|
||||
Integer idx = attrToIndex.get(attrRef.getAttribute());
|
||||
if (idx == null) {
|
||||
DBSEntityConstraint refConstraint = association.getReferencedConstraint();
|
||||
if (!(refConstraint instanceof DBSEntityReferrer constraintReferrer)) {
|
||||
return null;
|
||||
}
|
||||
DBSEntityReferrer sourceSide = reverse ? constraintReferrer : associationReferrer;
|
||||
DBSEntityReferrer targetSide = reverse ? associationReferrer : constraintReferrer;
|
||||
|
||||
List<? extends DBSEntityAttributeRef> sourceAttrs = sourceSide.getAttributeReferences(monitor);
|
||||
List<? extends DBSEntityAttributeRef> targetAttrs = targetSide.getAttributeReferences(monitor);
|
||||
if (sourceAttrs == null || targetAttrs == null
|
||||
|| sourceAttrs.isEmpty() || sourceAttrs.size() != targetAttrs.size()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
List<WebSQLReferenceColumnMapping> mapping = new ArrayList<>(sourceAttrs.size());
|
||||
for (int i = 0; i < sourceAttrs.size(); i++) {
|
||||
DBSEntityAttribute sourceAttr = sourceAttrs.get(i).getAttribute();
|
||||
Integer sourceIdx = sourceAttr == null ? null : attrToIndex.get(sourceAttr);
|
||||
if (sourceIdx == null) {
|
||||
return null;
|
||||
}
|
||||
indexList.add(idx);
|
||||
DBSEntityAttribute targetAttr = targetAttrs.get(i).getAttribute();
|
||||
if (targetAttr == null) {
|
||||
return null;
|
||||
}
|
||||
// getOrdinalPosition() is 1-based. Normalize to 0-based to match sourceColumnIndex
|
||||
int targetIdx = targetAttr.getOrdinalPosition() - 1;
|
||||
mapping.add(new WebSQLReferenceColumnMapping(
|
||||
sourceIdx, sourceAttr.getName(), targetIdx, targetAttr.getName()));
|
||||
}
|
||||
return indexList;
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-14
@@ -54,6 +54,14 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
|
||||
.dataFetcher("sqlDialectInfo", env ->
|
||||
getService(env).getDialectInfo(getWebConnection(env))
|
||||
)
|
||||
.dataFetcher("sqlResultAssociations", env ->
|
||||
getService(env).getSqlResultAssociations(
|
||||
getWebSession(env),
|
||||
getSQLContext(env),
|
||||
getArgumentVal(env, "resultsId"),
|
||||
getArgument(env, "isReference")
|
||||
)
|
||||
)
|
||||
.dataFetcher("sqlListContexts", env ->
|
||||
getService(env).listContexts(getWebSession(env),
|
||||
getProjectReference(env),
|
||||
@@ -232,21 +240,17 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
|
||||
getDataFilter(env),
|
||||
getDataFormat(env)
|
||||
))
|
||||
.dataFetcher("asyncSqlNavigateForeignKey", env ->
|
||||
getService(env).asyncNavigateForeignKey(
|
||||
getWebSession(env),
|
||||
getSQLContext(env),
|
||||
getArgumentVal(env, "resultsId"),
|
||||
new WebSQLResultsRow(getArgument(env, "row")),
|
||||
getArgumentVal(env, "columnIndex"),
|
||||
getArgumentVal(env, "associationName"),
|
||||
CommonUtils.toBoolean(env.getArgument("isReference")),
|
||||
getDataFormat(env)
|
||||
))
|
||||
.dataFetcher("asyncSqlExecuteResults", env ->
|
||||
getService(env).asyncGetQueryResults(
|
||||
getWebSession(env), getArgumentVal(env, "taskId")
|
||||
))
|
||||
.dataFetcher("asyncSqlGenerateEntityQuery", env ->
|
||||
getService(env).asyncGenerateEntityQuery(
|
||||
getWebSession(env),
|
||||
getArgumentVal(env, "generatorId"),
|
||||
getArgumentVal(env, "nodePathList"),
|
||||
getGeneratorOptions(env)
|
||||
))
|
||||
.dataFetcher("asyncSqlExplainExecutionPlan", env ->
|
||||
getService(env).asyncSqlExplainExecutionPlan(
|
||||
getSQLContext(env),
|
||||
@@ -353,7 +357,7 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addServlets(ServletApplication application, DBWServletContext servletContext) throws DBException {
|
||||
public void addServlets(@NotNull ServletApplication application, @NotNull DBWServletContext servletContext) throws DBException {
|
||||
servletContext.addServlet(
|
||||
"sqlResultValueViewer",
|
||||
new WebSQLResultServlet(application, getServiceImpl()),
|
||||
@@ -432,11 +436,12 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
|
||||
private static WebSQLGeneratorOptions getGeneratorOptions(@NotNull DataFetchingEnvironment env) {
|
||||
Map<String, Object> optionsMap = getArgument(env, "generatorOptions");
|
||||
if (optionsMap == null) {
|
||||
return new WebSQLGeneratorOptions(true, false);
|
||||
return new WebSQLGeneratorOptions(true, false, false);
|
||||
}
|
||||
return new WebSQLGeneratorOptions(
|
||||
CommonUtils.toBoolean(optionsMap.get("useFullyQualifiedNames")),
|
||||
CommonUtils.toBoolean(optionsMap.get("compactSql"))
|
||||
CommonUtils.toBoolean(optionsMap.get("compactSql")),
|
||||
CommonUtils.toBoolean(optionsMap.get("showFullDdl"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+91
-82
@@ -31,13 +31,8 @@ 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.DBPDataKind;
|
||||
import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.*;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
|
||||
import org.jkiss.dbeaver.model.data.DBDReferenceNavigation;
|
||||
import org.jkiss.dbeaver.model.data.DBDReferenceUtils;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
|
||||
import org.jkiss.dbeaver.model.exec.DBExecUtils;
|
||||
@@ -61,7 +56,9 @@ import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorDescriptor;
|
||||
import org.jkiss.dbeaver.model.sql.semantics.completion.SQLCompletionProposalComparator;
|
||||
import org.jkiss.dbeaver.model.sql.semantics.completion.SQLQueryCompletionAnalyzer;
|
||||
import org.jkiss.dbeaver.model.sql.semantics.completion.SQLQueryCompletionContext;
|
||||
import org.jkiss.dbeaver.model.struct.*;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
|
||||
import org.jkiss.dbeaver.model.struct.DBSObject;
|
||||
import org.jkiss.dbeaver.model.struct.DBSWrapper;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -252,10 +249,33 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException {
|
||||
List<DBSObject> objectList = getObjectListFromNodeIds(session, nodePathList);
|
||||
return createAndRunGenerator(
|
||||
session, generatorId, objectList,
|
||||
options.useFullyQualifiedNames(), options.compactSql()
|
||||
);
|
||||
return createAndRunGenerator(session, generatorId, objectList, options);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAsyncTaskInfo asyncGenerateEntityQuery(
|
||||
@NotNull WebSession session,
|
||||
@NotNull String generatorId,
|
||||
@NotNull List<String> nodePathList,
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
@Override
|
||||
public void run(@NotNull DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
try {
|
||||
monitor.beginTask("Generate SQL", 1);
|
||||
monitor.subTask("Generate '" + generatorId + "' SQL");
|
||||
List<DBSObject> objectList = getObjectListFromNodeIds(session, monitor, nodePathList);
|
||||
this.result = createAndRunGenerator(monitor, generatorId, objectList, options);
|
||||
} catch (Throwable e) {
|
||||
throw new InvocationTargetException(e);
|
||||
} finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
};
|
||||
return session.createAndRunAsyncTask("Generate SQL", runnable);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -270,10 +290,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
) throws DBWebException {
|
||||
checkAndFillTruncatedData(sqlContext, resultsId, selectedRows);
|
||||
WebDBDResultSetDataProvider dataProvider = new WebDBDResultSetDataProvider(resultsId, sqlContext, selectedRows);
|
||||
return createAndRunGenerator(
|
||||
webSession, generatorId, Collections.singletonList(dataProvider),
|
||||
options.useFullyQualifiedNames(), options.compactSql()
|
||||
);
|
||||
return createAndRunGenerator(webSession, generatorId, Collections.singletonList(dataProvider), options);
|
||||
}
|
||||
|
||||
private void checkAndFillTruncatedData(
|
||||
@@ -308,8 +325,17 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@NotNull WebSession session,
|
||||
@NotNull String generatorId,
|
||||
@NotNull List<DBSObject> objectList,
|
||||
boolean useFullyQualifiedNames,
|
||||
boolean compactSql
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException {
|
||||
return createAndRunGenerator(session.getProgressMonitor(), generatorId, objectList, options);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String createAndRunGenerator(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull String generatorId,
|
||||
@NotNull List<DBSObject> objectList,
|
||||
@NotNull WebSQLGeneratorOptions options
|
||||
) throws DBWebException {
|
||||
SQLGeneratorDescriptor generator = SQLGeneratorConfigurationRegistry.getInstance().getGenerator(generatorId);
|
||||
if (generator == null) {
|
||||
@@ -317,9 +343,14 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
try {
|
||||
SQLGenerator<DBSObject> generatorInstance = generator.createGenerator(objectList);
|
||||
generatorInstance.setFullyQualifiedNames(useFullyQualifiedNames);
|
||||
generatorInstance.setCompactSQL(compactSql);
|
||||
generatorInstance.run(session.getProgressMonitor());
|
||||
generatorInstance.setFullyQualifiedNames(options.useFullyQualifiedNames());
|
||||
generatorInstance.setCompactSQL(options.compactSql());
|
||||
if (options.showFullDdl()) {
|
||||
generatorInstance.setShowFullDdl(true);
|
||||
generatorInstance.setShowComments(true);
|
||||
generatorInstance.setShowPermissions(true);
|
||||
}
|
||||
generatorInstance.run(monitor);
|
||||
return generatorInstance.getResult();
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error creating SQL generator", e);
|
||||
@@ -331,12 +362,24 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<DBSObject> getObjectListFromNodeIds(@NotNull WebSession session, @NotNull List<String> nodePathList) throws DBWebException {
|
||||
private List<DBSObject> getObjectListFromNodeIds(
|
||||
@NotNull WebSession session,
|
||||
@NotNull List<String> nodePathList
|
||||
) throws DBWebException {
|
||||
return getObjectListFromNodeIds(session, session.getProgressMonitor(), nodePathList);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<DBSObject> getObjectListFromNodeIds(
|
||||
@NotNull WebSession session,
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull List<String> nodePathList
|
||||
) throws DBWebException {
|
||||
try {
|
||||
List<DBSObject> objectList = new ArrayList<>(nodePathList.size());
|
||||
DBNModel navigatorModel = session.getNavigatorModelOrThrow();
|
||||
for (String nodePath : nodePathList) {
|
||||
DBNNode node = navigatorModel.getNodeByPath(session.getProgressMonitor(), nodePath);
|
||||
DBNNode node = navigatorModel.getNodeByPath(monitor, nodePath);
|
||||
if (node == null) {
|
||||
throw new DBException("Node '" + nodePath + "' not found");
|
||||
}
|
||||
@@ -463,6 +506,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@Nullable List<WebSQLResultsRow> addedRows,
|
||||
@Nullable WebDataFormat dataFormat
|
||||
) throws DBException {
|
||||
checkDataEditPermission(contextInfo);
|
||||
WebSQLExecuteInfo[] result = new WebSQLExecuteInfo[1];
|
||||
|
||||
DBExecUtils.tryExecuteRecover(
|
||||
@@ -475,6 +519,13 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
private void checkDataEditPermission(@NotNull WebSQLContextInfo contextInfo) throws DBWebException {
|
||||
if (!contextInfo.getProcessor().getConnection().getDataSourceContainer()
|
||||
.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EDIT_DATA)) {
|
||||
throw new DBWebException("Data edit is restricted for this connection");
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowableFunction<T, R> {
|
||||
R apply(T obj) throws DBException;
|
||||
@@ -527,6 +578,7 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
|
||||
@Override
|
||||
public String updateResultsDataBatchScript(@NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, @Nullable List<WebSQLResultsRow> updatedRows, @Nullable List<WebSQLResultsRow> deletedRows, @Nullable List<WebSQLResultsRow> addedRows, WebDataFormat dataFormat) throws DBWebException {
|
||||
checkDataEditPermission(contextInfo);
|
||||
try {
|
||||
return contextInfo.getProcessor().generateResultsDataUpdateScript(
|
||||
contextInfo.getProcessor().getWebSession().getProgressMonitor(),
|
||||
@@ -553,6 +605,10 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
if (DBWorkbench.isDistributed() && !webSession.hasPermission(DBWConstants.PERMISSION_SQL_EXECUTE_QUERY)) {
|
||||
throw new DBWebException("Permission denied");
|
||||
}
|
||||
if (!contextInfo.getProcessor().getConnection().getDataSourceContainer()
|
||||
.hasModifyPermission(DBPDataSourcePermission.PERMISSION_EXECUTE_SCRIPTS)) {
|
||||
throw new DBWebException("Script execution is restricted for this connection");
|
||||
}
|
||||
return WebSQLUtils.createAsyncTaskExecuteSqlQuery(
|
||||
webSession,
|
||||
contextInfo,
|
||||
@@ -603,72 +659,25 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
return contextInfo.getProcessor().getWebSession().createAndRunAsyncTask("Read data from container " + nodePath, runnable);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WebAsyncTaskInfo asyncNavigateForeignKey(
|
||||
public List<WebSQLQueryResultAssociation> getSqlResultAssociations(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull WebSQLResultsRow row,
|
||||
int columnIndex,
|
||||
@NotNull String associationName,
|
||||
boolean isReference,
|
||||
@Nullable WebDataFormat dataFormat
|
||||
) {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
@Override
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
try {
|
||||
monitor.beginTask("Navigate foreign key", 1);
|
||||
WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId);
|
||||
DBSEntity sourceEntity = resolveSourceEntity(resultsInfo.getAttributes(), columnIndex);
|
||||
DBSEntityAssociation association = isReference
|
||||
? DBStructUtils.findReverseAssociationByName(monitor, sourceEntity, associationName)
|
||||
: DBStructUtils.findForwardAssociationByName(monitor, sourceEntity, associationName);
|
||||
|
||||
WebDBDResultSetDataProvider dataProvider = new WebDBDResultSetDataProvider(
|
||||
resultsId,
|
||||
contextInfo,
|
||||
List.of(row)
|
||||
);
|
||||
DBDReferenceNavigation navigation = isReference
|
||||
? DBDReferenceUtils.resolveReferenceNavigation(monitor, dataProvider, association, dataProvider.getSelectedRows())
|
||||
: DBDReferenceUtils.resolveAssociationNavigation(monitor, dataProvider, association, dataProvider.getSelectedRows());
|
||||
if (!(navigation.getTargetEntity() instanceof DBSDataContainer targetDataContainer)) {
|
||||
throw new DBWebException("Referenced entity '" + navigation.getTargetEntity().getName() + "' is not a data container");
|
||||
}
|
||||
WebSQLExecuteInfo executeResults = contextInfo.getProcessor().readDataFromContainer(
|
||||
contextInfo,
|
||||
monitor,
|
||||
targetDataContainer,
|
||||
null,
|
||||
WebSQLDataFilter.from(navigation.getTargetFilter()),
|
||||
dataFormat
|
||||
);
|
||||
this.result = executeResults.getStatusMessage();
|
||||
this.extendedResults = executeResults;
|
||||
} catch (Throwable e) {
|
||||
throw new InvocationTargetException(e);
|
||||
} finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
};
|
||||
return webSession.createAndRunAsyncTask("Navigate foreign key from results " + resultsId, runnable);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private DBSEntity resolveSourceEntity(
|
||||
@NotNull DBDAttributeBinding[] attributes,
|
||||
int columnIndex
|
||||
@Nullable Boolean isReference
|
||||
) throws DBException {
|
||||
if (columnIndex < 0 || columnIndex >= attributes.length) {
|
||||
throw new DBWebException("Column index '" + columnIndex + "' is out of range");
|
||||
DBDAttributeBinding[] attributes = contextInfo.getResults(resultsId).getAttributes();
|
||||
if (isReference == null) {
|
||||
// Both forward associations and reverse references
|
||||
List<WebSQLQueryResultAssociation> associations =
|
||||
new ArrayList<>(WebSQLUtils.collectAssociations(webSession, attributes));
|
||||
associations.addAll(WebSQLUtils.collectReferences(webSession, attributes));
|
||||
return associations;
|
||||
}
|
||||
DBSEntityAttribute entityAttribute = attributes[columnIndex].getEntityAttribute();
|
||||
if (entityAttribute == null) {
|
||||
throw new DBException("Column [" + attributes[columnIndex].getName() + "] is not bound to any entity");
|
||||
}
|
||||
return entityAttribute.getParentObject();
|
||||
return isReference
|
||||
? WebSQLUtils.collectReferences(webSession, attributes)
|
||||
: WebSQLUtils.collectAssociations(webSession, attributes);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -3,10 +3,9 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-Name: Cloudbeaver Web Service - Administration
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
|
||||
Bundle-Version: 1.0.148.qualifier
|
||||
Bundle-Release-Date: 20260706
|
||||
Bundle-Version: 1.0.153.qualifier
|
||||
Bundle-Release-Date: 20260921
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-21
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: io.cloudbeaver.server.ce,
|
||||
org.jkiss.dbeaver.registry
|
||||
Require-Bundle: io.cloudbeaver.server.ce
|
||||
Automatic-Module-Name: io.cloudbeaver.service.admin
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.admin</artifactId>
|
||||
<version>1.0.148-SNAPSHOT</version>
|
||||
<version>1.0.153-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
####################################################
|
||||
# Administration API
|
||||
####################################################
|
||||
|
||||
"Describes the type of the administrative subject (user or team)"
|
||||
enum AdminSubjectType {
|
||||
"User"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user