Compare commits

...
Author SHA1 Message Date
Ruslan 499cda86b3 dbeaver/pro#9724 fix graphql schema for doc 2026-07-31 12:47:47 +02:00
Ruslan ca03a8eff3 dbeaver/pro#9724 fix graphql schema for doc 2026-07-31 11:24:19 +02:00
Serge Rider 3a60e04a84 dbeaver/pro#9893 Project navigator model is required 2026-07-30 20:12:48 +03:00
dbeaver-devops 8b33e3a08f dbeaver/pro#10040 26.1.5 version bump 2026-07-28 19:27:19 +02:00
b021ceec4b dbeaver/pro#9665 adds global network profiles (#4465)
* dbeaver/pro#9665 adds global network profiles

* dbeaver/pro#9665 marks global profiles in selector

* dbeaver/pro#9665 cleanup

* dbeaver/pro#9665 fixes document-global G color so it adapts to the theme

* dbeaver/pro#9665 splits logic to tab service & bootstrap

* dbeaver/pro#9665 hides connections tab if no sub-tabs was registered

* Merge branch 'devel' into 9665-cb-global-network-profiles-in-cloudbeaver

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-07-28 19:52:23 +03:00
Ruslan Musaevandsergeyteleshev d318579745 dbeaver/pro#9755 navigator settings sync (#4491)
* dbeaver/pro#9755 navigator settings sync

* dbeaver/pro#9755 navigator settings sync

* dbeaver/pro#9755 navigator settings sync

* dbeaver/pro#9755 adds synchronization for changing connection settings

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-07-28 19:25:16 +03:00
Ainur 8d17ed0ecd dbeaver/pro#9964 fix generating urls for saml with custom root uri (#4486) 2026-07-28 19:02:00 +03:00
DaniilandDaria Marutkina 3199a99b29 dbeaver/pro#9822 API for full DDL for PostgreSQL (#4477)
* dbeaver/pro#9822 API for full DDL for PostgreSQL

* dbeaver/pro#9822 linter fix

* dbeaver/pro#9822 linter fix

* dbeaver/pro#9822 add cancellable async tasks for DDL generation

* dbeaver/pro#9822 linter fix

* dbeaver/pro#9822 linter fix

* dbeaver/pro#9822 comment fix

* dbeaver/pro#9822 comment fix

* dbeaver/pro#9822 remove PostgreSQL-only restriction for full DDL feature

* dbeaver/pro#9822 review fix: add deprecated annotations to ghl schemas

* dbeaver/pro#9822 review fix: revert changes for metadataGetNodeDDL

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-28 16:42:41 +02:00
Sychev AndreyandDaria Marutkina 39008f7170 dbeaver/pro#9865 feat: add title prop to SelectField component (#4501)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-28 16:42:29 +02:00
8bf33d539f dbeaver/pro#9905 adds fallback to get connection from nodeId in case nodePath is not set (#4499)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-07-28 16:37:56 +02:00
Elizabeth 42f6720acd dbeaver/pro#10033 Fix file connections creation (#4503) 2026-07-28 16:08:27 +02:00
sergeyteleshevandEvgenia 0a3cb12c0b dbeaver/pro#9798 adds sorting for columns in user tables (#4484)
* dbeaver/pro#9798 adds sorting for columns in user tables

* dbeaver/pro#9798 adds comparator helpers

* dbeaver/pro#9798 moves user helpers to compareUser file

* dbeaver/pro#9798 fixes naming and any

* dbeaver/pro# adds grantedUsersIdsMap to make less computations

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-07-28 13:22:43 +02:00
202823e731 dbeaver/pro#9719 move ai to community (#4471)
* dbeaver/pro#9719 move ai to community

* dbeaver/pro#9719 return prompt generator id input and fix system message type

* dbeaver/pro#9719 return prompt generator id for conversation

* dbeaver/pro#9719 remove rag

* dbeaver/pro#9719 move ai to ce

* dbeaver/pro#9719 fix description for ai chat

* dbeaver/pro#9719 clear feature

* dbeaver/pro#9719 fix conflicts

* dbeaver/pro#9719 remove unused import

---------

Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-07-28 13:15:33 +02:00
Ruslan MusaevandAinur ed088fe923 dbeaver/pro#9934 api for user list downloaded (#4498)
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2026-07-28 12:01:49 +02:00
df21c3396a dbeaver/pro#9617 api for data-import settings (#4423)
* dbeaver/pro#9617 api for data import settings

* dbeaver/pro#9617 api for data import settings

* dbeaver/pro#9617 api for data import settings

* dbeaver/pro#9617 api for data import settings

* dbeaver/pro#9617 api for data import settings

* dbeaver/pro#9617 api for data import settings

* dbeaver/pro#9617 feat: support new import API on FE

* dbeaver/pro#9617 api for data import settings

---------

Co-authored-by: Sychev Andrey <nukemoore@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com>
2026-07-28 12:01:24 +02:00
90ccdc6432 dbeaver/pro#9621 feat: add state to diagram tab (#4495)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
2026-07-28 11:39:37 +02:00
sergeyteleshevandmr-anton-t dba371a320 dbeaver/pro#10011 fixes js-yaml, brace-expansion, postcss CVEs (#4500)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-07-28 11:19:12 +02:00
d96cd8f0d2 dbeaver/pro#9912 add ability to save null for default auto commit mode (#4481)
* dbeaver/pro#9912 add ability to save null for default auto commit mode

* dbeaver/pro#9912 return null value by default

* dbeaver/pro#9912 return valid values

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-07-28 11:09:37 +02:00
ElizabethandDiana bd6e7d48f3 dbeaver/pro#9491 Handle exception from setUrl (#4479)
Co-authored-by: Diana <31996417+uslss@users.noreply.github.com>
2026-07-28 07:51:45 +02:00
8b41e4530f dbeaver/pro#4490 bump postgres version (#4493)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-27 18:51:50 +02:00
alexandDaria Marutkina a555f1e759 dbeaver/pro#4425 add controlled mode (#4468)
* dbeaver/pro#4425 add controlled mode

* dbeaver/pro#4425 update yarn lock

* dbeaver/pro#4425 use controlled state for users

* dbeaver/pro#4425 update copyright

* dbeaver/pro#4425 update notice

* dbeaver/pro#4425 update tokens

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-27 17:09:40 +02:00
alexandDaria Marutkina fdaa40e431 dbeaver/pro#8663 add auto resize textarea (#4497)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-27 17:02:18 +02:00
sergeyteleshevandDaria Marutkina 4c2f9d92e5 dbeaver/pro#9170 adds download button for the script (#4474)
* dbeaver/pro#9170 adds download button for the script

* dbeaver/pro#9170 gets sql filename from node or model

* dbeaver/pro#9170 adds action buttons

* dbeaver/pro#9170 adds borders to the buttons

* dbeaver/pro#9170 fixes build

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-27 14:35:17 +02:00
sergeyteleshevandmr-anton-t a92011eee6 dbeaver/pro#9967 fixes immutable & fast-uri CVEs (#4492)
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-07-27 11:52:01 +02:00
ElizabethandDiana 61a7757a0e dbeaver/pro#9445 Update Clickhouse driver version (#4480)
Co-authored-by: Diana <31996417+uslss@users.noreply.github.com>
2026-07-24 13:11:02 +02:00
Nikita Akilov a53ca3592f dbeaver/dbeaver-devops#2751 stop including curl with cloud products (#4494) 2026-07-24 11:22:42 +02:00
Sychev AndreyandEvgenia 9e682617e6 dbeaver/pro#9638 [CB] refresh is needed to see replaced connectionfolder in another instance (#4464)
* dbeaver/pro#9638 fix: update project filtering logic to use project IDs for active projects

Changed all four spots from reference equality to id comparison, matching the rest of the codebase, see https://github.com/dbeaver/cloudbeaver/issues/4430

* dbeaver/pro#9638 refactor(connectionUpdateHandler): move node to a new parent on change

* dbeaver/pro#9638 revert commit eb2ba7871e

* dbeaver/pro#9638 fix: update node parents on move in other session

Server events don't carry the actual data, so besides outdating (which lets the handlers invalidate the previous state, e.g. the old location in the navigation tree) we explicitly load the connection to guarantee that the handlers receive the actual state

* dbeaver/pro#9638 fix: sync connection folder move from another session

* dbeaver/pro#9638 fix: revert back connectionUpdateHandler

* dbeaver/pro#9638 fix: remove try catch

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-07-23 17:33:26 +02:00
sergeyteleshevandDaria Marutkina 16dda810d7 dbeaver/pro#9171 adds ability to open sql editor with context from Generate SQL dialog (#4488)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-07-23 16:30:47 +02:00
Sychev AndreyandEvgenia 6042f16342 dbeaver/pro#9426 feat: add loading state to save button (#4487)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-07-23 16:30:31 +02:00
Viktor Kirst 0afdd06c80 dbeaver/pro#9768 Fix long-polling session cookie after session ID rotation (#4467) 2026-07-23 16:04:31 +03:00
Sychev Andreyandmr-anton-t 3158aebca4 dbeaver/pro#9877 UI fixes (#4473)
* dbeaver/pro#9877 fix: select render field popover

* dbeaver/pro#9877 fix: revert back ripple hover effect

* dbeaver/pro#9877 fix: make all outlines 1px by default

* dbeaver/pro#9877 fix: switch outline

* dbeaver/pro#9877 fix: topbar buttons outline

* dbeaver/pro#9877 fix: tablist styles

* dbeaver/pro#9877 fix: rule priorities

* dbeaver/pro#9877 fix: eslint

* dbeaver/pro#9877 fix: eslint

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-07-23 13:07:10 +02:00
mayerro 99866fdc12 Revert "dbeaver/dbeaver-devops#2751 remove curl from base java to fix cve (#4483)"
This reverts commit f54f377344.
2026-07-23 11:17:11 +02:00
sergeyteleshev a58eb59a32 dbeaver/pro#9938 fixes 8 CVEs (#4478) 2026-07-23 11:08:30 +02:00
DaniilandEvgenia eda1f8720f dbeaver/pro#9721 [CB] Add a flag into reverse proxy allowing new user… (#4460)
* dbeaver/pro#9721 [CB] Add a flag into reverse proxy allowing new users creation

* dbeaver/pro#9721 [CB] code style fix

* dbeaver/pro#9721 [CB] code style fix

* dbeaver/pro#9721 move the method isAutoUserProvisioningEnabled to SMAuthProviderExternal

* dbeaver/pro#9721 linter fix

* dbeaver/pro#9721 code review fix

* dbeaver/pro#9721 code review fix

* dbeaver/pro#9721 code review fix

* dbeaver/pro#9721 Fix login for existing users without reverse proxy credentials when auto user provisioning is disabled

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-07-23 10:09:30 +02:00
Nikolay f54f377344 dbeaver/dbeaver-devops#2751 remove curl from base java to fix cve (#4483)
* dbeaver/dbeaver-devops#2751 removed curl from base java to fix cve

* dbeaver/dbeaver-devops#2751 removed an extra comment
2026-07-22 16:31:49 +03:00
alex 81a9be17b8 dbeaver/pro#9659 add dnd scroll container (#4476) 2026-07-22 14:24:53 +02:00
432 changed files with 13746 additions and 613 deletions
-2
View File
@@ -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
@@ -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.15.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 1.0.16.qualifier
Bundle-Release-Date: 20260817
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.15-SNAPSHOT</version>
<version>1.0.16-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.106.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 1.0.107.qualifier
Bundle-Release-Date: 20260817
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
@@ -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 = 无自动断开连接
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.106-SNAPSHOT</version>
<version>1.0.107-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -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(),
@@ -103,7 +103,7 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
return projectSettings;
}
@Nullable
@NotNull
@Override
public DBNModel getNavigatorModel() {
return webSession.getNavigatorModel();
@@ -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();
}
}
@@ -103,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(
@@ -467,7 +467,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 +541,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
@@ -598,4 +595,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);
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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();
}
}
}
@@ -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);
}
@@ -297,6 +300,11 @@ 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);
@@ -57,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)));
@@ -307,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
@@ -330,7 +331,11 @@ public class WebDataSourceUtils {
}
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.4.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 26.1.5.qualifier
Bundle-Release-Date: 20260817
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."/>
</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.4-SNAPSHOT</version>
<version>26.1.5-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.151.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 1.0.152.qualifier
Bundle-Release-Date: 20260817
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.151-SNAPSHOT</version>
<version>1.0.152-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.4.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 26.1.5.qualifier
Bundle-Release-Date: 20260817
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.4-SNAPSHOT</version>
<version>26.1.5-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,3 +1,7 @@
####################################################
# Core API
####################################################
extend type ServerConfig {
serverURL: String! @deprecated
rootURI: String!
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 26.1.4.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 26.1.5.qualifier
Bundle-Release-Date: 20260817
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.sql;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>26.1.4-SNAPSHOT</version>
<version>26.1.5-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
@@ -556,7 +556,7 @@ type ConnectionInfo {
supportedDataFormats: [ ResultDataFormat! ]!
configurationType: DriverConfigurationType
#Access properties
"Access properties"
canViewSettings: Boolean!
canEdit: Boolean!
canDelete: Boolean!
@@ -572,6 +572,29 @@ type ConnectionInfo {
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 {
@@ -714,7 +737,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
@@ -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!
@@ -153,10 +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")
# 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)")
@@ -213,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
@@ -221,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! ]!
}
@@ -296,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
}
####################################################
@@ -397,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.
@@ -593,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,
@@ -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
@@ -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{" +
@@ -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,18 +163,7 @@ 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());
@@ -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,10 @@ 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);
log.debug("HTTP Long-Poll channel opened for session " + sid);
return ps;
@@ -253,7 +243,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 +254,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>
@@ -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 {
@@ -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(
@@ -18,6 +18,7 @@ package io.cloudbeaver.service.sql;
public record WebSQLGeneratorOptions(
boolean useFullyQualifiedNames,
boolean compactSql
boolean compactSql,
boolean showFullDdl
) {
}
@@ -244,6 +244,13 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL>
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),
@@ -429,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"))
);
}
@@ -252,10 +252,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 +293,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 +328,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 +346,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 +365,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");
}
@@ -3,8 +3,8 @@ 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.150.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260817
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.150-SNAPSHOT</version>
<version>1.0.151-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"
@@ -0,0 +1,15 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver AI Integration
Bundle-SymbolicName: io.cloudbeaver.service.ai;singleton:=true
Bundle-Version: 1.0.1.qualifier
Bundle-Release-Date: 20260817
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.service.ai
Require-Bundle: org.jkiss.dbeaver.model.ai,
io.cloudbeaver.model,
io.cloudbeaver.server.ce
Export-Package: io.cloudbeaver.service.ai,
io.cloudbeaver.service.ai.model,
io.cloudbeaver.service.ai.model.inputs
@@ -0,0 +1,6 @@
source..=src/
output..=target/classes/
bin.includes=.,\
META-INF/,\
schema/,\
plugin.xml
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<extension point="io.cloudbeaver.service">
<service id="ai" label="AI Service" description="AI service"
class="io.cloudbeaver.service.ai.gql.WebServiceBindingAI"/>
<service id="ai.configurator" label="AI Configurator" description="AI configurator"
class="io.cloudbeaver.service.ai.gql.WebServiceAIConfigurator">
</service>
</extension>
<extension point="io.cloudbeaver.object.feature.provider">
<featureProvider id="AIFeatureProvider" class="io.cloudbeaver.service.ai.WebAIFeatureProvider"/>
</extension>
<extension point="org.jkiss.dbeaver.ws.event">
<clientEvent id="cb_client_ai_function_call_confirmation"
class="io.cloudbeaver.service.ai.model.events.WSAiFunctionCallConfirmationClientEvent"
handler="io.cloudbeaver.service.ai.model.events.WSAiFunctionCallConfirmationHandler"/>
</extension>
</plugin>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.cloudbeaver</groupId>
<artifactId>bundles</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.ai</artifactId>
<version>1.0.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -0,0 +1,62 @@
####################################################
# AI events
####################################################
extend enum CBServerEventId {
cb_ai_chat_message_chunk @since(version: "25.1.1")
cb_ai_chat_message_error @since(version: "25.1.1")
cb_ai_chat_message @since(version: "25.3.5")
}
extend enum CBClientEventId {
cb_client_ai_function_call_confirmation @since(version: "26.0.3")
}
extend enum CBEventTopic {
cb_ai @since(version: "25.1.1")
}
type WSAIChatMessageChunkEvent implements CBServerEvent @since(version: "25.1.1") {
id: CBServerEventId!
topicId: CBEventTopic
conversationId: ID!
messageId: ID!
chunk: String
completed: Boolean!
}
type WSAIChatMessageErrorEvent implements CBServerEvent @since(version: "25.1.1") {
id: CBServerEventId!
topicId: CBEventTopic
conversationId: ID!
messageId: ID!
errorMessage: String
}
type WSAIChatMessageEvent implements CBServerEvent @since(version: "25.3.5") {
id: CBServerEventId!
topicId: CBEventTopic
conversationId: ID!
messageId: ID!
role: String!
content: String!
displayMessage: String!
time: String!
"Present only with CONFIRMATION role"
functionConfirmation: AIFunctionConfirmation
"Present only with FUNCTION role"
functionCall: AIFunctionCall
"Present only with FUNCTION role"
functionResult: AIFunctionResult
}
type WSAIFunctionCallConfirmationEvent implements CBClientEvent @since(version: "26.0.3") {
id: CBClientEventId!
topicId: CBEventTopic
conversationId: ID!
messageId: ID!
"Returns a list of confirmed functions"
confirmedFunctionCalls: [ID!]
"Returns a list of declined functions"
declinedFunctionCalls: [ID!]
}
@@ -0,0 +1,277 @@
####################################################
# AI admin queries
####################################################
enum AIMessageType {
SYSTEM,
USER,
ASSISTANT,
ERROR,
WARNING,
CONFIRMATION,
ATTACHMENT,
FUNCTION
}
"""
Scope of the AI chat conversation.
Can be a database connection, a current database, a current schema or a custom scope.
"""
enum AIDatabaseScope @since(version: "25.1.1") {
CURRENT_SCHEMA
CURRENT_DATABASE
CURRENT_DATASOURCE
CUSTOM
}
type AISettingsInfo @since(version: "23.2.2") {
activeEngine: ID!
defaultConfiguration: ID
language: String
}
type AIEngineInfo @since(version: "23.2.2") {
"Unique identifier of the AI engine."
id: ID!
"Name of the AI engine."
name: String!
}
type AIChatConversationInfo @since(version: "25.1.1") {
dataSourceId: DataSourceId
id: ID!
promptGeneratorId: ID
caption: String!
"Last message creation time."
time: DateTime!
messages: [AIMessage!]!
settings: AIChatConversationSettings
waitingForResponse: Boolean! @since(version: "25.1.5")
metrics: AIChatConversationMetrics @since(version: "25.3.5")
"ID of the AI engine configuration profile used for this conversation."
profile: ID @since(version: "26.1.3")
}
type AIChatConversationMetrics @since(version: "25.3.5") {
totalInputTokens: Int!
totalOutputTokens: Int!
}
type AIChatConversationSettings {
metaTransferConfirmed: Boolean!
scope: AIDatabaseScope
"If the scope is set to CUSTOM, this field is required."
customObjectIds: [ID!]
}
type AIMessage {
conversationId: ID!
id: ID!
role: AIMessageType!
content: String!
displayMessage: String!
time: DateTime!
"Present only with CONFIRMATION role"
functionConfirmation: AIFunctionConfirmation
"Present only with FUNCTION role"
functionCall: AIFunctionCall
"Present only with FUNCTION role"
functionResult: AIFunctionResult
}
type AISendChatMessageInfo @since(version: "25.1.1") {
conversation: AIChatConversationInfo!
userMessage: AIMessage!
"Message that would be received via websockets."
assistantMessage: AIMessage!
}
type AIDataSourceSettingsInfo @since(version: "25.3.3") {
"Indicates whether user confirmed meta transfer to AI for the current data source."
metaTransferConfirmed: Boolean!
"Default scope for AI operations in this data source."
scope: AIDatabaseScope
"If the scope is set to CUSTOM, this field is required."
customObjectIds: [ID!]
}
type DataSourceId @since(version: "25.1.1") {
projectId: ID!
connectionId: ID!
}
type AIConfigurationProfileInfo @since(version: "26.1.3") {
"Unique identifier of the AI engine profile."
id: ID!
"Name of the AI engine profile."
name: String!
"ID of the AI engine that this profile belongs to."
engineId: ID!
}
type AIAdminConfigurationProfileInfo @since(version: "26.1.3") {
"Unique identifier of the AI engine profile."
id: ID!
"Name of the AI engine profile."
name: String!
"ID of the AI engine that this profile belongs to."
engineId: ID!
"Configuration of the AI engine profile."
configuration: [ObjectPropertyInfo!]!
}
type AIFunction @since(version: "26.0.4") {
"Unique identifier of the function (combines toolbox ID and function ID)"
id: ID!
"Human-readable name of the function"
name: String!
"Detailed description of what the function does"
description: String
"URL or path to the function icon"
icon: String
"Indicates whether the function is a system function (can be auto-confirmed when required)"
system: Boolean!
}
type AIFunctionCall @since(version: "26.0.3") {
id: ID!
functionName: String!
arguments: Object
hint: String
}
type AIFunctionConfirmation @since(version: "26.0.3") {
functionCalls: [AIFunctionCall!]!
}
enum AIFunctionType {
ACTION
INFORMATION
}
type AIFunctionResult @since(version: "26.0.3") {
type: AIFunctionType!
value: Object!
}
input AISettingsConfig @since(version: "23.2.2") {
"Default configuration profile ID. May be null if AI wasn't configured"
defaultConfiguration: ID
"Language to be used as a default for AI responses"
language: String
}
input AIEngineConfig @since(version: "23.2.2") {
properties: Object
}
input AIDatabaseScopeInput {
id: AIDatabaseScope!
"If custom scope is selected, this field is required."
customScopes: [ID!]
}
input DataSourceIdInput @since(version: "25.1.1") {
projectId: ID!
connectionId: ID!
}
input AIChatConversationInput {
dataSourceId: DataSourceIdInput
caption: String
settings: AIChatConversationSettingsInput
"ID of the prompt generator to use for this conversation (sql - by default)."
promptGeneratorId: ID
}
input AIChatConversationSettingsInput {
metaTransferConfirmed: Boolean
scope: AIDatabaseScope
profile: ID
customObjectIds: [ID!]
}
input AIDataSourceSettingsInput @since(version: "25.3.3") {
"Flag indicating whether MCP integration is enabled for the current data source."
mcpEnabled: Boolean
"Flag indicating whether user confirmed meta transfer to AI."
metaTransferConfirmed: Boolean
"Default scope for AI operations in this data source."
scope: AIDatabaseScope
"If custom scope is selected, this field is required."
customObjectIds: [ID!]
}
input AIConfigurationProfileInput @since(version: "26.1.3") {
"Unique identifier of the AI engine profile."
profileId: ID!
"Name of the AI engine profile."
profileName: String
"ID of the AI engine that this profile belongs to."
engineId: ID
"Configuration of the AI engine profile."
configuration: AIEngineConfig
}
extend type Query @since(version: "23.2.2") {
"Returns the list of available AI engines."
aiListEngines: [AIEngineInfo!]
"Returns the global AI settings."
aiSettings: AISettingsInfo!
"Returns the properties of the specified AI engine for displaying it in the UI. In case settings are passed, fills it with defaults and returns back."
aiListEngineProperties(engineId: ID!, profileId: ID, settings : AIEngineConfig): [ObjectPropertyInfo!]!
"Return the list of available AI functions."
aiListFunctions: [AIFunction!] @since(version: "26.0.4")
"Returns the list of available AI engine configuration profiles."
aiListProfiles: [AIConfigurationProfileInfo!]! @since(version: "26.1.3")
"Returns AI chat conversation info for the specified conversation ID."
aiChatConversationInfo(conversationId: ID!, loadMetrics: Boolean): AIChatConversationInfo! @since(version: "25.1.1")
"Returns list of AI chat conversations for the specified connection."
aiListChatConversations(dataSourceId: DataSourceIdInput): [AIChatConversationInfo!]! @since(version: "25.1.1")
"Returns AI settings for the specified connection."
aiDataSourceSettings(dataSourceId: DataSourceIdInput!): AIDataSourceSettingsInfo! @since(version: "25.3.3")
}
extend type Mutation @since(version: "23.2.2") {
"Saves the global AI settings (active AI engine)."
aiSaveSettings(settings: AISettingsConfig!): AISettingsInfo!
"Saves the configuration of the specified AI engine."
aiSaveEngineConfiguration(profileId: ID!, settings : AIEngineConfig!): Boolean! @deprecated(reason: "use profiles api")
"Creates an anync task to perform AI query completion."
asyncAiPerformQueryCompletion(projectId: ID, connectionId: ID!, contextId: ID!, request: String!): AsyncTaskInfo!
"Retrieves the result of an async AI query completion task."
asyncAiPerformQueryCompletionResult(taskId: ID!): String
"Creates a new AI engine configuration profile."
aiCreateProfile(config: AIConfigurationProfileInput!): AIAdminConfigurationProfileInfo! @since(version: "26.1.3")
"Updates the configuration of the specified AI engine configuration profile."
aiUpdateProfile(config: AIConfigurationProfileInput!): AIAdminConfigurationProfileInfo! @since(version: "26.1.3")
"Deletes the specified AI engine configuration profile."
aiDeleteProfile(profileId: ID!): Boolean! @since(version: "26.1.3")
"""
Creates a new AI chat conversation.
Conversation is created in the context of a project and connection.
If the connection and project are not specified, the conversation will be created without a context.
"""
aiCreateChatConversation(config: AIChatConversationInput!): AIChatConversationInfo! @since(version: "25.1.1")
"Updates the caption of the AI chat conversation"
aiUpdateChatConversation(conversationId: ID!, config: AIChatConversationInput!): AIChatConversationInfo! @since(version: "25.1.1")
"Deletes the specified AI chat conversation"
aiDeleteChatConversation(conversationId: ID!): Boolean! @since(version: "25.1.1")
"Sends a chat message to the AI chat conversation and returns a message ID from response. The response will be received partially via websockets."
aiSendChatMessage(conversationId: ID!, prompt: String!): AISendChatMessageInfo! @since(version: "25.1.1")
"Clears the chat messages in the AI chat conversation. The messages will be removed from the conversation."
aiClearLastChatMessages(conversationId: ID!, messageId: ID!): Boolean! @since(version: "25.1.1")
"Saves AI settings (e.g. supporting confirming metadata transfer) for the specified connection."
aiSaveDataSourceSettings(dataSourceId: DataSourceIdInput!, settings: AIDataSourceSettingsInput!): AIDataSourceSettingsInfo! @since(version: "25.3.3")
}
@@ -0,0 +1,46 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.navigator.DBWFeatureProvider;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.ai.utils.AIUtils;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import java.util.ArrayList;
import java.util.List;
public class WebAIFeatureProvider implements DBWFeatureProvider {
public static final String AI_FEATURE_ID = "ai";
private static final String FEATURE_CAN_DESCRIBE_OBJECT = "canDescribeObject";
@NotNull
@Override
public List<String> getNodeFeatures(@NotNull WebSession webSession, @NotNull DBNNode node) {
List<String> features = new ArrayList<>();
if (!ServletAppUtils.getServletApplication().getAppConfiguration().isFeatureEnabled(AI_FEATURE_ID)) {
return features;
}
if (node instanceof DBNDatabaseNode element && AIUtils.isEligible(element.getObject())) {
features.add(FEATURE_CAN_DESCRIBE_OBJECT);
}
return features;
}
}
@@ -0,0 +1,299 @@
/*
* 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.ai;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.ai.model.WebAIChatConversation;
import io.cloudbeaver.service.ai.model.WebAIMessage;
import io.cloudbeaver.service.ai.model.WebAISendChatMessageInfo;
import io.cloudbeaver.service.ai.model.WebAiChatResponseConsumer;
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
import io.cloudbeaver.utils.ServletAppUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.ai.*;
import org.jkiss.dbeaver.model.ai.qm.AIChatStorage;
import org.jkiss.dbeaver.model.ai.quota.UserTokenQuotaService;
import org.jkiss.dbeaver.model.ai.registry.AIAssistantRegistry;
import org.jkiss.dbeaver.model.ai.utils.AIUtils;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.navigator.DBNUtils;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import java.time.Clock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class WebAIUtils {
private static final Log log = Log.getLog(WebAIUtils.class);
private static final String AI_WAITING_ATTR = "ai.waiting.";
public static final String AI_CHAT_ATTR = "ai_chat";
public static final String AI_CHAT_USER_ATTR = "ai_chat_user";
@NotNull
public static List<String> convertObjectIdsToNodePaths(
@NotNull DBRProgressMonitor monitor,
@NotNull DBPProject project,
@NotNull String[] objectIds
) {
List<String> customObjectIdList = new ArrayList<>();
for (String id : objectIds) {
if (id != null && !id.isEmpty()) {
try {
DBSObject dbpObject = DBUtils.findObjectById(monitor, project, id);
if (dbpObject != null) {
DBNDatabaseNode dbNode = DBNUtils.getNodeByObject(monitor, dbpObject, false);
if (dbNode != null) {
customObjectIdList.add(dbNode.getNodeUri());
}
}
} catch (DBException e) {
log.error("Error finding object by ID: " + id, e);
}
}
}
return customObjectIdList;
}
@NotNull
public static String[] convertNodePathsToObjectIds(
@NotNull WebSession session,
@NotNull DBPProject project,
@NotNull String[] nodePaths
) {
List<DBNNode> nodes = new ArrayList<>();
for (String nodePath : nodePaths) {
if (nodePath != null && !nodePath.isEmpty()) {
try {
DBNNode node = session.getNavigatorModelOrThrow().getNodeByPath(session.getProgressMonitor(), project, nodePath);
if (node != null) {
nodes.add(node);
}
} catch (DBException e) {
log.error("Error finding object by ID: " + nodePath, e);
}
}
}
return nodes.stream()
.map(DBNDatabaseNode.class::cast)
.map(DBNDatabaseNode::getValueObject)
.map(DBSObject.class::cast)
.map(DBUtils::getObjectFullId)
.toArray(String[]::new);
}
@Nullable
public static AIChatSession findAiChatSession(@NotNull WebSession webSession) {
return webSession.getAttribute(AI_CHAT_ATTR);
}
@Nullable
public static AIContextSettingsChatConversation getConversationSettings(
@NotNull AIChatSession chatSession,
@NotNull AIChatConversation conversation
) {
DBPDataSourceContainer container = conversation.getDataSource();
if (container == null) {
return null;
}
AIContextSettingsChatConversation chatContextSettings = conversation.getCustomSettings();
if (chatContextSettings == null) {
chatContextSettings = new AIContextSettingsChatConversation(chatSession, conversation);
conversation.setCustomSettings(chatContextSettings);
}
chatContextSettings.loadDataSourceDefaults();
chatSession.updateScopeSettingsIfNeeded(chatContextSettings, container);
return chatContextSettings;
}
@NotNull
public static CompletableFuture<AIChatConversation> scheduleConversationSubmission(
@NotNull WebSession webSession,
@NotNull AIChatSession aiChatSession,
@NotNull AIChatConversation conversation,
@Nullable AIConfirmation confirmation,
@NotNull String jobName
) {
webSession.setAttribute(getWaitingAttr(conversation), true);
CompletableFuture<AIChatConversation> result = new CompletableFuture<>();
RuntimeUtils.scheduleJob(
jobName, monitor -> {
try {
AIChatResponseConsumer subscriber = new WebAiChatResponseConsumer(conversation, webSession, aiChatSession);
aiChatSession.processAICompletion(
monitor,
conversation,
subscriber,
WebAIUtils.getConversationSettings(aiChatSession, conversation),
confirmation
).whenComplete((submittedConversation, error) -> {
if (error != null) {
result.completeExceptionally(error);
} else {
result.complete(submittedConversation);
}
});
} catch (DBException e) {
log.error("Error processing AI completion", e);
var errorMessage = conversation.addMessage(AIMessage.errorMessage(e));
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
aiChatSession.notifyMessageAdd(conversation, errorMessage);
} finally {
webSession.removeAttribute(getWaitingAttr(conversation));
}
}
);
return result;
}
@NotNull
public static String getWaitingAttr(@NotNull AIChatConversation conversation) {
return AI_WAITING_ATTR + conversation.getId();
}
@NotNull
public static AIChatSession getAiChatSession(@NotNull WebSession webSession) throws DBWebException {
validateAiPluginEnabled();
String aiChatSessionUser = webSession.getAttribute(AI_CHAT_USER_ATTR);
boolean sameUser = webSession.isAuthorizedInSecurityManager() &&
CommonUtils.equalObjects(webSession.getUserId(), aiChatSessionUser);
if (sameUser || webSession.getUserId() == null) {
AIChatSession aiChatSession = webSession.getAttribute(AI_CHAT_ATTR);
if (aiChatSession != null) {
return aiChatSession;
}
}
return createChatSession(webSession);
}
@NotNull
public static AIChatSession createChatSession(@NotNull WebSession webSession) {
AIAssistant assistant = AIAssistantRegistry.getInstance().getAssistant(webSession.getWorkspace());
AIChatStorage qmChatStorage = assistant.createChatStorage();
AIChatSession aiChatSession = new AIChatSession(
webSession.getWorkspace(),
dataSourceContainer -> DBUtils.getDefaultContext(dataSourceContainer, false),
qmChatStorage,
assistant.getChatSessionProvider(),
new UserTokenQuotaService(Clock.systemUTC(), qmChatStorage)
);
aiChatSession.addListener(new AIChatListener() {
@Override
public void messageAdded(@NotNull AIChatConversation conversation, @NotNull AIChatMessage message) {
if (message.message().getConfirmation() instanceof AIFunctionCallConfirmation fcc) {
for (AIFunctionCall fc : fcc.getFunctionCalls()) {
// functions are not resolved at the current step, we need to resolve them first
// otherwise no information about a function will be sent
AIFunctionDescriptor function = fc.getOrResolveFunction(aiChatSession.getAssistant().getToolboxManager());
if (function == null) {
log.warn("Function is not found for function call " + fc.getFunctionName());
}
}
WebAIMessage confirmationMessage = new WebAIMessage(message, conversation);
WSAiChatMessageEvent event = new WSAiChatMessageEvent(confirmationMessage);
webSession.addSessionEvent(event);
}
}
});
webSession.setAttribute(AI_CHAT_ATTR, aiChatSession);
if (webSession.getUser() != null) {
String userId = webSession.getUserId();
webSession.setAttribute(AI_CHAT_USER_ATTR, userId);
}
return aiChatSession;
}
@NotNull
public static AIChatConversation getAiChatConversation(
@NotNull WebSession webSession,
@NotNull String conversationId
) throws DBWebException {
try {
AIChatSession aiChatSession = getAiChatSession(webSession);
AIChatConversation conversation = aiChatSession.getConversation(conversationId);
if (conversation == null) {
throw new DBWebException("Invalid chat conversation ID " + conversationId);
}
return conversation;
} catch (DBException e) {
throw new DBWebException(e.getMessage(), e);
}
}
@NotNull
public static WebAISendChatMessageInfo submitPrompt(
@NotNull WebSession webSession,
@NotNull AIChatSession aiChatSession,
@NotNull AIChatConversation conversation,
@NotNull AIMessage message
) throws DBException {
if (!isMetaTransferConfirmed(conversation)) {
throw new DBWebException("AI services restricted for '%s'. Please contact your administrator if you need it.".formatted(
conversation.getDataSource()));
}
String caption = conversation.getCaption();
AIChatMessage promptMessage = conversation.addMessage(message);
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(promptMessage, conversation)));
aiChatSession.notifyMessageAdd(conversation, promptMessage);
if (!CommonUtils.equalObjects(caption, conversation.getCaption())) {
aiChatSession.notifyConversationRenamed(conversation, conversation.getCaption());
}
if (!AIUtils.hasValidConfiguration()) {
throw new DBWebException("Invalid AI configuration");
}
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) != null) {
throw new DBWebException("Conversation is already waiting for response");
}
AIChatMessage result = new AIChatMessage(conversation.getNextMessageId(), AIMessage.assistantMessage("", null));
WebAIUtils.scheduleConversationSubmission(webSession, aiChatSession, conversation, null, "AI completion");
return new WebAISendChatMessageInfo(
new WebAIChatConversation(webSession, conversation),
new WebAIMessage(promptMessage, conversation),
new WebAIMessage(result, conversation)
);
}
private static boolean isMetaTransferConfirmed(@NotNull AIChatConversation conversation) {
DBPDataSourceContainer dataSourceContainer = conversation.getDataSource();
if (dataSourceContainer == null) {
return true;
}
AIContextSettingsDataSource dsSettings = new AIContextSettingsDataSource(dataSourceContainer);
return dsSettings.isMetaTransferConfirmed();
}
public static void validateAiPluginEnabled() throws DBWebException {
var isAiEnabled = ServletAppUtils.getServletApplication().getAppConfiguration()
.isFeatureEnabled(WebAIFeatureProvider.AI_FEATURE_ID);
if (!isAiEnabled) {
throw new DBWebException("AI feature is disabled");
}
}
}
@@ -0,0 +1,36 @@
/*
* 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.ai;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.service.ai.gql.WebAISettingsConfig;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.ai.AIConstants;
import org.jkiss.dbeaver.runtime.DBWorkbench;
public class WebServiceAIManager {
public WebAISettingsConfig getAiSettings() throws DBWebException {
return null;
}
@Nullable
private static String getAiLanguage() {
return DBWorkbench.getPlatform().getPreferenceStore()
.getString(AIConstants.AI_RESPONSE_LANGUAGE);
}
}
@@ -0,0 +1,176 @@
/*
* 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.ai.gql;
import io.cloudbeaver.*;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
import io.cloudbeaver.service.ai.model.*;
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
import io.cloudbeaver.service.ai.model.inputs.WebAIChatConversationInput;
import io.cloudbeaver.service.ai.model.inputs.WebAIConfigurationProfileInput;
import io.cloudbeaver.service.ai.model.inputs.WebAiChatCompletionSettingsInput;
import io.cloudbeaver.service.sql.WebSQLContextInfo;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.rm.RMConstants;
import java.util.List;
import java.util.Map;
/**
* Service for executing GraphQL functions for AI module.
*/
public interface DBWServiceAI extends DBWService {
@NotNull
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
WebAISettingsConfig getAiSettings() throws DBWebException;
@NotNull
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
List<WebAIEngine> getEngineConfigurations() throws DBWebException;
@NotNull
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
List<WebPropertyInfo> getEngineConfigurationParameters(
@NotNull WebSession webSession,
@NotNull String engineId,
@Nullable String profileId,
@Nullable Map<String, Object> engineSettings
) throws DBWebException;
@NotNull
@WebAction
List<WebAIFunctionInfo> getFunctions(@NotNull WebSession webSession) throws DBWebException;
@NotNull
@WebAction
List<WebAIConfigurationProfile> getProfiles(@NotNull WebSession webSession) throws DBWebException;
@NotNull
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
WebAISettingsConfig saveAiSettings(
@NotNull WebSession webSession,
@NotNull WebAISettingsConfig config
) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean saveEngineConfiguration(
@NotNull WebSession webSession,
@NotNull String profileId,
@WebParameterSecure @NotNull Map<String, Object> engineSettings
) throws DBWebException;
@NotNull
@WebAction
WebAsyncTaskInfo performQueryCompletion(
@NotNull WebSession webSession,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String request
) throws DBWebException;
@Nullable
@WebAction
String performQueryCompletionResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException;
@NotNull
@WebAction
WebAIChatConversation createChat(
@NotNull WebSession webSession,
@NotNull WebAIChatConversationInput conversationInput
) throws DBWebException;
@NotNull
@WebAction
WebAIChatConversation updateChatConversation(
@NotNull WebSession webSession,
@NotNull String conversationId,
@NotNull WebAIChatConversationInput conversationInput
) throws DBWebException;
@WebAction
boolean deleteChatConversation(@NotNull WebSession webSession, @NotNull String conversationId) throws DBWebException;
@NotNull
@WebAction
List<WebAIChatConversation> getChatConversations(
@NotNull WebSession webSession,
@Nullable DataSourceId dataSourceId
) throws DBWebException;
@NotNull
@WebAction
WebAIChatConversation getChatConversationInfo(
@NotNull WebSession webSession,
@NotNull String conversationId,
@Nullable Boolean loadMetrics
) throws DBWebException;
@NotNull
@WebAction
WebAISendChatMessageInfo asyncSendChatMessage(
@NotNull WebSession webSession,
@NotNull String conversationId,
@NotNull String prompt
) throws DBWebException;
@WebAction
boolean setLastChatMessage(
@NotNull WebSession webSession,
@NotNull String conversationId,
@NotNull String messageId
) throws DBWebException;
@NotNull
@WebAction
WebAIDataSourceSettings getDataSourceAiSettings(
@NotNull HttpServletRequest request,
@NotNull WebSession webSession,
@NotNull DataSourceId dataSourceId
) throws DBWebException;
@NotNull
@WebProjectAction(requireProjectPermissions = RMConstants.PERMISSION_PROJECT_DATASOURCES_EDIT)
WebAIDataSourceSettings saveDataSourceAiSettings(
@NotNull HttpServletRequest request,
@NotNull WebSession webSession,
@NotNull @WebObjectId String projectId,
@NotNull DataSourceId dataSourceId,
@NotNull WebAiChatCompletionSettingsInput settings
) throws DBWebException;
@NotNull
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
WebAIConfigurationProfile createProfile(
@NotNull WebSession webSession,
@WebParameterSecure @NotNull WebAIConfigurationProfileInput config
) throws DBWebException;
@NotNull
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
WebAIConfigurationProfile updateProfile(
@NotNull WebSession webSession,
@WebParameterSecure @NotNull WebAIConfigurationProfileInput config
) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteProfile(@NotNull WebSession webSession, @NotNull String profileId) throws DBWebException;
}
@@ -0,0 +1,44 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.gql;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.ai.registry.AIEngineDescriptor;
import org.jkiss.dbeaver.model.meta.Property;
public class WebAIEngine {
@NotNull
private final AIEngineDescriptor descriptor;
public WebAIEngine(@NotNull AIEngineDescriptor descriptor) {
this.descriptor = descriptor;
}
@NotNull
@Property
public String getId() {
return descriptor.getId();
}
@NotNull
@Property
public String getName() {
return descriptor.getLabel();
}
}
@@ -0,0 +1,51 @@
/*
* 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.ai.gql;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
public class WebAISettingsConfig {
private final String activeEngine;
private final String defaultConfiguration;
private final String language;
public WebAISettingsConfig(
@NotNull String engineId,
@Nullable String defaultConfiguration,
@Nullable String language
) {
this.defaultConfiguration = defaultConfiguration;
this.activeEngine = engineId;
this.language = language;
}
@NotNull
public String getActiveEngine() {
return activeEngine;
}
@Nullable
public String getDefaultConfiguration() {
return defaultConfiguration;
}
public String getLanguage() {
return language;
}
}
@@ -0,0 +1,679 @@
/*
* 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.ai.gql;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebAsyncTaskProcessor;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.service.ai.WebAIUtils;
import io.cloudbeaver.service.ai.model.*;
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
import io.cloudbeaver.service.ai.model.inputs.WebAIChatConversationInput;
import io.cloudbeaver.service.ai.model.inputs.WebAIConfigurationProfileInput;
import io.cloudbeaver.service.ai.model.inputs.WebAiChatCompletionSettingsInput;
import io.cloudbeaver.service.sql.WebSQLContextInfo;
import io.cloudbeaver.service.sql.WebSQLProcessor;
import io.cloudbeaver.utils.ServletAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import io.cloudbeaver.utils.WebEventUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.NotNull;
import org.jkiss.code.NotNullWhen;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.ai.*;
import org.jkiss.dbeaver.model.ai.engine.AIDatabaseContext;
import org.jkiss.dbeaver.model.ai.engine.AIEngine;
import org.jkiss.dbeaver.model.ai.engine.AIEngineProperties;
import org.jkiss.dbeaver.model.ai.engine.AIModel;
import org.jkiss.dbeaver.model.ai.prompt.AIPromptGenerateSql;
import org.jkiss.dbeaver.model.ai.registry.*;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.logical.DBSLogicalDataSource;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.websocket.event.WSWorkspaceConfigurationChangedEvent;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.properties.PropertySourceEditable;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;
public class WebServiceAI implements DBWServiceAI {
private static final Log log = Log.getLog(WebServiceAI.class);
public static final String AI_WAITING_ATTR = "ai.waiting.";
@NotNull
@Override
public WebAISettingsConfig getAiSettings() throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
AISettings settings = AISettingsManager.getInstance().getSettings();
try {
AIConfigurationProfile configuration = settings.getDefaultConfiguration();
return new WebAISettingsConfig(
configuration.getEngineId(),
configuration.getProfileId(),
getAiLanguage()
);
} catch (DBException e) {
throw new DBWebException(e);
}
}
@NotNull
@Override
public List<WebAIEngine> getEngineConfigurations() throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
List<WebAIEngine> result = new ArrayList<>();
AIEngineRegistry.getInstance().getCompletionEngines().forEach(
engineDescriptor -> result.add(new WebAIEngine(engineDescriptor))
);
return result;
}
@NotNull
@Override
public List<WebPropertyInfo> getEngineConfigurationParameters(
@NotNull WebSession webSession,
@NotNull String engineId,
@Nullable String profileId,
@Nullable Map<String, Object> settingsInput
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
try {
AIConfigurationProfile profile = getDefaultConfiguration(engineId, profileId);
AIEngineProperties engineConfiguration;
if (settingsInput != null) {
engineConfiguration = toEngineConfiguration(
webSession.getProgressMonitor(),
profile,
fillModelDefaults(webSession.getProgressMonitor(), profile, settingsInput)
);
} else {
engineConfiguration = profile.getConfiguration();
}
return List.of(
WebServiceUtils.getObjectFilteredProperties(webSession, engineConfiguration, null)
);
} catch (DBException e) {
throw new DBWebException("Error getting engine configuration parameters", e);
}
}
@NotNull
private AIConfigurationProfile getDefaultConfiguration(@NotNull String engineId, @Nullable String profileId) throws DBException {
AISettings aiSettings = AISettingsManager.getInstance().getSettings();
if (profileId != null) {
return aiSettings.getConfiguration(profileId);
}
AIConfigurationProfile profile = new AIConfigurationProfile();
profile.setEngineId(engineId);
profile.getEngineDescriptor(); // validate engine descriptor
return profile;
}
@NotNull
@Override
public List<WebAIFunctionInfo> getFunctions(@NotNull WebSession webSession) throws DBWebException {
AIAssistant assistant = AIAssistantRegistry.getInstance().getAssistant(webSession.getWorkspace());
return assistant.getToolboxManager().getAllFunctions(AIFunctionPurpose.TOOL).stream()
.map(WebAIFunctionInfo::new)
.toList();
}
@NotNull
@Override
public List<WebAIConfigurationProfile> getProfiles(@NotNull WebSession webSession) {
return Arrays.stream(AISettingsManager.getInstance().getSettings().getConfigurations())
.map(profile -> new WebAIConfigurationProfile(webSession, profile))
.toList();
}
@NotNull
@Override
public WebAISettingsConfig saveAiSettings(@NotNull WebSession webSession, @NotNull WebAISettingsConfig config) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
try {
setAiLanguage(webSession, config.getLanguage());
} catch (DBException e) {
throw new DBWebException("Error when saving AI language", e);
}
AISettings aiSettings = AISettingsManager.getInstance().getSettings();
AIConfigurationProfile profile = aiSettings.getConfigurationOrNull(config.getDefaultConfiguration());
aiSettings.setDefaultConfiguration(profile);
AISettingsManager.getInstance().saveSettings();
addAISettingsChangedEvent(webSession);
return new WebAISettingsConfig(
profile == null ? "" : profile.getEngineId(),
profile == null ? null : profile.getProfileId(),
getAiLanguage()
);
}
@Override
public boolean saveEngineConfiguration(
@NotNull WebSession webSession,
@NotNull String profileId,
@NotNull Map<String, Object> engineSettingsInput
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
try {
AISettings settings = AISettingsManager.getInstance().getSettings();
AIConfigurationProfile profile = settings.getConfiguration(profileId);
profile.setConfiguration(toEngineConfiguration(webSession.getProgressMonitor(), profile, engineSettingsInput));
AISettingsManager.getInstance().saveSettings();
addAISettingsChangedEvent(webSession);
return true;
} catch (DBException e) {
throw new DBWebException("Error saving AI configuration " + profileId);
}
}
@NotNull
@Override
public WebAsyncTaskInfo performQueryCompletion(
@NotNull WebSession webSession,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String request
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
monitor.beginTask("Perform query completion", 1);
monitor.subTask("Perform query completion for " + request);
try {
WebSQLProcessor processor = contextInfo.getProcessor();
DBSLogicalDataSource lDataSource = new DBSLogicalDataSource(
processor.getConnection().getDataSourceContainer(),
"AI logical wrapper",
null
);
AIDatabaseContext dbContext = new AIDatabaseContext.Builder(lDataSource)
.setExecutionContext(processor.getExecutionContext())
.build();
AIAssistant assistant = AIAssistantRegistry.getInstance().getAssistant(webSession.getWorkspace());
AIFunctionContext fc = new AIFunctionContext(monitor, dbContext, new AIPromptGenerateSql());
AIMessage userMessage = AIMessage.userMessage(request);
AIAssistantResponse result = assistant.generateText(
monitor,
AISettingsManager.getStaticSettings().getDefaultConfiguration(),
fc,
List.of(userMessage)
);
if (result.isText()) {
this.result = AITextUtils.extractGeneratedSqlQuery(
monitor, dbContext, userMessage, result.getText());
} else {
this.result = "";
}
} catch (DBException e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
};
return webSession.createAndRunAsyncTask("AI perform completion query", runnable);
}
@Nullable
@Override
public String performQueryCompletionResult(@NotNull WebSession webSession, @NotNull String taskId) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
WebAsyncTaskInfo taskStatus = webSession.asyncTaskStatus(taskId, false);
return CommonUtils.toString(taskStatus.getResult());
}
@NotNull
@Override
public WebAIChatConversation createChat(
@NotNull WebSession webSession,
@NotNull WebAIChatConversationInput conversationInput
) throws DBWebException {
AIChatSession aiChatSession = WebAIUtils.getAiChatSession(webSession);
DBPDataSourceContainer dataSource = getDataSource(webSession, conversationInput.dataSourceId());
try {
String promptGeneratorId = conversationInput.promptGeneratorId();
if (promptGeneratorId == null) {
// by default use SQL generator
promptGeneratorId = AIPromptGenerateSql.SQL_GENERATOR_ID;
}
AIPromptGeneratorDescriptor descriptor = AIPromptGeneratorRegistry.getInstance().getPromptGenerator(promptGeneratorId);
if (descriptor == null) {
throw new DBWebException("Invalid prompt generator ID " + promptGeneratorId);
}
AIChatConversation newConversation = new AIChatConversation(
conversationInput.caption() != null ? conversationInput.caption() : "New conversation",
descriptor.createGenerator(),
dataSource
);
aiChatSession.addConversation(newConversation);
saveCompletionSettings(webSession, aiChatSession, newConversation, conversationInput.settings());
return new WebAIChatConversation(webSession, newConversation);
} catch (DBException e) {
throw new DBWebException("Error creating conversation", e);
}
}
@NotNull
@Override
public WebAIChatConversation updateChatConversation(
@NotNull WebSession webSession,
@NotNull String conversationId,
@NotNull WebAIChatConversationInput input
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
AIChatSession aiChatSession = WebAIUtils.getAiChatSession(webSession);
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
String caption = input.caption();
if (caption != null) {
renameConversation(aiChatSession, conversation, caption);
}
saveCompletionSettings(webSession, aiChatSession, conversation, input.settings());
return new WebAIChatConversation(webSession, conversation);
}
@Override
public boolean deleteChatConversation(@NotNull WebSession webSession, @NotNull String conversationId) throws DBWebException {
try {
AIChatSession aiChatSession = WebAIUtils.getAiChatSession(webSession);
AIChatConversation conversation = aiChatSession.getConversation(conversationId);
if (conversation == null) {
throw new DBWebException("Invalid conversation ID " + conversationId);
}
aiChatSession.removeConversation(conversation);
} catch (DBException e) {
throw new DBWebException(e.getMessage(), e);
}
return true;
}
@NotNull
@Override
public List<WebAIChatConversation> getChatConversations(
@NotNull WebSession webSession,
@Nullable DataSourceId dataSourceId
) throws DBWebException {
AIChatSession session = WebAIUtils.getAiChatSession(webSession);
DBPDataSourceContainer connectionInfo = getDataSource(webSession, dataSourceId);
try {
return session.getAllConversations(connectionInfo).stream()
.map(c -> new WebAIChatConversation(webSession, c))
.collect(Collectors.toList());
} catch (DBException e) {
throw new DBWebException(e.getMessage(), e);
}
}
@NotNull
@Override
public WebAIChatConversation getChatConversationInfo(
@NotNull WebSession webSession,
@NotNull String conversationId,
@Nullable Boolean loadMetrics
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
WebAIChatConversationMetrics metrics = null;
if (loadMetrics != null && loadMetrics) {
AIExtendedUsage aiExtendedUsage = conversation.computeUsage();
metrics = WebAIChatConversationMetrics.from(aiExtendedUsage);
}
return new WebAIChatConversation(webSession, conversation, metrics);
}
@NotNull
@Override
public WebAISendChatMessageInfo asyncSendChatMessage(
@NotNull WebSession webSession,
@NotNull String conversationId,
@NotNull String prompt
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
try {
AIChatSession aiChatSession = WebAIUtils.getAiChatSession(webSession);
AIChatConversation conversation = aiChatSession.getConversation(conversationId);
if (conversation == null) {
throw new DBWebException("Invalid chat conversation ID " + conversationId);
}
if (prompt.trim().isEmpty()) {
throw new DBWebException("Invalid prompt");
}
AIMessage message = AIMessage.userMessage(prompt.trim());
return WebAIUtils.submitPrompt(webSession, aiChatSession, conversation, message);
} catch (DBException e) {
throw new DBWebException(e.getMessage(), e);
}
}
@Override
public boolean setLastChatMessage(
@NotNull WebSession webSession,
@NotNull String conversationId,
@NotNull String messageId
) throws DBWebException {
AIChatSession chatSession = WebAIUtils.getAiChatSession(webSession);
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
AIChatMessage message = conversation.getMessages().stream()
.filter(m -> messageId.equals(CommonUtils.toString(m.id())))
.findFirst()
.orElseThrow(() -> new DBWebException("Invalid message ID " + messageId));
chatSession.notifyMessagesRemove(conversation, message);
conversation.clearMessagesAfter(message);
return true;
}
@NotNull
@Override
public WebAIDataSourceSettings getDataSourceAiSettings(
@NotNull HttpServletRequest request,
@NotNull WebSession webSession,
@NotNull DataSourceId dataSourceId
) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = getDataSource(webSession, dataSourceId);
AIContextSettingsDataSource settings = new AIContextSettingsDataSource(dataSourceContainer);
String userOrigin = ServletAppUtils.getOriginFromRequest(request);
return new WebAIDataSourceSettings(settings, userOrigin);
}
@NotNull
@Override
public WebAIDataSourceSettings saveDataSourceAiSettings(
@NotNull HttpServletRequest request,
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull DataSourceId dataSourceId,
@NotNull WebAiChatCompletionSettingsInput settingsInput
) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = getDataSource(webSession, dataSourceId);
AIContextSettingsDataSource aiSettings = new AIContextSettingsDataSource(dataSourceContainer);
if (settingsInput.mcpEnabled() != null) {
aiSettings.setMcpEnabled(settingsInput.mcpEnabled());
}
if (settingsInput.metaTransferConfirmed() != null) {
aiSettings.setMetaTransferConfirmed(settingsInput.metaTransferConfirmed());
}
if (settingsInput.scope() != null) {
aiSettings.setScope(settingsInput.scope());
}
if (settingsInput.customObjectIds() != null) {
aiSettings.setCustomObjectIds(
WebAIUtils.convertNodePathsToObjectIds(
webSession,
dataSourceContainer.getProject(),
settingsInput.customObjectIds()
)
);
}
aiSettings.saveSettings();
String userOrigin = ServletAppUtils.getOriginFromRequest(request);
return new WebAIDataSourceSettings(aiSettings, userOrigin);
}
@NotNull
@Override
public WebAIConfigurationProfile createProfile(
@NotNull WebSession webSession,
@NotNull WebAIConfigurationProfileInput input
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
AISettings settings = AISettingsManager.getInstance().getSettings();
if (CommonUtils.isEmpty(input.profileId())) {
throw new DBWebException("Profile ID is not specified");
}
if (CommonUtils.isEmpty(input.engineId())) {
throw new DBWebException("Engine ID is not specified");
}
if (CommonUtils.isEmpty(input.profileName())) {
throw new DBWebException("Profile name is not specified");
}
AIEngineDescriptor engineDescriptor = AIEngineRegistry.getInstance().getEngineDescriptor(input.engineId());
if (engineDescriptor == null) {
throw new DBWebException("Invalid AI engine configuration ID " + input.engineId());
}
try {
settings.createConfiguration(input.profileId(), engineDescriptor);
AIConfigurationProfile profile = settings.getConfiguration(input.profileId());
profile.setProfileName(input.profileName());
if (input.configuration() != null) {
profile.setConfiguration(toEngineConfiguration(webSession.getProgressMonitor(), profile, input.configuration()));
}
AISettingsManager.getInstance().saveSettings();
addAISettingsChangedEvent(webSession);
return new WebAIConfigurationProfile(webSession, settings.getConfiguration(input.profileId()));
} catch (DBException e) {
throw new DBWebException("Error creating AI configuration " + input.profileId(), e);
}
}
@NotNull
@Override
public WebAIConfigurationProfile updateProfile(
@NotNull WebSession webSession,
@NotNull WebAIConfigurationProfileInput input
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
try {
AISettings settings = AISettingsManager.getInstance().getSettings();
AIConfigurationProfile profile = settings.getConfiguration(input.profileId());
if (input.profileName() != null) {
profile.setProfileName(input.profileName());
}
if (input.configuration() != null) {
profile.setConfiguration(toEngineConfiguration(webSession.getProgressMonitor(), profile, input.configuration()));
}
AISettingsManager.getInstance().saveSettings();
addAISettingsChangedEvent(webSession);
return new WebAIConfigurationProfile(webSession, profile);
} catch (DBException e) {
throw new DBWebException("Error updating AI configuration " + input.profileId(), e);
}
}
@Override
public boolean deleteProfile(@NotNull WebSession webSession, @NotNull String profileId) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
try {
AISettings settings = AISettingsManager.getInstance().getSettings();
AIConfigurationProfile profile = settings.getConfiguration(profileId);
settings.removeConfiguration(profile);
} catch (DBException e) {
throw new DBWebException("Error deleting AI configuration " + profileId, e);
}
return true;
}
@NotNullWhen("dataSourceId != null")
private DBPDataSourceContainer getDataSource(
@NotNull WebSession webSession,
@Nullable DataSourceId dataSourceId
) throws DBWebException {
if (dataSourceId == null) {
return null;
}
return WebDataSourceUtils.getWebConnectionInfo(webSession, dataSourceId.projectId(), dataSourceId.connectionId())
.getDataSourceContainer();
}
private void addAISettingsChangedEvent(@NotNull WebSession webSession) {
ServletAppUtils.getServletApplication().getEventController().addEvent(
new WSWorkspaceConfigurationChangedEvent(
AISettingsManager.AI_CONFIGURATION_FILE_NAME,
WebEventUtils.getSmSessionId(webSession),
webSession.getUserId()
)
);
}
@NotNull
private Map<String, Object> fillModelDefaults(
@NotNull DBRProgressMonitor monitor,
@NotNull AIConfigurationProfile profile,
@NotNull Map<String, Object> engineSettingsInput
) throws DBException {
// TODO: Think how default values can be set without modifying input map and not affecting the code model
Map<String, Object> inputProperties = JSONUtils.getObject(engineSettingsInput, "properties");
AIEngineProperties configuration;
try {
configuration = profile.getConfiguration();
if (!configuration.isValidConfiguration()) {
inputProperties.put(AIConstants.AI_CONTEXT_SIZE_PROPERTY, AIConstants.DEFAULT_CONTEXT_WINDOW_SIZE);
return engineSettingsInput;
}
} catch (DBException e) {
throw new DBWebException("Error converting engine configuration", e);
}
if (!profile.getEngineDescriptor().isProvidesMetadata()) {
return engineSettingsInput;
}
try (AIEngine<?> engine = profile.getEngineDescriptor().createEngineInstance(configuration)) {
Object modelId = inputProperties.get(AIConstants.AI_MODEL_PROPERTY);
AIModel model = engine.getModels(monitor)
.stream()
.filter(m -> Objects.equals(m.name(), modelId))
.findFirst()
.orElse(null);
if (model != null) {
inputProperties.put(AIConstants.AI_CONTEXT_SIZE_PROPERTY, model.contextWindowSize());
inputProperties.put(AIConstants.AI_TEMPERATURE_PROPERTY, model.defaultTemperature());
} else {
inputProperties.put(AIConstants.AI_CONTEXT_SIZE_PROPERTY, AIConstants.DEFAULT_CONTEXT_WINDOW_SIZE);
log.info("Model " + modelId + " not found");
}
} catch (DBException e) {
log.info("Error fetching models ", e);
}
return engineSettingsInput;
}
@NotNull
private AIEngineProperties toEngineConfiguration(
@NotNull DBRProgressMonitor progressMonitor,
@NotNull AIConfigurationProfile profile,
@NotNull Map<String, Object> engineSettingsInput
) throws DBWebException {
Map<String, Object> engineProperties = JSONUtils.getObject(engineSettingsInput, "properties");
// TODO: Think how default values can be set without modifying input map and not affecting the code model
if (engineProperties.get(AIConstants.AI_TEMPERATURE_PROPERTY) instanceof String stringValue && stringValue.isBlank()) {
engineProperties.put(AIConstants.AI_TEMPERATURE_PROPERTY, 0.0);
}
if (engineProperties.get(AIConstants.AI_CONTEXT_SIZE_PROPERTY) instanceof String stringValue && stringValue.isBlank()) {
engineProperties.put(AIConstants.AI_CONTEXT_SIZE_PROPERTY, AIConstants.DEFAULT_CONTEXT_WINDOW_SIZE);
}
try {
AIEngineProperties configuration = profile.getConfiguration();
PropertySourceEditable pse = new PropertySourceEditable(configuration, configuration);
pse.collectProperties();
for (DBPPropertyDescriptor pd : pse.getProperties()) {
Object value = engineProperties.get(pd.getId());
if (value != null) {
pse.setPropertyValue(progressMonitor, pd.getId(), value);
}
}
return configuration;
} catch (DBException e) {
throw new DBWebException("Error converting engine configuration", e);
}
}
private void renameConversation(
@NotNull AIChatSession chatSession,
@NotNull AIChatConversation conversation,
@NotNull String newName
) {
conversation.setCaption(newName.trim());
chatSession.notifyConversationRenamed(conversation, newName.trim());
}
private static void saveCompletionSettings(
@NotNull WebSession webSession,
@NotNull AIChatSession session,
@NotNull AIChatConversation conversation,
@Nullable WebAiChatCompletionSettingsInput settingsInput
) throws DBWebException {
AIContextSettingsChatConversation settings = WebAIUtils.getConversationSettings(session, conversation);
if (settings == null || settingsInput == null) {
return;
}
if (settingsInput.profile() != null) {
AIConfigurationProfile profile = AISettingsManager.getInstance().getSettings().getConfigurationOrNull(settingsInput.profile());
if (profile == null) {
throw new DBWebException("Invalid AI configuration profile " + settingsInput.profile());
}
conversation.setProfile(profile);
}
if (settingsInput.metaTransferConfirmed() != null) {
settings.setMetaTransferConfirmed(settingsInput.metaTransferConfirmed());
}
if (settingsInput.scope() != null) {
settings.setScope(settingsInput.scope());
}
if (settingsInput.customObjectIds() != null) {
DBPProject project = conversation.getDataSource() == null ? null : conversation.getDataSource().getProject();
if (project != null) {
settings.setCustomObjectIds(WebAIUtils.convertNodePathsToObjectIds(webSession, project, settingsInput.customObjectIds()));
}
}
try {
settings.saveSettings();
} catch (DBException e) {
throw new DBWebException("Error saving AI completion settings", e);
}
}
private static void setAiLanguage(@NotNull WebSession webSession, @Nullable String language) throws DBException {
if (language != null) {
CBApplication.getInstance().saveProductConfiguration(
webSession,
Map.of(AIConstants.AI_RESPONSE_LANGUAGE, language)
);
}
}
@Nullable
private static String getAiLanguage() {
return DBWorkbench.getPlatform().getPreferenceStore()
.getString(AIConstants.AI_RESPONSE_LANGUAGE);
}
}
@@ -0,0 +1,66 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.gql;
import io.cloudbeaver.model.app.ServletAppConfiguration;
import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.app.ServletServerConfiguration;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.ai.WebAIFeatureProvider;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.ai.registry.AISettingsManager;
import org.jkiss.dbeaver.model.websocket.event.WSWorkspaceConfigurationChangedEvent;
public class WebServiceAIConfigurator implements DBWServiceServerConfigurator {
@Override
public void configureServer(
@NotNull ServletApplication application,
@Nullable WebSession session,
@NotNull ServletServerConfiguration serverConfiguration,
@NotNull ServletAppConfiguration appConfig
) throws DBException {
var aiDisabled = !appConfig.isFeatureEnabled(WebAIFeatureProvider.AI_FEATURE_ID);
if (aiDisabled && !AISettingsManager.isConfigExists()) {
// do nothing, ai disabled and settings not exists
return;
}
var settings = AISettingsManager.getInstance().getSettings();
settings.setAiDisabled(aiDisabled);
application.getEventController().addEvent(
new WSWorkspaceConfigurationChangedEvent(
AISettingsManager.AI_CONFIGURATION_FILE_NAME,
null,
null
)
);
}
@Override
public void migrateConfigurationIfNeeded(@NotNull ServletApplication application) throws DBException {
}
@Override
public void reloadConfiguration(@NotNull ServletAppConfiguration appConfig) throws DBException {
}
}
@@ -0,0 +1,215 @@
/*
* 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.ai.gql;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.service.DBWBindingContext;
import io.cloudbeaver.service.DBWServiceInitializer;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.ai.WebAIFeatureProvider;
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
import io.cloudbeaver.service.ai.model.inputs.WebAIChatConversationInput;
import io.cloudbeaver.service.ai.model.inputs.WebAIConfigurationProfileInput;
import io.cloudbeaver.service.ai.model.inputs.WebAiChatCompletionSettingsInput;
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.ai.AIConstants;
import org.jkiss.dbeaver.model.ai.registry.AISettingsManager;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.utils.PrefUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class WebServiceBindingAI extends WebServiceBindingBase<DBWServiceAI> implements DBWServiceInitializer {
private static final Log log = Log.getLog(WebServiceBindingAI.class);
public WebServiceBindingAI() {
super(DBWServiceAI.class, new WebServiceAI(), "schema/service.ai.graphqls");
DBPPreferenceStore store = DBWorkbench.getPlatform().getPreferenceStore();
PrefUtils.setDefaultPreferenceValue(store, AIConstants.AI_INCLUDE_SOURCE_TEXT_IN_QUERY_COMMENT, true);
}
@Override
public void bindWiring(DBWBindingContext model) {
model.getQueryType()
.dataFetcher("aiSettings", env -> getService(env).getAiSettings())
.dataFetcher("aiListEngines", env -> getService(env).getEngineConfigurations())
.dataFetcher(
"aiListEngineProperties",
env -> getService(env).getEngineConfigurationParameters(
getWebSession(env),
getArgumentVal(env, "engineId"),
getArgument(env, "profileId"),
env.getArgument("settings")
)
).dataFetcher("aiListFunctions", env -> getService(env).getFunctions(getWebSession(env))
).dataFetcher("aiListProfiles", env -> getService(env).getProfiles(getWebSession(env))
).dataFetcher(
"aiListChatConversations",
env -> getService(env).getChatConversations(
getWebSession(env),
JSONUtils.deserializeObject(getArgument(env, "dataSourceId"), DataSourceId.class)
)
).dataFetcher(
"aiChatConversationInfo",
env -> getService(env).getChatConversationInfo(
getWebSession(env),
getArgumentVal(env, "conversationId"),
env.getArgument("loadMetrics")
)
).dataFetcher(
"aiDataSourceSettings",
env -> getService(env).getDataSourceAiSettings(
GraphQLEndpoint.getServletRequestOrThrow(env),
getWebSession(env),
JSONUtils.deserializeObject(getArgumentVal(env, "dataSourceId"), DataSourceId.class)
)
);
model.getMutationType()
.dataFetcher(
"aiSaveSettings",
env -> getService(env).saveAiSettings(
getWebSession(env),
JSONUtils.deserializeObject(getArgumentVal(env, "settings"), WebAISettingsConfig.class)
)
)
.dataFetcher(
"aiSaveDataSourceSettings",
env -> {
DataSourceId dataSourceId = JSONUtils.deserializeObject(
getArgumentVal(env, "dataSourceId"),
DataSourceId.class
);
return getService(env).saveDataSourceAiSettings(
GraphQLEndpoint.getServletRequestOrThrow(env),
getWebSession(env),
dataSourceId.projectId(),
dataSourceId,
JSONUtils.deserializeObject(getArgumentVal(env, "settings"), WebAiChatCompletionSettingsInput.class)
);
}
)
.dataFetcher(
"aiSaveEngineConfiguration",
env -> getService(env).saveEngineConfiguration(
getWebSession(env),
getArgumentVal(env, "profileId"),
getArgumentVal(env, "settings")
)
).dataFetcher(
"asyncAiPerformQueryCompletion",
env -> getService(env).performQueryCompletion(
getWebSession(env),
WebServiceBindingSQL.getSQLContext(env),
getArgumentVal(env, "request")
)
).dataFetcher(
"asyncAiPerformQueryCompletionResult",
env -> getService(env).performQueryCompletionResult(
getWebSession(env),
getArgumentVal(env, "taskId")
)
).dataFetcher(
"aiCreateChatConversation",
env -> getService(env).createChat(
getWebSession(env),
JSONUtils.deserializeObject(getArgument(env, "config"), WebAIChatConversationInput.class)
)
).dataFetcher(
"aiUpdateChatConversation",
env -> getService(env).updateChatConversation(
getWebSession(env),
getArgumentVal(env, "conversationId"),
JSONUtils.deserializeObject(getArgument(env, "config"), WebAIChatConversationInput.class)
)
).dataFetcher(
"aiDeleteChatConversation",
env -> getService(env).deleteChatConversation(
getWebSession(env),
getArgumentVal(env, "conversationId")
)
).dataFetcher(
"aiSendChatMessage",
env -> getService(env).asyncSendChatMessage(
getWebSession(env),
getArgumentVal(env, "conversationId"),
getArgumentVal(env, "prompt")
)
).dataFetcher(
"aiClearLastChatMessages",
env -> getService(env).setLastChatMessage(
getWebSession(env),
getArgumentVal(env, "conversationId"),
getArgumentVal(env, "messageId")
)
).dataFetcher(
"aiCreateProfile", env -> getService(env).createProfile(
getWebSession(env),
JSONUtils.deserializeObject(getArgumentVal(env, "config"), WebAIConfigurationProfileInput.class)
)
).dataFetcher(
"aiUpdateProfile", env -> getService(env).updateProfile(
getWebSession(env),
JSONUtils.deserializeObject(getArgumentVal(env, "config"), WebAIConfigurationProfileInput.class)
)
).dataFetcher(
"aiDeleteProfile", env -> getService(env).deleteProfile(
getWebSession(env),
getArgumentVal(env, "profileId")
)
);
}
@Override
public void initializeService(@NotNull CBApplication<?> application) {
// adds event listener to config file changer
AISettingsManager.getInstance().addChangedListener(
x -> {
// check if AI is enabled
boolean aiEnabled = !x.getSettings().isAiDisabled();
List<String> enabledFeatures = new ArrayList<>(Arrays.asList(application.getAppConfiguration().getEnabledFeatures()));
boolean aiFeatureEnabled = enabledFeatures.contains(WebAIFeatureProvider.AI_FEATURE_ID);
// remove AI feature if it is disabled (and do not remove if it is enabled)
if ((aiEnabled && aiFeatureEnabled) || (!aiEnabled && !aiFeatureEnabled)) {
// nothing to change
return;
}
if (aiEnabled) {
enabledFeatures.add(WebAIFeatureProvider.AI_FEATURE_ID);
} else {
enabledFeatures.remove(WebAIFeatureProvider.AI_FEATURE_ID);
}
application.getAppConfiguration().setEnabledFeatures(enabledFeatures.toArray(String[]::new));
if (application.isConfigurationMode()) {
return;
}
try {
application.flushConfiguration();
} catch (Exception e) {
log.error("Failed to save server configuration", e);
}
}
);
}
}
@@ -0,0 +1,132 @@
/*
* 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.ai.model;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.ai.gql.WebServiceAI;
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.ai.AIChatConversation;
import org.jkiss.dbeaver.model.ai.AIContextSettingsChatConversation;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
import java.util.UUID;
public class WebAIChatConversation {
@NotNull
private final WebSession webSession;
@NotNull
private final AIChatConversation conversation;
@Nullable
private final WebAIChatConversationMetrics metrics;
public WebAIChatConversation(
@NotNull WebSession webSession,
@NotNull AIChatConversation conversation
) {
this.webSession = webSession;
this.conversation = conversation;
this.metrics = null;
}
public WebAIChatConversation(
@NotNull WebSession webSession,
@NotNull AIChatConversation conversation,
@Nullable WebAIChatConversationMetrics metrics
) {
this.webSession = webSession;
this.conversation = conversation;
this.metrics = metrics;
}
@Nullable
public DataSourceId getDataSourceId() {
DBPDataSourceContainer dataSourceContainer = conversation.getDataSource();
if (dataSourceContainer == null) {
return null;
}
return new DataSourceId(
dataSourceContainer.getProject().getId(),
dataSourceContainer.getId()
);
}
@NotNull
public UUID getId() {
return conversation.getId();
}
@NotNull
public String getPromptGeneratorId() {
return conversation.getPromptGenerator().generatorId();
}
@NotNull
public String getCaption() {
return conversation.getCaption();
}
@Nullable
public WebAIChatConversationSettings getSettings() {
AIContextSettingsChatConversation settings = conversation.getCustomSettings();
if (settings == null) {
return null;
}
return new WebAIChatConversationSettings(webSession, settings, conversation.getDataSource());
}
@NotNull
public OffsetDateTime getTime() {
LocalDateTime time = conversation.getMessages().isEmpty() ? conversation.getTime() : conversation.getLastMessageTime();
return time.atZone(ZoneId.systemDefault())
.withZoneSameInstant(ZoneOffset.UTC)
.toOffsetDateTime();
}
@NotNull
public List<WebAIMessage> getMessages() {
return conversation.getMessages().stream()
.map(message -> new WebAIMessage(message, conversation))
.toList();
}
public boolean isWaitingForResponse() {
return webSession.getAttribute(WebServiceAI.AI_WAITING_ATTR + conversation.getId()) != null;
}
@Nullable
public WebAIChatConversationMetrics getMetrics() {
return metrics;
}
@Nullable
public String getProfile() {
return conversation.getProfile() == null ? null : conversation.getProfile().getProfileId();
}
@NotNull
public AIChatConversation getConversation() {
return conversation;
}
}
@@ -0,0 +1,44 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model;
import org.jkiss.dbeaver.model.ai.AIExtendedUsage;
public class WebAIChatConversationMetrics {
private final int totalInputTokens;
private final int totalOutputTokens;
private WebAIChatConversationMetrics(int totalInputTokens, int totalOutputTokens) {
this.totalInputTokens = totalInputTokens;
this.totalOutputTokens = totalOutputTokens;
}
public int getTotalInputTokens() {
return totalInputTokens;
}
public int getTotalOutputTokens() {
return totalOutputTokens;
}
public static WebAIChatConversationMetrics from(AIExtendedUsage aiExtendedUsage) {
return new WebAIChatConversationMetrics(
aiExtendedUsage.totalInputTokens(),
aiExtendedUsage.totalOutputTokens()
);
}
}
@@ -0,0 +1,73 @@
/*
* 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.ai.model;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.ai.WebAIUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.ai.AIContextSettingsChatConversation;
import org.jkiss.dbeaver.model.ai.AIDatabaseScope;
import org.jkiss.dbeaver.model.app.DBPProject;
import java.util.List;
public class WebAIChatConversationSettings {
private static final Log log = Log.getLog(WebAIChatConversationSettings.class);
@NotNull
private final WebSession webSession;
@NotNull
private final AIContextSettingsChatConversation settings;
@Nullable
private final DBPProject project;
public WebAIChatConversationSettings(
@NotNull WebSession webSession,
@NotNull AIContextSettingsChatConversation settings,
@Nullable DBPDataSourceContainer container
) {
this.webSession = webSession;
this.settings = settings;
this.project = container == null ? null : container.getProject();
}
public boolean isMetaTransferConfirmed() {
return true; //FIXME: we need to implement this
}
public AIDatabaseScope getScope() {
return settings.getScope();
}
public List<String> getCustomObjectIds() {
if (project == null) {
return List.of();
}
String[] customObjectIds = settings.getCustomObjectIds();
if (customObjectIds == null || customObjectIds.length == 0) {
return List.of();
}
return WebAIUtils.convertObjectIdsToNodePaths(
webSession.getProgressMonitor(),
project,
customObjectIds
);
}
}
@@ -0,0 +1,57 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.WebPropertyInfo;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.ai.AIConfigurationProfile;
public class WebAIConfigurationProfile {
@NotNull
private final transient WebSession webSession;
@NotNull
private final AIConfigurationProfile profile;
public WebAIConfigurationProfile(@NotNull WebSession webSession, @NotNull AIConfigurationProfile profile) {
this.webSession = webSession;
this.profile = profile;
}
@NotNull
public String getId() {
return profile.getProfileId();
}
@NotNull
public String getName() {
return profile.getProfileName();
}
@NotNull
public String getEngineId() {
return profile.getEngineId();
}
@NotNull
public WebPropertyInfo[] getConfiguration() throws DBException {
return WebServiceUtils.getObjectFilteredProperties(webSession, profile.getConfiguration(), null);
}
}
@@ -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.ai.model;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.ai.AIContextSettings;
import org.jkiss.dbeaver.model.ai.AIContextSettingsDataSource;
import org.jkiss.dbeaver.model.ai.AIDatabaseScope;
import org.jkiss.dbeaver.model.meta.Property;
public class WebAIDataSourceSettings {
@NotNull
private final AIContextSettingsDataSource settings;
@NotNull
private final String userOrigin;
public WebAIDataSourceSettings(@NotNull AIContextSettingsDataSource settings, @NotNull String userOrigin) {
this.settings = settings;
this.userOrigin = userOrigin;
}
@Property
public boolean isMetaTransferConfirmed() {
return settings.isMetaTransferConfirmed();
}
@Nullable
@Property
public AIDatabaseScope getScope() {
return settings.getScope();
}
@NotNull
public DBPDataSourceContainer getDataSourceContainer() {
return settings.getDataSourceContainer();
}
@NotNull
public AIContextSettings getSettings() {
return settings;
}
@NotNull
public String getUserOrigin() {
return userOrigin;
}
}
@@ -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.service.ai.model;
import io.cloudbeaver.WebServiceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.ai.AIFunctionDescriptor;
public class WebAIFunctionInfo {
@NotNull
private final transient AIFunctionDescriptor descriptor;
public WebAIFunctionInfo(@NotNull AIFunctionDescriptor descriptor) {
this.descriptor = descriptor;
}
@NotNull
public String getId() {
return descriptor.getFullId();
}
@NotNull
public String getName() {
return descriptor.getName();
}
@Nullable
public String getDescription() {
return descriptor.getAiDescription();
}
public boolean isSystem() {
return descriptor.isSystem();
}
@Nullable
public String getIcon() {
return WebServiceUtils.makeIconId(descriptor.getIcon());
}
}
@@ -0,0 +1,91 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.ai.*;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
public class WebAIMessage {
private final AIChatMessage message;
private final AIChatConversation conversation;
public WebAIMessage(@NotNull AIChatMessage message, @NotNull AIChatConversation conversation) {
this.message = message;
this.conversation = conversation;
}
@NotNull
public String getConversationId() {
return conversation.getId().toString();
}
public int getId() {
return message.id();
}
@NotNull
public AIMessageType getRole() {
return message.message().getRole();
}
@NotNull
public String getContent() {
return message.message().getContent();
}
@NotNull
public String getDisplayMessage() {
return message.message().getDisplayMessage();
}
@Nullable
public AIFunctionCall getFunctionCall() {
return message.message().getFunctionCall();
}
@Nullable
public List<AIFunctionCall> getConfirmationFunctionCalls() {
if (message.message().getConfirmation() instanceof AIFunctionCallConfirmation fcc) {
return fcc.getFunctionCalls();
}
return null;
}
@Nullable
public AIConfirmation getFunctionConfirmation() {
return message.message().getConfirmation();
}
@Nullable
public AIFunctionResult getFunctionResult() {
return message.message().getFunctionResult();
}
@NotNull
public OffsetDateTime getTime() {
return message.message().getTime().atZone(ZoneId.systemDefault())
.withZoneSameInstant(ZoneOffset.UTC)
.toOffsetDateTime();
}
}
@@ -0,0 +1,27 @@
/*
* 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.ai.model;
import org.jkiss.code.NotNull;
public record WebAISendChatMessageInfo(
@NotNull WebAIChatConversation conversation,
@NotNull WebAIMessage userMessage,
@NotNull WebAIMessage assistantMessage
) {
}
@@ -0,0 +1,105 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageChunkEvent;
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageErrorEvent;
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.ai.*;
import org.jkiss.utils.CommonUtils;
import java.util.List;
public class WebAiChatResponseConsumer implements AIChatResponseConsumer {
private final StringBuilder responseBuilder;
private final AIChatConversation conversation;
private final WebSession webSession;
private final AIChatSession chatSession;
public WebAiChatResponseConsumer(
@NotNull AIChatConversation conversation,
@NotNull WebSession webSession,
@NotNull AIChatSession chatSession
) {
this.conversation = conversation;
this.webSession = webSession;
this.chatSession = chatSession;
this.responseBuilder = new StringBuilder();
}
@Override
public void nextMessageChunk(@NotNull String item) {
if (CommonUtils.isEmpty(item)) {
return;
}
if (responseBuilder.isEmpty()) {
var chatMessage = new AIChatMessage(conversation.getNextMessageId(), AIMessage.assistantMessage("", null));
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(chatMessage, conversation)));
}
responseBuilder.append(item);
webSession.addSessionEvent(new WSAiChatMessageChunkEvent(
conversation.getId(),
conversation.getNextMessageId(),
item,
false
));
}
@Override
public void processFunctionCall(@NotNull AIMessage fcMessage) {
// Send function call to front-end over web sockets
AIChatMessage chatMessage = conversation.addMessage(fcMessage);
WebAIMessage webMessage = new WebAIMessage(chatMessage, conversation);
webSession.addSessionEvent(new WSAiChatMessageEvent(webMessage));
chatSession.notifyMessageAdd(conversation, chatMessage);
}
@Override
public void warning(@NotNull String message) {
AIChatMessage chatMessage = conversation.addMessage(AIMessage.warningMessage(message));
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(chatMessage, conversation)));
}
@Override
public void error(@NotNull Throwable throwable) {
var errorMessage = conversation.addMessage(AIMessage.errorMessage(throwable));
if (responseBuilder.isEmpty()) {
webSession.addSessionEvent(
new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
} else {
webSession.addSessionEvent(
new WSAiChatMessageErrorEvent(
conversation.getId(),
conversation.getNextMessageId(),
throwable
));
}
chatSession.notifyMessageAdd(conversation, errorMessage);
}
@Override
public void complete(@NotNull List<AIMessageMeta> meta, boolean finishConversation) {
if (responseBuilder.isEmpty()) {
return;
}
AIChatMessage responseMessage = conversation.addMessage(AIMessage.assistantMessage(responseBuilder.toString(), meta));
chatSession.notifyMessageAdd(conversation, responseMessage);
webSession.addSessionEvent(new WSAiChatMessageChunkEvent(conversation.getId(), responseMessage.id(), null, true));
}
}
@@ -0,0 +1,61 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model.events;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
import org.jkiss.utils.CommonUtils;
import java.util.UUID;
public class WSAiChatMessageChunkEvent extends WSAbstractSessionEvent {
private final String messageId;
@NotNull
private final String conversationId;
@Nullable
private final String chunk;
private final boolean completed;
public WSAiChatMessageChunkEvent(@NotNull UUID conversationId, int messageId, @Nullable String chunk, boolean completed) {
super("cb_ai_chat_message_chunk", WSConstants.TOPIC_AI);
this.messageId = CommonUtils.toString(messageId);
this.conversationId = conversationId.toString();
this.chunk = chunk;
this.completed = completed;
}
@NotNull
public String getConversationId() {
return conversationId;
}
public String getMessageId() {
return messageId;
}
@Nullable
public String getChunk() {
return chunk;
}
public boolean isCompleted() {
return completed;
}
}
@@ -0,0 +1,56 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model.events;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
import org.jkiss.utils.CommonUtils;
import java.util.UUID;
public class WSAiChatMessageErrorEvent extends WSAbstractSessionEvent {
private final String messageId;
@NotNull
private final String conversationId;
@Nullable
private final String errorMessage;
public WSAiChatMessageErrorEvent(@NotNull UUID conversationId, int messageId, @Nullable Throwable throwable) {
super("cb_ai_chat_message_error", WSConstants.TOPIC_AI);
this.conversationId = conversationId.toString();
this.messageId = CommonUtils.toString(messageId);
this.errorMessage = CommonUtils.getAllExceptionMessages(throwable);
}
@NotNull
public String getConversationId() {
return conversationId;
}
@NotNull
public String getMessageId() {
return messageId;
}
@Nullable
public String getErrorMessage() {
return errorMessage;
}
}
@@ -0,0 +1,113 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model.events;
import io.cloudbeaver.service.ai.model.WebAIMessage;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.ai.AIFunctionCall;
import org.jkiss.dbeaver.model.ai.AIFunctionCallConfirmation;
import org.jkiss.dbeaver.model.ai.AIFunctionResult;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.session.WSAbstractSessionEvent;
import org.jkiss.utils.CommonUtils;
import java.time.format.DateTimeFormatter;
public class WSAiChatMessageEvent extends WSAbstractSessionEvent {
private static final String ID = "cb_ai_chat_message";
private final String messageId;
@NotNull
private final String conversationId;
@Nullable
private final String content;
@Nullable
private final String displayMessage;
@NotNull
private final String role;
private final String time;
@Nullable
private AIFunctionCallConfirmation functionConfirmation;
@Nullable
private final AIFunctionCall functionCall;
@Nullable
private final AIFunctionResult functionResult;
public WSAiChatMessageEvent(@NotNull WebAIMessage message) {
super(ID, WSConstants.TOPIC_AI);
this.messageId = CommonUtils.toString(message.getId());
this.conversationId = message.getConversationId();
this.displayMessage = message.getDisplayMessage();
this.content = message.getContent();
this.role = message.getRole().name();
this.time = message.getTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
this.functionCall = message.getFunctionCall();
this.functionResult = message.getFunctionResult();
if (message.getConfirmationFunctionCalls() != null) {
this.functionConfirmation = new AIFunctionCallConfirmation(
message.getConfirmationFunctionCalls());
}
}
@NotNull
public String getConversationId() {
return conversationId;
}
public String getMessageId() {
return messageId;
}
@Nullable
public String getDisplayMessage() {
return displayMessage;
}
@Nullable
public String getContent() {
return content;
}
@NotNull
public String getRole() {
return role;
}
@NotNull
public String getTime() {
return time;
}
@Nullable
public AIFunctionCallConfirmation getFunctionConfirmation() {
return functionConfirmation;
}
@Nullable
public AIFunctionCall getFunctionCall() {
return functionCall;
}
@Nullable
public AIFunctionResult getFunctionResult() {
return functionResult;
}
}
@@ -0,0 +1,68 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model.events;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSClientEvent;
import java.util.List;
public class WSAiFunctionCallConfirmationClientEvent extends WSClientEvent {
public static final String ID = "cb_client_ai_function_call_confirmation";
@NotNull
private final String conversationId;
@NotNull
private final String messageId;
private final List<String> confirmedFunctionCalls;
private final List<String> declinedFunctionCalls;
public WSAiFunctionCallConfirmationClientEvent(
@NotNull String conversationId,
@NotNull String messageId,
@NotNull List<String> confirmedFunctionCalls,
@NotNull List<String> declinedFunctionCalls
) {
super(ID, WSConstants.TOPIC_AI);
this.conversationId = conversationId;
this.messageId = messageId;
this.confirmedFunctionCalls = confirmedFunctionCalls;
this.declinedFunctionCalls = declinedFunctionCalls;
}
@NotNull
public String getConversationId() {
return conversationId;
}
@NotNull
public String getMessageId() {
return messageId;
}
@NotNull
public List<String> getConfirmedFunctionCalls() {
return confirmedFunctionCalls == null ? List.of() : confirmedFunctionCalls;
}
@NotNull
public List<String> getDeclinedFunctionCalls() {
return declinedFunctionCalls == null ? List.of() : declinedFunctionCalls;
}
}
@@ -0,0 +1,101 @@
/*
* 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.ai.model.events;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.ai.WebAIUtils;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.ai.*;
import org.jkiss.dbeaver.model.ai.utils.AIUtils;
import org.jkiss.dbeaver.model.auth.SMSession;
import org.jkiss.dbeaver.model.websocket.event.WSClientEventHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class WSAiFunctionCallConfirmationHandler implements WSClientEventHandler<WSAiFunctionCallConfirmationClientEvent> {
public static final Log log = Log.getLog(WSAiFunctionCallConfirmationHandler.class);
@Override
public void handleEvent(@NotNull SMSession session, @NotNull WSAiFunctionCallConfirmationClientEvent event) throws DBException {
if (session instanceof WebSession webSession) {
AIChatSession chatSession = WebAIUtils.findAiChatSession(webSession);
if (chatSession == null) {
webSession.addSessionError(new DBException("Chat session '" + event.getConversationId() + "' not found."));
return;
}
AIChatConversation conversation = chatSession.getConversation(event.getConversationId());
if (conversation == null) {
webSession.addSessionError(new DBException("Chat conversation '" + event.getConversationId() + "' not found"));
return;
}
AIChatMessage message = conversation.getMessage(event.getMessageId());
if (message == null || !(message.message().getConfirmation() instanceof AIFunctionCallConfirmation fcc)) {
webSession.addSessionError(new DBException("Chat conversation confirmation '" + event.getMessageId() + "' not found"));
return;
}
List<AIFunctionCall> declinedCalls = new ArrayList<>();
List<AIFunctionCall> approvedCalls = new ArrayList<>();
List<String> confirmedFunctions = event.getConfirmedFunctionCalls();
List<String> declinedFunctions = event.getDeclinedFunctionCalls();
for (AIFunctionCall fc : fcc.getFunctionCalls()) {
if (confirmedFunctions.contains(fc.getId().toString())) {
approvedCalls.add(fc);
} else if (declinedFunctions.contains(fc.getId().toString())) {
declinedCalls.add(fc);
}
}
chatSession.declineFunctionCalls(conversation, declinedCalls);
CompletableFuture<AIChatConversation> submission;
if (approvedCalls.isEmpty()) {
submission = WebAIUtils.scheduleConversationSubmission(
webSession,
chatSession,
conversation,
null,
"Continue AI conversation"
);
} else {
submission = WebAIUtils.scheduleConversationSubmission(
webSession,
chatSession,
conversation,
new AIFunctionCallConfirmation(approvedCalls),
"Confirm function calls"
);
AIToolboxManager toolboxManager = chatSession.getAssistant().getToolboxManager();
if (!declinedCalls.isEmpty() && !AIUtils.hasInformationFunctions(toolboxManager, approvedCalls)) {
submission = submission.thenCompose(ignored -> WebAIUtils.scheduleConversationSubmission(
webSession,
chatSession,
conversation,
null,
"Continue AI conversation"
));
}
}
}
}
}
@@ -0,0 +1,22 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model.inputs;
import org.jkiss.code.NotNull;
public record DataSourceId(@NotNull String projectId, @NotNull String connectionId) {
}
@@ -0,0 +1,27 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model.inputs;
import org.jkiss.code.Nullable;
public record WebAIChatConversationInput(
@Nullable String promptGeneratorId,
@Nullable DataSourceId dataSourceId,
@Nullable String caption,
@Nullable WebAiChatCompletionSettingsInput settings
) {
}
@@ -0,0 +1,30 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model.inputs;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import java.util.Map;
public record WebAIConfigurationProfileInput(
@NotNull String profileId,
@Nullable String profileName,
@Nullable String engineId,
@Nullable Map<String, Object> configuration
) {
}
@@ -0,0 +1,28 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package io.cloudbeaver.service.ai.model.inputs;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.ai.AIDatabaseScope;
public record WebAiChatCompletionSettingsInput(
@Nullable Boolean metaTransferConfirmed,
@Nullable AIDatabaseScope scope,
@Nullable String[] customObjectIds,
@Nullable String profile,
@Nullable Boolean mcpEnabled) {
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Authentication
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
Bundle-Version: 1.0.150.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260817
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.150-SNAPSHOT</version>
<version>1.0.151-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,3 +1,7 @@
####################################################
# Authentication API
####################################################
enum AuthCredentialEncryption {
none,
plain,
@@ -15,11 +19,11 @@ type AuthCredentialInfo {
displayName: String!
description: String
# This field must be shown in admin panel
"This field must be shown in admin panel"
admin: Boolean!
# This field must be shown in login form
"This field must be shown in login form"
user: Boolean!
# This field can be used as user identifier
"This field can be used as user identifier"
identifying: Boolean!
possibleValues: [String]
@@ -80,8 +80,9 @@ public class ReverseProxyConfigurator implements DBWServiceServerConfigurator {
parameters.put(RPConstants.PARAM_FIRST_NAME, RPAuthProvider.X_FIRST_NAME);
parameters.put(RPConstants.PARAM_LAST_NAME, RPAuthProvider.X_LAST_NAME);
parameters.put(RPConstants.PARAM_FULL_NAME, RPAuthProvider.X_FULL_NAME);
parameters.put(RPAuthProvider.PARAM_AUTO_USER_PROVISIONING, true);
smReverseProxyProviderConfiguration.setParameters(parameters);
authApplication.getAuthConfiguration().addAuthProviderConfiguration(smReverseProxyProviderConfiguration );
authApplication.getAuthConfiguration().addAuthProviderConfiguration(smReverseProxyProviderConfiguration);
try {
authApplication.flushConfiguration();
} catch (Exception e) {
@@ -129,7 +129,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
try {
Map<String, Object> authParameters = new HashMap<>();
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getOriginFromRequest(httpRequest));
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getHostOriginFromRequest(httpRequest));
var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, authParameters, forceSessionsLogout);
if (smAuthInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Data Transfer
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 1.0.152.qualifier
Bundle-Release-Date: 20260817
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.151-SNAPSHOT</version>
<version>1.0.152-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,4 +1,6 @@
####################################################
# Data transfer queries
####################################################
type DataTransferProcessorInfo {
id: ID!
@@ -34,6 +36,27 @@ type DataTransferDefaultExportSettings {
supportedEncodings: [String!]!
}
"""
Database dependent data import settings.
Which of them are supported by a particular connection is reported by ConnectionInfo.driverConfiguration.
New database dependent import settings should be added here.
"""
input DataTransferImportSettings {
"Id of the 'on duplicate key' replace method"
onDuplicateKeyMethod: ID
"Use bulk load during import"
useBulkLoad: Boolean
"Use transactions during import"
useTransactions: Boolean
}
input DataTransferImportParameters {
"Import processor ID"
processorId: ID!
"Import settings. Omitted settings keep their default value"
settings: DataTransferImportSettings
}
input DataTransferParameters {
"Processor ID"
processorId: ID!
@@ -78,6 +101,15 @@ extend type Query {
parameters: DataTransferParameters!
): AsyncTaskInfo!
"Creates async task for data transfer import of a previously uploaded file into the results"
dataTransferImportDataIntoResults(
projectId: ID,
connectionId: ID!,
contextId: ID!,
resultsId: ID!,
parameters: DataTransferImportParameters!
): AsyncTaskInfo!
"Deletes data transfer file by ID"
dataTransferRemoveDataFile(dataFileId: String!): Boolean @deprecated(reason: "25.0.1")
@@ -21,13 +21,9 @@ import io.cloudbeaver.WebAction;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferDefaultExportSettings;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferStreamProcessor;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferTaskConfig;
import io.cloudbeaver.service.data.transfer.impl.*;
import io.cloudbeaver.service.sql.WebSQLContextInfo;
import io.cloudbeaver.service.sql.WebSQLProcessor;
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.rm.RMConstants;
@@ -61,11 +57,17 @@ public interface DBWServiceDataTransfer extends DBWService {
@NotNull
@WebAction(requireGlobalPermissions = RMConstants.PERMISSION_DATA_EDITOR_IMPORT)
WebAsyncTaskInfo asyncImportDataContainer(
@NotNull String processorId,
@NotNull Path path,
@NotNull WebSQLResultsInfo webSQLResultsInfo,
@NotNull WebSQLContextInfo sqlContextInfo,
@NotNull String resultsId,
@NotNull WebDataTransferImportParameters parameters,
@NotNull WebSession webSession) throws DBWebException;
@NotNull
WebAsyncTaskInfo runImportDataTask(
@NotNull WebSession webSession,
@NotNull String taskId,
@NotNull Path path) throws DBWebException;
@NotNull
@WebAction(requireGlobalPermissions = RMConstants.PERMISSION_DATA_EDITOR_EXPORT)
WebAsyncTaskInfo dataTransferExportDataFromResults(
@@ -94,4 +96,10 @@ public interface DBWServiceDataTransfer extends DBWService {
@NotNull WebDataTransferTaskConfig taskConfig,
@NotNull OutputStream outputStream
) throws DBException;
}
/**
* Checks if the user has permission to import data.
* Returns {@code true} if import is allowed, {@code false} otherwise.
*/
boolean validateImportPermission(@NotNull WebSession session);
}
@@ -21,10 +21,7 @@ import io.cloudbeaver.service.DBWBindingContext;
import io.cloudbeaver.service.DBWServiceBindingServlet;
import io.cloudbeaver.service.DBWServletContext;
import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferImportServlet;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferParameters;
import io.cloudbeaver.service.data.transfer.impl.WebDataTransferServlet;
import io.cloudbeaver.service.data.transfer.impl.WebServiceDataTransfer;
import io.cloudbeaver.service.data.transfer.impl.*;
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
@@ -56,6 +53,12 @@ public class WebServiceBindingDataTransfer extends WebServiceBindingBase<DBWServ
getArgumentVal(env, "resultsId"),
new WebDataTransferParameters(getArgument(env, "parameters"))
))
.dataFetcher("dataTransferImportDataIntoResults", env -> getService(env).asyncImportDataContainer(
WebServiceBindingSQL.getSQLContext(env),
getArgument(env, "resultsId"),
new WebDataTransferImportParameters(getArgument(env, "parameters")),
getWebSession(env)
))
.dataFetcher("dataTransferRemoveDataFile", env -> getService(env).dataTransferRemoveDataFile(
getWebSession(env),
getArgumentVal(env, "dataFileId")
@@ -0,0 +1,47 @@
/*
* 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.data.transfer.impl;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import java.util.Map;
/**
* Parameters of a data import
*/
public class WebDataTransferImportParameters {
private final String processorId;
private final Map<String, Object> settings;
public WebDataTransferImportParameters(@NotNull Map<String, Object> params) {
this.processorId = JSONUtils.getString(params, "processorId");
this.settings = JSONUtils.getObjectOrNull(params, "settings");
}
@Nullable
public String getProcessorId() {
return processorId;
}
@Nullable
public Map<String, Object> getSettings() {
return settings;
}
}
@@ -20,31 +20,23 @@ import com.google.gson.stream.JsonWriter;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.BaseWebPlatform;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebAppUtils;
import io.cloudbeaver.server.WebApplication;
import io.cloudbeaver.service.WebServiceServletBase;
import io.cloudbeaver.service.data.transfer.DBWServiceDataTransfer;
import io.cloudbeaver.service.sql.WebSQLContextInfo;
import io.cloudbeaver.service.sql.WebSQLProcessor;
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
import io.cloudbeaver.service.sql.WebServiceBindingSQL;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.HttpConstants;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
@@ -54,11 +46,10 @@ import java.util.UUID;
@MultipartConfig
public class WebDataTransferImportServlet extends WebServiceServletBase {
private static final Log log = Log.getLog(WebDataTransferImportServlet.class);
public static final String ECLIPSE_JETTY_MULTIPART_CONFIG = "org.eclipse.jetty.multipartConfig";
private static final String PARAM_TASK_ID = "taskId";
DBWServiceDataTransfer dbwServiceDataTransfer;
private final DBWServiceDataTransfer dbwServiceDataTransfer;
public WebDataTransferImportServlet(WebApplication application, DBWServiceDataTransfer dbwServiceDataTransfer) {
super(application);
@@ -79,7 +70,7 @@ public class WebDataTransferImportServlet extends WebServiceServletBase {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Permission denied");
return;
}
if (validateImportPermission(session)) {
if (!dbwServiceDataTransfer.validateImportPermission(session)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Import is not allowed for this user");
return;
}
@@ -91,57 +82,38 @@ public class WebDataTransferImportServlet extends WebServiceServletBase {
request.setAttribute(ECLIPSE_JETTY_MULTIPART_CONFIG, MULTI_PART_CONFIG);
Map<String, Object> variables = getVariables(request);
String projectId = JSONUtils.getString(variables, "projectId");
String connectionId = JSONUtils.getString(variables, "connectionId");
String contextId = JSONUtils.getString(variables, "contextId");
String resultId = JSONUtils.getString(variables, "resultsId");
String processorId = JSONUtils.getString(variables, "processorId");
if (projectId == null || connectionId == null || contextId == null || resultId == null || processorId == null) {
throw new IllegalArgumentException("Missing required parameters");
String taskId = JSONUtils.getString(variables, "taskId");
if (CommonUtils.isEmpty(taskId)) {
throw new IllegalArgumentException("Missing required parameter '" + PARAM_TASK_ID + "'");
}
WebConnectionInfo webConnectionInfo = session.getAccessibleProjectById(projectId).getWebConnectionInfo(connectionId);
WebSQLProcessor processor = WebServiceBindingSQL.getSQLProcessor(webConnectionInfo);
WebSQLContextInfo webSQLContextInfo = processor.getContext(contextId);
if (webSQLContextInfo == null) {
throw new DBWebException("Context is empty");
}
WebSQLResultsInfo webSQLResultsInfo = webSQLContextInfo.getResults(resultId);
Path filePath;
String fileName = UUID.randomUUID().toString();
Path filePath = tempFolder.resolve(fileName);
try {
InputStream file = request.getPart("fileData").getInputStream();
filePath = tempFolder.resolve(UUID.randomUUID().toString());
Files.write(filePath, file.readAllBytes());
request.getPart("fileData").write(fileName);
} catch (ServletException e) {
throw new DBWebException(e.getMessage());
}
WebAsyncTaskInfo asyncImportDataContainer =
dbwServiceDataTransfer.asyncImportDataContainer(processorId, filePath, webSQLResultsInfo, session);
WebAsyncTaskInfo importTask;
try {
importTask = dbwServiceDataTransfer.runImportDataTask(session, taskId, filePath);
} catch (Exception e) {
Files.deleteIfExists(filePath);
throw e;
}
response.setContentType(HttpConstants.CONTENT_TYPE_JSON);
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("id", asyncImportDataContainer.getId());
parameters.put("name", asyncImportDataContainer.getName());
parameters.put("running", asyncImportDataContainer.isRunning());
parameters.put("status", asyncImportDataContainer.getStatus());
parameters.put("error", asyncImportDataContainer.getError());
parameters.put("taskResult", asyncImportDataContainer.getTaskResult());
Map<String, Object> result = new LinkedHashMap<>();
result.put("id", importTask.getId());
result.put("name", importTask.getName());
result.put("running", importTask.isRunning());
result.put("status", importTask.getStatus());
result.put("error", importTask.getError());
result.put("taskResult", importTask.getTaskResult());
try (JsonWriter writer = new JsonWriter(response.getWriter())) {
JSONUtils.serializeMap(writer, parameters);
JSONUtils.serializeMap(writer, result);
}
}
}
private boolean validateImportPermission(@NotNull WebSession session) {
if (WebAppUtils.getWebApplication().isCommunity()) {
Map<String, Object> productSettings = WebAppUtils.getWebApplication().getServerConfiguration().getProductSettings();
return JSONUtils.getBoolean(productSettings, CBConstants.PREF_DATA_EDITOR_IMPORT_DISABLED_OLD, false);
}
return !session.hasGlobalPermission(DBWConstants.GLOBAL_PERMISSION_DATA_EDITOR_IMPORT);
}
}
}
@@ -0,0 +1,68 @@
/*
* 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.data.transfer.impl;
import io.cloudbeaver.service.sql.WebSQLResultsInfo;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.tools.transfer.registry.DataTransferProcessorDescriptor;
/**
* A data import task config
*/
public class WebDataTransferImportTaskConfig {
@NotNull
private final String taskId;
@NotNull
private final WebSQLResultsInfo results;
@NotNull
private final DataTransferProcessorDescriptor processor;
@NotNull
private final WebDataTransferImportParameters parameters;
public WebDataTransferImportTaskConfig(
@NotNull String taskId,
@NotNull WebSQLResultsInfo results,
@NotNull DataTransferProcessorDescriptor processor,
@NotNull WebDataTransferImportParameters parameters
) {
this.taskId = taskId;
this.results = results;
this.processor = processor;
this.parameters = parameters;
}
@NotNull
public String getTaskId() {
return taskId;
}
@NotNull
public WebSQLResultsInfo getResults() {
return results;
}
@NotNull
public DataTransferProcessorDescriptor getProcessor() {
return processor;
}
@NotNull
public WebDataTransferImportParameters getParameters() {
return parameters;
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,10 +18,12 @@ package io.cloudbeaver.service.data.transfer.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WebDataTransferSessionConfig {
private final Map<String, WebDataTransferTaskConfig> tasks = new HashMap<>();
private final Map<String, WebDataTransferImportTaskConfig> importTasks = new ConcurrentHashMap<>();
public WebDataTransferSessionConfig() {
}
@@ -36,10 +38,19 @@ public class WebDataTransferSessionConfig {
}
}
public void addImportTask(WebDataTransferImportTaskConfig importTaskConfig) {
importTasks.put(importTaskConfig.getTaskId(), importTaskConfig);
}
public WebDataTransferImportTaskConfig consumeImportTask(String taskId) {
return importTasks.remove(taskId);
}
public WebDataTransferSessionConfig deleteExportFiles() {
synchronized (tasks) {
tasks.clear();
}
importTasks.clear();
return this;
}
@@ -32,6 +32,7 @@ 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.data.json.JSONUtils;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
@@ -39,6 +40,7 @@ import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSDataManipulator;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSObjectContainer;
import org.jkiss.dbeaver.tools.transfer.DTConstants;
import org.jkiss.dbeaver.tools.transfer.IDataTransferConsumer;
import org.jkiss.dbeaver.tools.transfer.IDataTransferProcessor;
import org.jkiss.dbeaver.tools.transfer.database.*;
@@ -233,20 +235,54 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
}
@NotNull
@Override
public WebAsyncTaskInfo asyncImportDataContainer(
@NotNull String processorId,
@NotNull Path path,
@NotNull WebSQLResultsInfo sqlContext,
@NotNull WebSQLContextInfo sqlContext,
@NotNull String resultsId,
@NotNull WebDataTransferImportParameters parameters,
@NotNull WebSession webSession
) throws DBWebException {
webSession.addInfoMessage("Import data");
DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(processorId);
if (!validateImportPermission(webSession)) {
throw new DBWebException("Permission denied. Data import is not allowed for this user");
}
DataTransferProcessorDescriptor processor = DataTransferRegistry.getInstance().getProcessor(parameters.getProcessorId());
if (processor == null) {
throw new DBWebException("Wrong data processor '" + processorId + "'");
throw new DBWebException("Wrong data processor '" + parameters.getProcessorId() + "'");
}
WebSQLResultsInfo results = sqlContext.getResults(resultsId);
if (!(results.getDataContainer() instanceof DBSDataManipulator)) {
throw new DBWebException("Results '" + resultsId + "' do not support data import");
}
// The task is created but not started: it is executed once the file is uploaded for this task id
WebAsyncTaskInfo taskInfo = webSession.createAsyncTask("Data import");
WebDataTransferUtils.getSessionDataTransferConfig(webSession)
.addImportTask(new WebDataTransferImportTaskConfig(taskInfo.getId(), results, processor, parameters));
return taskInfo;
}
@NotNull
@Override
public WebAsyncTaskInfo runImportDataTask(
@NotNull WebSession webSession,
@NotNull String taskId,
@NotNull Path path
) throws DBWebException {
WebDataTransferImportTaskConfig importTask =
WebDataTransferUtils.getSessionDataTransferConfig(webSession).consumeImportTask(taskId);
if (importTask == null) {
throw new DBWebException("Data import task '" + taskId + "' not found");
}
WebAsyncTaskInfo taskInfo = webSession.getAsyncTask(taskId, "Data import", false);
if (taskInfo == null) {
throw new DBWebException("Data import task '" + taskId + "' not found");
}
webSession.addInfoMessage("Import data");
log.info(String.format("Data import started: [userId=%s]", webSession.getUserId()));
DBSDataContainer dataContainer = sqlContext.getDataContainer();
DataTransferProcessorDescriptor processor = importTask.getProcessor();
DBSDataContainer dataContainer = importTask.getResults().getDataContainer();
Map<String, Object> settings = importTask.getParameters().getSettings();
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
@Override
public void run(@NotNull DBRProgressMonitor monitor) throws InvocationTargetException {
@@ -254,7 +290,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
try {
monitor.subTask("Import data using " + processor.getName());
try {
importData(monitor, processor, (DBSDataManipulator) dataContainer, path);
importData(monitor, processor, (DBSDataManipulator) dataContainer, path, settings);
} catch (Exception e) {
if (e instanceof DBException) {
throw e;
@@ -273,7 +309,15 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
}
}
};
return webSession.createAndRunAsyncTask("Data import", runnable);
return webSession.runAsyncTask(taskInfo, runnable);
}
public boolean validateImportPermission(@NotNull WebSession session) {
if (WebAppUtils.getWebApplication().isCommunity()) {
Map<String, Object> productSettings = WebAppUtils.getWebApplication().getServerConfiguration().getProductSettings();
return !JSONUtils.getBoolean(productSettings, CBConstants.PREF_DATA_EDITOR_IMPORT_DISABLED_OLD, false);
}
return session.hasGlobalPermission(DBWConstants.GLOBAL_PERMISSION_DATA_EDITOR_IMPORT);
}
private void exportData(
@@ -342,7 +386,8 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
@NotNull DBRProgressMonitor monitor,
@NotNull DataTransferProcessorDescriptor processor,
@NotNull DBSDataManipulator dataContainer,
@NotNull Path path
@NotNull Path path,
@Nullable Map<String, Object> settings
) throws DBException {
IDataTransferProcessor processorInstance = processor.getInstance();
@@ -354,6 +399,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
DatabaseConsumerSettings databaseConsumerSettings = new DatabaseConsumerSettings();
databaseConsumerSettings.setContainer((DBSObjectContainer) dataContainer.getDataSource());
databaseConsumerSettings.setEnableQmLogging(true);
applyImportSettings(databaseConsumerSettings, settings);
consumer.setSettings(databaseConsumerSettings);
StreamProducerSettings producerSettings = new StreamProducerSettings();
@@ -384,4 +430,22 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
}
}
private void applyImportSettings(
@NotNull DatabaseConsumerSettings consumerSettings,
@Nullable Map<String, Object> settings
) {
if (CommonUtils.isEmpty(settings)) {
return;
}
consumerSettings.setOnDuplicateKeyInsertMethodId(CommonUtils.toString(
settings.get(DTConstants.PROP_ON_DUPLICATE_KEY_METHOD),
consumerSettings.getOnDuplicateKeyInsertMethodId()));
consumerSettings.setUseBulkLoad(CommonUtils.getBoolean(
settings.get(DTConstants.PROP_USE_BULK_LOAD),
consumerSettings.isUseBulkLoad()));
consumerSettings.setUseTransactions(CommonUtils.getBoolean(
settings.get(DTConstants.PROP_USE_TRANSACTIONS),
consumerSettings.isUseTransactions()));
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - File System
Bundle-SymbolicName: io.cloudbeaver.service.fs;singleton:=true
Bundle-Version: 1.0.68.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 1.0.69.qualifier
Bundle-Release-Date: 20260817
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.fs</artifactId>
<version>1.0.68-SNAPSHOT</version>
<version>1.0.69-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,3 +1,7 @@
####################################################
# File system API
####################################################
type FSFile @since(version: "23.2.2") {
"Name of the file or folder"
name: String!
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Web Service - LDAP
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.ldap.auth;singleton:=true
Bundle-Version: 1.0.33.qualifier
Bundle-Release-Date: 20260803
Bundle-Version: 1.0.34.qualifier
Bundle-Release-Date: 20260817
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,

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