mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5103cab9e | ||
|
|
ce309e2061 |
@@ -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:
|
||||
|
||||
@@ -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 = 无自动断开连接
|
||||
@@ -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>
|
||||
|
||||
+3
-1
@@ -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();
|
||||
|
||||
-7
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -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(
|
||||
|
||||
+7
-13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-58
@@ -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;
|
||||
}
|
||||
}
|
||||
-75
@@ -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();
|
||||
}
|
||||
}
|
||||
+3
-43
@@ -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,
|
||||
|
||||
+2
-18
@@ -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) {
|
||||
|
||||
-6
@@ -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;
|
||||
}
|
||||
|
||||
-1
@@ -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);
|
||||
|
||||
+6
-11
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+7
-22
@@ -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
|
||||
|
||||
+2
-10
@@ -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{" +
|
||||
|
||||
+20
-19
@@ -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 +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");
|
||||
|
||||
+2
-3
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-24
@@ -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(
|
||||
|
||||
+1
-2
@@ -18,7 +18,6 @@ package io.cloudbeaver.service.sql;
|
||||
|
||||
public record WebSQLGeneratorOptions(
|
||||
boolean useFullyQualifiedNames,
|
||||
boolean compactSql,
|
||||
boolean showFullDdl
|
||||
boolean compactSql
|
||||
) {
|
||||
}
|
||||
|
||||
+2
-10
@@ -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"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+15
-61
@@ -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")
|
||||
}
|
||||
-46
@@ -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;
|
||||
}
|
||||
}
|
||||
-316
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
-36
@@ -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);
|
||||
}
|
||||
}
|
||||
-182
@@ -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;
|
||||
}
|
||||
-52
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
-51
@@ -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;
|
||||
}
|
||||
}
|
||||
-709
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-66
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
-221
@@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
-132
@@ -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;
|
||||
}
|
||||
}
|
||||
-44
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
-73
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
-57
@@ -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);
|
||||
}
|
||||
}
|
||||
-64
@@ -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;
|
||||
}
|
||||
}
|
||||
-56
@@ -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());
|
||||
}
|
||||
}
|
||||
-91
@@ -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();
|
||||
}
|
||||
}
|
||||
-27
@@ -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
|
||||
) {
|
||||
|
||||
}
|
||||
-119
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-61
@@ -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;
|
||||
}
|
||||
}
|
||||
-56
@@ -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;
|
||||
}
|
||||
}
|
||||
-113
@@ -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;
|
||||
}
|
||||
}
|
||||
-68
@@ -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;
|
||||
}
|
||||
}
|
||||
-101
@@ -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"
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
-22
@@ -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) {
|
||||
}
|
||||
-27
@@ -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
|
||||
) {
|
||||
}
|
||||
-30
@@ -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
|
||||
) {
|
||||
}
|
||||
-28
@@ -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 {
|
||||
|
||||
+1
-2
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
}
|
||||
|
||||
-29
@@ -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) {
|
||||
}
|
||||
+4
-7
@@ -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>
|
||||
|
||||
-34
@@ -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")
|
||||
|
||||
|
||||
+9
-17
@@ -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
Reference in New Issue
Block a user