Compare commits

..
556 changed files with 1170 additions and 15798 deletions
-37
View File
@@ -28,43 +28,6 @@ You can see a live demo of CloudBeaver server here: https://demo.cloudbeaver.io
## Changelog
### 26.1.4 2026-08-03
- AI Assistant:
- Added AI Chat to help users generate and fix queries and explore data more easily. It’s integrated with the SQL Editor to run generated queries instantly. Access it from the top toolbar. All AI features can be turned off in the Server configuration.
- Added the option to configure AI profiles in the Administration panel, usable in the AI Chat and for the @ai command in the SQL Editor. Both OpenAI and Copilot engines can be utilized for configuration.
- Connectivity:
- Resolved the issue where the application could no longer connect to the database after running for an extended period.
- Resolved the issue where user connection settings required re-login in a new session to apply their changes.
- Accessibility:
- Added a loader to the Save button in the Administration panel for visualizing the loading process.
- Improved drag-and-drop in the Navigator Tree for objects by enlarging the auto-scroll trigger areas.
- General:
- Added the ability to export generated SQL to a .sql file from the Generate SQL dialog.
- Improved "Open in SQL Editor" behavior by preserving the source table context for the Generate SQL dialog.
- Fixed synchronization between instances eliminates the need to refresh to see the updated connection or folder in another instance.
- Security:
- Added a new "Auto-create users" setting for the reverse proxy provider, allowing new user creation on login. It is enabled by default.
- Updated the PostgreSQL JDBC driver to version 42.7.13, including a fix for a high-severity security vulnerability (CVE-2026-54291).
- Fixed the critical vulnerability (CVE-2026-59873) in the tar library. The library was updated to version 7.5.19.
- Fixed the high vulnerability (CVE-2026-10050) in the jetty-security library. The library was updated to version 12.1.11.
- Fixed the high vulnerability (CVE-2026-14257) in the brace-expansion library. The library was updated to version 5.0.8.
- Fixed the high vulnerability (GHSA-r28c-9q8g-f849) in the postcss library. The library was updated to version 8.5.18.
- Fixed the high vulnerability (GHSA-pm4m-ph32-ghv5) in the js-yaml library. The library was updated to version 5.2.2.
- Fixed the high vulnerability (CVE-2026-59880) in the immutable library. The library was updated to version 5.1.8.
- Fixed the high vulnerability (CVE-2026-59879) in the fast-uri library. The library was updated to version 3.1.3.
- Fixed the high vulnerability (CVE-2026-13311) in the shell-quote library. The library was updated to version 1.9.0.
- Fixed the high vulnerability (GHSA-gcfj-64vw-6mp9) in the axios library. The library was updated to version 1.18.0.
- Databases and drivers:
- ClickHouse
- The driver was updated to version 0.10.0.
- Improved JSON display in the Value panel.
- MySQL: Fixed support for comments starting with '#' symbol in the SQL Editor.
- PostgreSQL:
- The driver was updated to version 42.7.13.
- Fixed an error that occurred when applying filters to database nodes.
- Fixed generated calls for procedures with OUT parameters.
### 26.1.3 2026-07-20
- Data Editor:
+2
View File
@@ -9,6 +9,8 @@ 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.16.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.15.qualifier
Bundle-Release-Date: 20260803
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.16-SNAPSHOT</version>
<version>1.0.15-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.107.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.106.qualifier
Bundle-Release-Date: 20260803
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.autoCommitMode.name = Auto-commit mode
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Auto-commit
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.autoCommitMode.name = Авто-коммит
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.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.autoCommitMode.name = Tự động commit
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.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.autoCommitMode.name = 自动提交
meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.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.107-SNAPSHOT</version>
<version>1.0.106-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -77,7 +77,9 @@ public class WebConnectionConfigInputHandler<T extends WebConnectionConfig, C ex
}
dataSource.setFolder(input.getFolder() != null ? registry.getFolder(input.getFolder()) : null);
dataSource.getConnectionConfiguration().getBootstrap().setDefaultAutoCommit(input.isDefaultAutoCommit());
if (input.isDefaultAutoCommit() != null) {
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;
}
@NotNull
@Nullable
@Override
public DBNModel getNavigatorModel() {
return webSession.getNavigatorModel();
@@ -69,13 +69,6 @@ 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
*/
@@ -1,62 +0,0 @@
/*
* 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,14 +103,8 @@ public class WebConnectionConfig {
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, -1);
readOnly = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_READ_ONLY);
if (expertSettingsValues != null) {
defaultAutoCommit = AutoCommitMode.from(
JSONUtils.getString(expertSettingsValues, WebExpertSettingsProperties.PROP_AUTO_COMMIT_MODE));
} else {
defaultAutoCommit = JSONUtils.getBoolean(params, WebExpertSettingsProperties.PROP_AUTO_COMMIT, true);
}
defaultAutoCommit = JSONUtils.getBoolean(
expertSettingsValues != null ? expertSettingsValues : 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_MODE, AutoCommitMode.fromValue(isAutocommit()));
expertSettings.put(WebExpertSettingsProperties.PROP_AUTO_COMMIT, isAutocommit());
expertSettings.put(WebExpertSettingsProperties.PROP_KEEP_ALIVE_INTERVAL, getKeepAliveInterval());
expertSettings.put(WebExpertSettingsProperties.PROP_READ_ONLY, isReadOnly());
expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_CATALOG, getDefaultCatalogName());
@@ -541,9 +541,12 @@ public class WebConnectionInfo {
}
@Property
@Nullable
public Boolean isAutocommit() {
return dataSourceContainer.getConnectionConfiguration().getBootstrap().getDefaultAutoCommit();
public boolean isAutocommit() {
Boolean isAutoCommit = dataSourceContainer.getConnectionConfiguration().getBootstrap().getDefaultAutoCommit();
if (isAutoCommit == null) {
return true;
}
return isAutoCommit;
}
@Property
@@ -595,13 +598,4 @@ public class WebConnectionInfo {
this.credentialsSavedInSession = credentialsSavedInSession;
}
/**
* Database-dependent driver configuration used to drive data import
*/
@Property
@NotNull
public WebDriverConfiguration getDriverConfiguration() {
return new WebDriverConfiguration(dataSourceContainer);
}
}
@@ -1,58 +0,0 @@
/*
* 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;
}
}
@@ -1,75 +0,0 @@
/*
* 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,19 +1,3 @@
/*
* 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;
@@ -21,7 +5,6 @@ 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;
@@ -32,7 +15,6 @@ 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";
@@ -48,15 +30,9 @@ public class WebExpertSettingsProperties implements DBPObject {
return 0;
}
@NotNull
@Property(
order = 2,
id = PROP_AUTO_COMMIT_MODE,
visibleIf = AutoCommitFieldValidator.class,
listProvider = AutoCommitListProvider.class
)
public AutoCommitMode isAutoCommitMode() {
return AutoCommitMode.DEFAULT;
@Property(order = 2, id = PROP_AUTO_COMMIT, visibleIf = AutoCommitFieldValidator.class)
public boolean isAutoCommit() {
return true;
}
@Property(order = 3, id = PROP_READ_ONLY, visibleIf = ReadOnlyFieldValidator.class)
@@ -64,13 +40,11 @@ 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;
@@ -111,18 +85,4 @@ 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();
}
}
}
@@ -42,58 +42,63 @@ import java.util.*;
* Web connection info
*/
public class WebPropertyInfo {
@NotNull
private final WebSession session;
@NotNull
private final DBPPropertyDescriptor property;
@Nullable
private final DBPPropertySource propertySource;
private WebSession session;
private DBPPropertyDescriptor property;
private DBPPropertySource propertySource;
private boolean showProtected;
public WebPropertyInfo(
@NotNull WebSession session,
@NotNull DBPPropertyDescriptor property,
@Nullable DBPPropertySource propertySource
) {
private Object[] validValues;
private String[] supportedConfigurationTypes = new String[0];
private Object defaultValue;
public WebPropertyInfo(WebSession session, DBPPropertyDescriptor property, DBPPropertySource propertySource) {
this.session = session;
this.property = property;
this.propertySource = propertySource;
}
public WebPropertyInfo(@NotNull WebSession session, @NotNull DBPPropertyDescriptor property) {
this(session, property, null);
public WebPropertyInfo(WebSession session, DBPPropertyDescriptor property) {
this.session = session;
this.property = property;
}
public boolean isShowProtected() {
return showProtected;
}
public void setShowProtected(boolean showProtected) {
this.showProtected = showProtected;
}
///////////////////////////////////
// General properties
///////////////////////////////////
@NotNull
@Property
public String getId() {
return CommonUtils.toString(property.getId());
}
@NotNull
@Property
public String getDisplayName() {
if (property instanceof DBPNamedObjectLocalized localized) {
return localized.getLocalizedName(session.getLocale());
if (property instanceof DBPNamedObjectLocalized) {
return ((DBPNamedObjectLocalized) property).getLocalizedName(session.getLocale());
} else {
return property.getDisplayName();
}
}
@Nullable
@Property
public String getDescription() {
if (property instanceof DBPObjectWithDescriptionLocalized localized) {
return localized.getLocalizedDescription(session.getLocale());
if (property instanceof DBPObjectWithDescriptionLocalized) {
return ((DBPObjectWithDescriptionLocalized) property).getLocalizedDescription(session.getLocale());
} else {
return property.getDescription();
}
}
@Nullable
@Property
public String getHint() {
return property.getHint();
@@ -104,47 +109,38 @@ public class WebPropertyInfo {
return property instanceof ObjectPropertyDescriptor ? ((ObjectPropertyDescriptor) property).getOrderNumber() : -1;
}
@Nullable
@Property
public String getCategory() {
return property.getCategory();
}
@Nullable
@Property
public String getType() {
return getDataType();
}
@Nullable
@Property
public String getDataType() {
Class<?> dataType = property.getDataType();
return dataType == null ? null : dataType.getSimpleName();
}
@NotNull
@Property
public PropertyLength getLength() {
return property.getLength();
}
@Nullable
@Property
public Object getDefaultValue() throws DBException {
Object defaultValue = property.getDefaultValue();
if (property instanceof ObjectPropertyDescriptor && defaultValue == null) {
return getValue();
}
return defaultValue;
var defaultValue = property.getDefaultValue() == null ? this.defaultValue : property.getDefaultValue();
return defaultValue == null ? getValue() : defaultValue;
}
@Nullable
@Property
public Object getValue() throws DBException {
Object value = propertySource == null ? null : propertySource.getPropertyValue(session.getProgressMonitor(), property.getId());
if (property instanceof ObjectPropertyDescriptor opd &&
(opd.isPassword() || opd.isHidden())
(!showProtected && opd.isPassword() || opd.isHidden())
) {
return maskValue(value);
} else if (property instanceof PropertyDescriptor pd) {
@@ -156,7 +152,6 @@ public class WebPropertyInfo {
return value == null ? null : makePropertyValue(value);
}
@Nullable
@Property
public Object[] getValidValues() {
if (property instanceof IPropertyValueListProvider) {
@@ -169,18 +164,17 @@ public class WebPropertyInfo {
}
return validValues;
}
return validValues;
}
return null;
return validValues;
}
@NotNull
@Property
public String[] getFeatures() {
String[] features = property.getFeatures();
return features == null ? new String[0] : features;
}
@NotNull
@Property
public String[] getSupportedConfigurationTypes() {
if (property instanceof ProviderPropertyDescriptor) {
@@ -188,7 +182,7 @@ public class WebPropertyInfo {
.map(DBPDriverConfigurationType::toString)
.toArray(String[]::new);
}
return new String[0];
return supportedConfigurationTypes;
}
@Property
@@ -209,14 +203,12 @@ public class WebPropertyInfo {
return false;
}
@NotNull
@Override
public String toString() {
return CommonUtils.toString(property.getId());
}
@Nullable
private Object makePropertyValue(@Nullable Object value) {
private Object makePropertyValue(Object value) {
if (value == null) {
return null;
// } else if (value instanceof DBSObject) {
@@ -298,6 +290,21 @@ public class WebPropertyInfo {
return conditions;
}
//TODO: delete after refactoring on front-end
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
//TODO: delete after refactoring on front-end
public void setValidValues(Object[] validValues) {
this.validValues = validValues;
}
//TODO: delete after refactoring on front-end
public void setSupportedConfigurationTypes(String[] supportedConfigurationTypes) {
this.supportedConfigurationTypes = supportedConfigurationTypes;
}
public record Condition(@NotNull String expression, @NotNull Type conditionType) {
public enum Type {
HIDE,
@@ -1028,24 +1028,8 @@ public class LocalResourceController extends BaseLocalResourceController {
try {
while (resourcePath.startsWith("/")) resourcePath = resourcePath.substring(1);
Path targetPath = projectPath.resolve(resourcePath).normalize();
// hack for custom path implementations that returns / as root path
Iterator<Path> projectParts = projectPath.iterator();
Iterator<Path> targetParts = targetPath.iterator();
Iterator<Path> unnormalizedTargetParts = projectPath.resolve(resourcePath).iterator();
while (projectParts.hasNext()) {
if (!targetParts.hasNext()) {
throw new DBException("Invalid resource path");
}
Path projectPart = projectParts.next();
Path targetPart = targetParts.next();
unnormalizedTargetParts.next();
if (!projectPart.equals(targetPart)) {
throw new DBException("Invalid resource path");
}
}
// validating unnormalized target parts to avoid invalid characters in resource names
while (unnormalizedTargetParts.hasNext()) {
GeneralUtils.validateResourceNameUnconditionally(unnormalizedTargetParts.next().toString());
if (!targetPath.startsWith(projectPath)) {
throw new DBException("Invalid resource path");
}
return targetPath;
} catch (InvalidPathException e) {
@@ -59,7 +59,6 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
private final boolean isPrivate;
private final boolean isAuthHidden;
private final boolean isCaseInsensitive;
private final boolean sameTabRedirectOnLogout;
private final boolean serviceProvider;
private final String[] requiredFeatures;
private final boolean isRequired;
@@ -76,7 +75,6 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
this.isRequired = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_REQUIRED));
this.isAuthHidden = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_AUTH_HIDDEN));
this.isCaseInsensitive = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_CASE_INSENSITIVE));
this.sameTabRedirectOnLogout = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_SAME_TAB_LOGOUT_REDIRECT));
this.serviceProvider = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_SERVICE_PROVIDER));
for (IConfigurationElement cfgElement : cfg.getChildren(WebRegistryConstant.TAG_CONFIGURATION)) {
@@ -131,10 +129,6 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
return trusted;
}
public boolean isSameTabRedirectOnLogout() {
return sameTabRedirectOnLogout;
}
public boolean isPrivate() {
return isPrivate;
}
@@ -29,7 +29,6 @@ public class WebRegistryConstant {
public static final String ATTR_REQUIRED = "required";
public static final String ATTR_AUTH_HIDDEN = "authHidden";
public static final String ATTR_CASE_INSENSITIVE = "caseInsensitive";
public static final String ATTR_SAME_TAB_LOGOUT_REDIRECT = "sameTabRedirectOnLogout";
public static final String ATTR_REQUIRED_FEATURES = "requiredFeatures";
public static final String ATTR_CATEGORIES = "categories";
public static final String ATTR_SERVICE_PROVIDER = "serviceProvider";
@@ -192,16 +192,13 @@ public class ServletAppUtils {
@NotNull String serviceId,
@NotNull String origin
) throws DBException {
String finalOrigin = ServletAppUtils.substringBeforeRootURI(webAuthApplication.modifyOrigin(origin));
String finalOrigin = 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);
}
@@ -235,14 +232,6 @@ public class ServletAppUtils {
response.addCookie(sessionCookie);
}
public static String getHeaderOrLegacy(HttpServletRequest request, String headerName, String legacyHeaderName) {
String value = request.getHeader(headerName);
if (CommonUtils.isEmpty(value)) {
value = request.getHeader(legacyHeaderName);
}
return value;
}
public static String getRequestCookie(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
@@ -308,11 +297,6 @@ 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,12 +57,9 @@ public class WebDataSourceUtils {
private WebDataSourceUtils() {
}
public static void saveCredentialsInDataSource(
@NotNull WebConnectionInfo webConnectionInfo,
@NotNull DBPDataSourceContainer dataSourceContainer,
@NotNull DBPConnectionConfiguration configuration
) {
public static void saveCredentialsInDataSource(WebConnectionInfo webConnectionInfo, DBPDataSourceContainer dataSourceContainer, 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)));
@@ -310,7 +307,9 @@ public class WebDataSourceUtils {
if (config.getKeepAliveInterval() >= 0) {
dsConfig.setKeepAliveInterval(config.getKeepAliveInterval());
}
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
if (config.isDefaultAutoCommit() != null) {
dsConfig.getBootstrap().setDefaultAutoCommit(config.isDefaultAutoCommit());
}
dsConfig.getBootstrap().setDefaultCatalogName(config.getDefaultCatalogName());
dsConfig.getBootstrap().setDefaultSchemaName(config.getDefaultSchemaName());
// Save provider props
@@ -331,11 +330,7 @@ public class WebDataSourceUtils {
}
if (CommonUtils.isEmpty(config.getUrl())) {
try {
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
} catch (DBException e) {
log.error("Error preparing connection URL", e);
}
dsConfig.setUrl(driver.getConnectionURL(dsConfig));
}
// 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.5.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 26.1.4.qualifier
Bundle-Release-Date: 20260803
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -16,10 +16,6 @@
</product>
</extension>
<extension point="io.cloudbeaver.feature">
<feature id="ai" label="AI Integration" description="Enables AI Chat in the application." enabledByDefault="true"/>
</extension>
<extension point="org.jkiss.dbeaver.application">
<application
id="cloudbeaver-ce"
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>26.1.5-SNAPSHOT</version>
<version>26.1.4-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.152.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260803
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -66,7 +66,7 @@
<driver id="generic:trino_jdbc"/>
<driver id="duckdb:duckdb_jdbc"/>
<driver id="generic:kyuubi_hive"/>
<driver id="databend:databend_jdbc"/>
<driver id="databend:databend"/>
</extension>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.152-SNAPSHOT</version>
<version>1.0.151-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.5.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 26.1.4.qualifier
Bundle-Release-Date: 20260803
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.5-SNAPSHOT</version>
<version>26.1.4-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,7 +1,3 @@
####################################################
# 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.5.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 26.1.4.qualifier
Bundle-Release-Date: 20260803
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.5-SNAPSHOT</version>
<version>26.1.4-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,10 +1,6 @@
####################################################
# Base schema
####################################################
"Any object (JSON)"
# 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,29 +572,6 @@ 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 {
@@ -737,7 +714,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,7 +1,4 @@
####################################################
# Datasource events
####################################################
extend enum CBServerEventId {
"Datasource created"
@@ -31,10 +31,6 @@ 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"
@@ -61,7 +57,6 @@ 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"
@@ -183,26 +178,6 @@ 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,7 +1,3 @@
####################################################
# SQL events
####################################################
extend enum CBServerEventId {
"Database output log updated (e.g., DBMS in Oracle)"
cb_database_output_log_updated,
@@ -1,6 +1,4 @@
####################################################
# Nodes, objects and properties
####################################################
type ObjectDescriptor {
id: Int
@@ -145,7 +143,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,12 +153,10 @@ 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)")
@@ -215,7 +213,7 @@ type SQLQueryResults {
updateRowCount: Float
sourceQuery: String
"Actual data format of this result"
# Actual data format of this result
dataFormat: ResultDataFormat
resultSet: SQLResultSet
@@ -223,17 +221,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! ]!
}
@@ -298,8 +296,6 @@ type SQLQueryGenerator {
input SQLQueryGeneratorOptions {
useFullyQualifiedNames: Boolean!
compactSql: Boolean!
"Include nested objects, comments and permissions in generated DDL (since 26.0.4)"
showFullDdl: Boolean
}
####################################################
@@ -401,7 +397,7 @@ extend type Query {
options: Object @deprecated(reason: "use generatorOptions (26.0.3)")
nodePathList: [String!]!,
generatorOptions: SQLQueryGeneratorOptions
): String! @deprecated(reason: "use asyncSqlGenerateEntityQuery")
): String!
"""
Returns an auto-generated SQL query based on provided rows data.
@@ -597,13 +593,6 @@ 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,10 +21,8 @@ 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;
@@ -182,26 +180,13 @@ public class WebDatabaseDriverInfo {
@NotNull
@Property
public WebPropertyInfo[] getDriverProperties() throws DBWebException {
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);
}
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);
}
@Property
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,17 +44,12 @@ 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,
@NotNull CBEventsLongPollingServlet owner
) {
public CBEventsLongPolling(@NotNull BaseWebSession webSession) {
this.webSession = webSession;
this.lastPoll = System.currentTimeMillis();
this.webSession.addEventHandler(this);
this.processor = new CBClientEventProcessor(this.webSession);
this.servlet = owner;
}
@NotNull
@@ -95,10 +90,8 @@ 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
@@ -119,7 +112,6 @@ 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-2026 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,7 +83,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
return;
}
CBEventsLongPolling ps = getOrCreatePollSession(ws, resp);
CBEventsLongPolling ps = getOrCreatePollSession(ws);
ps.onPoll();
List<WSEvent> events = ps.pollEvents(POLL_TIMEOUT_SEC);
@@ -111,7 +111,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
return;
}
CBEventsLongPolling ps = getOrCreatePollSession(ws, resp);
CBEventsLongPolling ps = getOrCreatePollSession(ws);
ps.onUserActivity();
String json = new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
@@ -163,12 +163,23 @@ public class CBEventsLongPollingServlet extends HttpServlet {
) {
try {
return resolveSession(req);
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;
} catch (SMException e) {
log.debug("LP request rejected: access token expired");
sendError(resp, HttpConstants.CODE_TOKEN_EXPIRED, e.getMessage());
} catch (SecurityException e) {
log.debug("LP request rejected: " + e.getMessage());
log.warn("LP session resolve failed", e);
sendError(resp, HttpConstants.CODE_UNAUTHORIZED, e.getMessage());
}
return null;
@@ -203,7 +214,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
}
@NotNull
private CBEventsLongPolling getOrCreatePollSession(@NotNull BaseWebSession ws, @NotNull HttpServletResponse resp) {
private CBEventsLongPolling getOrCreatePollSession(@NotNull BaseWebSession ws) {
final String sid = ws.getSessionId();
return sessions.compute(sid, (key, existing) -> {
@@ -211,11 +222,9 @@ public class CBEventsLongPollingServlet extends HttpServlet {
return existing;
}
CBEventsLongPolling ps = new CBEventsLongPolling(ws, this);
CBEventsLongPolling ps = new CBEventsLongPolling(ws);
ps.handleWebSessionEvent(new WSSocketConnectedEvent(ws.getApplication().getApplicationRunId()));
resp.setHeader(WSConstants.WS_SESSION_HEADER, sid);
resp.setHeader(WSConstants.WS_SESSION_HEADER_LEGACY, sid);
log.debug("HTTP Long-Poll channel opened for session " + sid);
return ps;
@@ -244,8 +253,7 @@ 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,
@@ -255,13 +263,6 @@ 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>
@@ -305,7 +306,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
@Override
public BaseWebSession resolve(@NotNull HttpServletRequest req) throws SMException {
String sid = ServletAppUtils.getHeaderOrLegacy(req, WSConstants.WS_SESSION_HEADER, WSConstants.WS_SESSION_HEADER_LEGACY);
String sid = req.getHeader(WSConstants.WS_SESSION_HEADER);
String token = req.getHeader(WSConstants.WS_AUTH_HEADER);
if (CommonUtils.isEmpty(sid) && CommonUtils.isEmpty(token)) {
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,11 +47,6 @@ public class CBEventsWebSocket extends CBAbstractWebSocket implements CBWebSessi
} else {
this.webSession = (BaseWebSession) session.getUserProperties()
.get(CBWebSocketServerConfigurator.PROP_WEB_SESSION);
if (this.webSession == null) {
log.debug("No web session for websocket connection, closing");
close();
return;
}
this.webSession.addEventHandler(this);
handleEvent(new WSSocketConnectedEvent(webSession.getApplication().getApplicationRunId()));
log.debug("EventWebSocket connected to the " + webSession.getSessionId() + " session");
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -88,8 +88,7 @@ public class CBWebSocketServerConfigurator extends ServerEndpointConfig.Configur
throw new RuntimeException(e.getMessage(), e);
}
if (sec.getUserProperties().get(PROP_WEB_SESSION) == null) {
// no throwing exception here. will be handled in onOpen
log.debug("No web session found for websocket request, connection will be closed on open");
throw new RuntimeException("No web session found for websocket request");
}
}
@@ -33,6 +33,7 @@ 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;
@@ -57,7 +58,6 @@ 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,21 +168,10 @@ public class WebDatabaseObjectInfo {
return features.toArray(new String[0]);
}
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);
}
private void getObjectFeatures(DBSObject object, List<String> features) {
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)) {
@@ -192,9 +181,7 @@ 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
@@ -205,12 +192,8 @@ 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,15 +81,6 @@ 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,7 +18,6 @@ package io.cloudbeaver.service.sql;
public record WebSQLGeneratorOptions(
boolean useFullyQualifiedNames,
boolean compactSql,
boolean showFullDdl
boolean compactSql
) {
}
@@ -244,13 +244,6 @@ 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),
@@ -436,12 +429,11 @@ 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, false);
return new WebSQLGeneratorOptions(true, false);
}
return new WebSQLGeneratorOptions(
CommonUtils.toBoolean(optionsMap.get("useFullyQualifiedNames")),
CommonUtils.toBoolean(optionsMap.get("compactSql")),
CommonUtils.toBoolean(optionsMap.get("showFullDdl"))
CommonUtils.toBoolean(optionsMap.get("compactSql"))
);
}
@@ -252,33 +252,10 @@ public class WebServiceSQL implements DBWServiceSQL {
@NotNull WebSQLGeneratorOptions options
) throws DBWebException {
List<DBSObject> objectList = getObjectListFromNodeIds(session, nodePathList);
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);
return createAndRunGenerator(
session, generatorId, objectList,
options.useFullyQualifiedNames(), options.compactSql()
);
}
@NotNull
@@ -293,7 +270,10 @@ 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);
return createAndRunGenerator(
webSession, generatorId, Collections.singletonList(dataProvider),
options.useFullyQualifiedNames(), options.compactSql()
);
}
private void checkAndFillTruncatedData(
@@ -328,17 +308,8 @@ public class WebServiceSQL implements DBWServiceSQL {
@NotNull WebSession session,
@NotNull String generatorId,
@NotNull List<DBSObject> objectList,
@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
boolean useFullyQualifiedNames,
boolean compactSql
) throws DBWebException {
SQLGeneratorDescriptor generator = SQLGeneratorConfigurationRegistry.getInstance().getGenerator(generatorId);
if (generator == null) {
@@ -346,14 +317,9 @@ public class WebServiceSQL implements DBWServiceSQL {
}
try {
SQLGenerator<DBSObject> generatorInstance = generator.createGenerator(objectList);
generatorInstance.setFullyQualifiedNames(options.useFullyQualifiedNames());
generatorInstance.setCompactSQL(options.compactSql());
if (options.showFullDdl()) {
generatorInstance.setShowFullDdl(true);
generatorInstance.setShowComments(true);
generatorInstance.setShowPermissions(true);
}
generatorInstance.run(monitor);
generatorInstance.setFullyQualifiedNames(useFullyQualifiedNames);
generatorInstance.setCompactSQL(compactSql);
generatorInstance.run(session.getProgressMonitor());
return generatorInstance.getResult();
} catch (DBException e) {
throw new DBWebException("Error creating SQL generator", e);
@@ -365,24 +331,12 @@ public class WebServiceSQL implements DBWServiceSQL {
}
@NotNull
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 {
private List<DBSObject> getObjectListFromNodeIds(@NotNull WebSession session, @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(monitor, nodePath);
DBNNode node = navigatorModel.getNodeByPath(session.getProgressMonitor(), 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.151.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.150.qualifier
Bundle-Release-Date: 20260803
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.151-SNAPSHOT</version>
<version>1.0.150-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,7 +1,3 @@
####################################################
# Administration API
####################################################
"Describes the type of the administrative subject (user or team)"
enum AdminSubjectType {
"User"
@@ -1,15 +0,0 @@
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
@@ -1,6 +0,0 @@
source..=src/
output..=target/classes/
bin.includes=.,\
META-INF/,\
schema/,\
plugin.xml
@@ -1,20 +0,0 @@
<?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>
@@ -1,15 +0,0 @@
<?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>
@@ -1,62 +0,0 @@
####################################################
# 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!]
}
@@ -1,282 +0,0 @@
####################################################
# 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!
"Path to the AI engine icon."
icon: 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")
"Cancels the in-progress AI response generation in the specified conversation."
aiCancelChatMessage(conversationId: ID!): Boolean! @since(version: "26.1.5")
"Saves AI settings (e.g. supporting confirming metadata transfer) for the specified connection."
aiSaveDataSourceSettings(dataSourceId: DataSourceIdInput!, settings: AIDataSourceSettingsInput!): AIDataSourceSettingsInfo! @since(version: "25.3.3")
}
@@ -1,46 +0,0 @@
/*
* 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;
}
}
@@ -1,316 +0,0 @@
/*
* 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.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
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.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;
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
) {
CompletableFuture<AIChatConversation> result = new CompletableFuture<>();
AbstractJob job = new AbstractJob(jobName) {
@NotNull
@Override
protected IStatus run(@NotNull DBRProgressMonitor 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) {
if (monitor.isCanceled()) {
log.debug("AI completion cancelled", e);
} else {
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 {
// Only clear the flag if it still points to this job
if (webSession.getAttribute(getWaitingAttr(conversation)) == this) {
webSession.removeAttribute(getWaitingAttr(conversation));
}
}
return Status.OK_STATUS;
}
};
webSession.setAttribute(getWaitingAttr(conversation), job);
job.schedule();
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()));
}
AIChatMessage promptMessage;
AIChatMessage result;
synchronized (conversation) {
String caption = conversation.getCaption();
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");
}
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");
}
}
}
@@ -1,36 +0,0 @@
/*
* 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);
}
}
@@ -1,182 +0,0 @@
/*
* 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
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;
@WebAction
boolean cancelChatMessage(
@NotNull WebSession webSession,
@NotNull String conversationId
) 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;
}
@@ -1,52 +0,0 @@
/*
* 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 io.cloudbeaver.WebServiceUtils;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
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();
}
@Nullable
@Property
public String getIcon() {
return WebServiceUtils.makeIconId(descriptor.getIcon());
}
}
@@ -1,51 +0,0 @@
/*
* 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;
}
}
@@ -1,709 +0,0 @@
/*
* 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.events.WSAiChatMessageEvent;
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.internal.AIChatMessages;
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.AbstractJob;
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;
}
@Override
public boolean cancelChatMessage(
@NotNull WebSession webSession,
@NotNull String conversationId
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
synchronized (conversation) {
boolean completionStarted = conversation.isActive();
conversation.cancelConversation();
boolean hadPendingJob = false;
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) instanceof AbstractJob job) {
hadPendingJob = true;
job.cancel();
}
webSession.removeAttribute(WebAIUtils.getWaitingAttr(conversation));
if (hadPendingJob && !completionStarted) {
// The completion job was still queued, so its response consumer will not be called.
// We need to add a cancellation message to the conversation and notify the client.
AIChatMessage cancelMessage = conversation.addMessage(
AIMessage.warningMessage(AIChatMessages.ai_chat_conversation_cancelled));
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(cancelMessage, conversation)));
}
}
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);
}
}
@@ -1,66 +0,0 @@
/*
* 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 {
}
}
@@ -1,221 +0,0 @@
/*
* 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(
"aiCancelChatMessage",
env -> getService(env).cancelChatMessage(
getWebSession(env),
getArgumentVal(env, "conversationId")
)
).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);
}
}
);
}
}
@@ -1,132 +0,0 @@
/*
* 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;
}
}
@@ -1,44 +0,0 @@
/*
* 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()
);
}
}
@@ -1,73 +0,0 @@
/*
* 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
);
}
}
@@ -1,57 +0,0 @@
/*
* 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);
}
}
@@ -1,64 +0,0 @@
/*
* 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;
}
}
@@ -1,56 +0,0 @@
/*
* 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());
}
}
@@ -1,91 +0,0 @@
/*
* 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();
}
}
@@ -1,27 +0,0 @@
/*
* 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
) {
}
@@ -1,119 +0,0 @@
/*
* 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.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.dbeaver.model.ai.internal.AIChatMessages;
import org.jkiss.utils.CommonUtils;
import java.util.List;
import java.util.concurrent.CancellationException;
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) {
AIMessage aiMessage = throwable instanceof CancellationException cancellationException
? AIMessage.warningMessage(cancellationException.getMessage())
: AIMessage.errorMessage(throwable);
AIChatMessage errorMessage = conversation.addMessage(aiMessage);
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, boolean isCanceled) {
if (responseBuilder.isEmpty()) {
if (isCanceled) {
warning(AIChatMessages.ai_chat_conversation_cancelled);
}
return;
}
AIChatMessage responseMessage = conversation.addMessage(AIMessage.assistantMessage(responseBuilder.toString(), meta));
chatSession.notifyMessageAdd(conversation, responseMessage);
webSession.addSessionEvent(new WSAiChatMessageChunkEvent(conversation.getId(), responseMessage.id(), null, true));
if (isCanceled) {
warning(AIChatMessages.ai_chat_conversation_cancelled);
}
}
}
@@ -1,61 +0,0 @@
/*
* 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;
}
}
@@ -1,56 +0,0 @@
/*
* 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;
}
}
@@ -1,113 +0,0 @@
/*
* 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;
}
}
@@ -1,68 +0,0 @@
/*
* 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;
}
}
@@ -1,101 +0,0 @@
/*
* 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"
));
}
}
}
}
}
@@ -1,22 +0,0 @@
/*
* 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) {
}
@@ -1,27 +0,0 @@
/*
* 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
) {
}
@@ -1,30 +0,0 @@
/*
* 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
) {
}
@@ -1,28 +0,0 @@
/*
* 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.151.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.150.qualifier
Bundle-Release-Date: 20260803
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.151-SNAPSHOT</version>
<version>1.0.150-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,7 +1,3 @@
####################################################
# Authentication API
####################################################
enum AuthCredentialEncryption {
none,
plain,
@@ -19,11 +15,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]
@@ -108,14 +104,8 @@ type FederatedAuthResult @since(version: "25.0.3") {
userTokens: [UserAuthToken!]! @since(version: "25.0.3")
}
type LogoutLink @since(version: "26.1.4") {
url: String!
"If true, the URL must be opened as a full-page redirect in the current tab instead of a popup window"
sameTabRedirect: Boolean!
}
type LogoutInfo @since(version: "23.3.3") {
redirectLinks: [LogoutLink!]!
redirectLinks: [String!]!
}
type UserAuthToken {
@@ -80,9 +80,8 @@ 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) {
@@ -20,5 +20,5 @@ import org.jkiss.code.NotNull;
import java.util.List;
public record WebLogoutInfo(@NotNull List<WebLogoutLink> redirectLinks) {
public record WebLogoutInfo(@NotNull List<String> redirectLinks) {
}
@@ -1,29 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.service.auth;
import org.jkiss.code.NotNull;
/**
* A single logout redirect link.
*
* @param url the URL to open on logout
* @param sameTabRedirect if true, the URL must be opened as a full-page redirect
* in the current tab instead of a popup window
*/
public record WebLogoutLink(@NotNull String url, boolean sameTabRedirect) {
}
@@ -129,7 +129,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
}
try {
Map<String, Object> authParameters = new HashMap<>();
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getHostOriginFromRequest(httpRequest));
authParameters.put(SMConstants.USER_ORIGIN, ServletAppUtils.getOriginFromRequest(httpRequest));
var smAuthInfo = initiateAuthentication(webSession, providerId, providerConfigurationId, authParameters, forceSessionsLogout);
if (smAuthInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) {
@@ -249,7 +249,7 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
List<WebAuthInfo> removedInfos = webSession.removeAuthInfo(providerId);
var cbApp = CBApplication.getInstance();
List<WebLogoutLink> redirectLinks = new ArrayList<>();
List<String> logoutUrls = new ArrayList<>();
String origin = ServletAppUtils.getOriginFromRequest(httpRequest);
for (WebAuthInfo removedInfo : removedInfos) {
if (removedInfo.getAuthProviderDescriptor()
@@ -274,14 +274,11 @@ public class WebServiceAuthImpl implements DBWServiceAuth {
);
}
if (CommonUtils.isNotEmpty(logoutUrl)) {
redirectLinks.add(new WebLogoutLink(
logoutUrl,
removedInfo.getAuthProviderDescriptor().isSameTabRedirectOnLogout()
));
logoutUrls.add(logoutUrl);
}
}
}
return new WebLogoutInfo(redirectLinks);
return new WebLogoutInfo(logoutUrls);
} catch (DBException e) {
throw new DBWebException("User logout failed", e);
}
@@ -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.152.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260803
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.152-SNAPSHOT</version>
<version>1.0.151-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,6 +1,4 @@
####################################################
# Data transfer queries
####################################################
type DataTransferProcessorInfo {
id: ID!
@@ -36,29 +34,6 @@ 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
"Open new connection(s) for the import task"
openNewConnection: Boolean
}
input DataTransferImportParameters {
"Import processor ID"
processorId: ID!
"Import settings. Omitted settings keep their default value"
settings: DataTransferImportSettings
}
input DataTransferParameters {
"Processor ID"
processorId: ID!
@@ -103,15 +78,6 @@ 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,9 +21,13 @@ 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.*;
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.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;
@@ -57,17 +61,11 @@ public interface DBWServiceDataTransfer extends DBWService {
@NotNull
@WebAction(requireGlobalPermissions = RMConstants.PERMISSION_DATA_EDITOR_IMPORT)
WebAsyncTaskInfo asyncImportDataContainer(
@NotNull WebSQLContextInfo sqlContextInfo,
@NotNull String resultsId,
@NotNull WebDataTransferImportParameters parameters,
@NotNull String processorId,
@NotNull Path path,
@NotNull WebSQLResultsInfo webSQLResultsInfo,
@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(
@@ -96,10 +94,4 @@ 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);
}
}

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